7.7 KiB
7.7 KiB
任务014:MIMO审查问题修复
目标
修复MIMO审查发现的5个严重问题和部分警告。
修复清单
1. list_users元组编译问题( 高优先级)
位置: src/routes/users.rs
问题: list_users 使用7元素元组可能导致sqlx编译错误。
修复方案:
// 定义结构体替代元组
#[derive(sqlx::FromRow)]
struct UserRow {
id: i64,
phone: String,
name: Option<String>,
role: i32,
organization_id: Option<i64>,
status: i32,
created_at: chrono::NaiveDateTime,
}
// 查询时使用结构体
let users = sqlx::query_as::<_, UserRow>(
"SELECT id, phone, name, role, organization_id, status, created_at FROM users ..."
)
.fetch_all(pool)
.await?;
2. 充电记录COUNT查询缺少JOIN( 高优先级)
位置: src/routes/charge_records.rs
问题: COUNT查询缺少compartments表JOIN,分页总数计算错误。
修复方案:
// 修改COUNT查询,添加JOIN
let total: (i64,) = sqlx::query_as(
r#"SELECT COUNT(*) FROM charge_records cr
INNER JOIN compartments c ON cr.compartment_id = c.id
INNER JOIN cabin_boards cb ON c.cabin_board_id = cb.id
INNER JOIN cabinets cab ON cb.cabinet_id = cab.id
WHERE cab.project_id = ?"# // 添加组织过滤
)
.bind(project_id)
.fetch_one(pool)
.await?;
3. H5后端接口实现(🔴 高优先级)
位置: 新建 src/routes/h5.rs
问题: H5前端10+个接口均会404,后端未实现。
修复方案:
// 新建 h5.rs 模块
use axum::extract::{Path, State};
use axum::Json;
use serde_json::{json, Value};
use crate::error::AppError;
use crate::middleware::auth::{self, CurrentUser};
use crate::tcp::commands::AppState;
/// GET /api/h5/dashboard — H5首页数据
pub async fn get_dashboard(
user: CurrentUser,
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
// 查询柜子统计、今日充电、告警等
// ...
}
/// GET /api/h5/projects — H5项目列表
pub async fn get_projects(
user: CurrentUser,
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
// 查询用户有权访问的项目
// ...
}
/// GET /api/h5/cabinets — H5设备列表
pub async fn get_cabinets(
user: CurrentUser,
State(state): State<AppState>,
Path(project_id): Path<i64>,
) -> Result<Json<Value>, AppError> {
// 查询项目下的柜子
// ...
}
/// GET /api/h5/cabinets/:id — H5设备详情
pub async fn get_cabinet_detail(
user: CurrentUser,
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<Value>, AppError> {
// 查询柜子详情(仓控板+仓体)
// ...
}
/// POST /api/h5/charge/start — H5开始充电
pub async fn start_charge(
user: CurrentUser,
State(state): State<AppState>,
Json(body): Json<StartChargeRequest>,
) -> Result<Json<Value>, AppError> {
auth::check_permission(&user, "charge:start")?;
// 下发充电指令到设备
// ...
}
/// POST /api/h5/charge/stop — H5停止充电
pub async fn stop_charge(
user: CurrentUser,
State(state): State<AppState>,
Json(body): Json<StopChargeRequest>,
) -> Result<Json<Value>, AppError> {
auth::check_permission(&user, "charge:stop")?;
// 下发停止指令到设备
// ...
}
/// POST /api/h5/door/open — H5开门
pub async fn open_door(
user: CurrentUser,
State(state): State<AppState>,
Json(body): Json<OpenDoorRequest>,
) -> Result<Json<Value>, AppError> {
auth::check_permission(&user, "charge:open_door")?;
// 下发开门指令到设备
// ...
}
路由注册: 在 src/routes/mod.rs 中添加:
mod h5;
// 在 build 函数中添加
router = router
.route("/api/h5/dashboard", get(h5::get_dashboard))
.route("/api/h5/projects", get(h5::get_projects))
.route("/api/h5/cabinets", get(h5::get_cabinets))
.route("/api/h5/cabinets/:id", get(h5::get_cabinet_detail))
.route("/api/h5/charge/start", post(h5::start_charge))
.route("/api/h5/charge/stop", post(h5::stop_charge))
.route("/api/h5/door/open", post(h5::open_door));
4. 文件下载端点实现(🔴 高优先级)
位置: src/routes/downloads.rs
问题: /api/downloads/:id/file 未实现,导出功能不完整。
修复方案:
use axum::body::Body;
use axum::http::header;
use axum::response::Response;
use tokio::fs;
/// GET /api/downloads/:id/file — 下载文件
pub async fn download_file(
user: CurrentUser,
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Response<Body>, AppError> {
auth::check_permission(&user, "charge_record:export")?;
let db = &state.mysql;
// 查询下载任务
let task: Option<DownloadTask> = sqlx::query_as(
"SELECT * FROM download_tasks WHERE id = ? AND user_id = ?"
)
.bind(id)
.bind(user.user_id)
.fetch_optional(db)
.await?;
let task = task.ok_or(AppError::NotFound("下载任务不存在".into()))?;
if task.status != 2 {
return Err(AppError::BadRequest("文件未生成完成".into()));
}
// 读取文件
let file_path = task.file_path.ok_or(AppError::NotFound("文件路径不存在".into()))?;
let file_content = fs::read(&file_path).await.map_err(|e| {
AppError::Internal(format!("读取文件失败: {}", e))
})?;
// 返回文件
let response = Response::builder()
.status(200)
.header(header::CONTENT_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.header(header::CONTENT_DISPOSITION, format!("attachment; filename=\"{}\"", task.file_name))
.body(Body::from(file_content))
.map_err(|e| AppError::Internal(format!("构建响应失败: {}", e)))?;
Ok(response)
}
5. TCP auth_str频率限制(🟡 中优先级)
位置: src/tcp/handler.rs
问题: auth_str接口无频率限制,可能被暴力调用。
修复方案:
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::time::{Duration, Instant};
// 频率限制器
pub struct RateLimiter {
attempts: Arc<RwLock<HashMap<String, Vec<Instant>>>>,
max_attempts: usize,
window: Duration,
}
impl RateLimiter {
pub fn new(max_attempts: usize, window_secs: u64) -> Self {
Self {
attempts: Arc::new(RwLock::new(HashMap::new())),
max_attempts,
window: Duration::from_secs(window_secs),
}
}
pub async fn check(&self, key: &str) -> bool {
let mut attempts = self.attempts.write().await;
let now = Instant::now();
let window_start = now - self.window;
// 清理过期记录
let entry = attempts.entry(key.to_string()).or_insert_with(Vec::new);
entry.retain(|t| *t > window_start);
if entry.len() >= self.max_attempts {
return false; // 超限
}
entry.push(now);
true
}
}
// 在 handler 中使用
lazy_static::lazy_static! {
static ref AUTH_STR_LIMITER: RateLimiter = RateLimiter::new(5, 300); // 5次/5分钟
}
// 在 auth_str handler 中
if !AUTH_STR_LIMITER.check(&dev_id).await {
return Err(AppError::TooManyRequests("请求过于频繁".into()));
}
质量约束
- 完成代码后执行
cargo check+cargo clippy+tsc --noEmit,零报错零警告 - 分层拆分,单函数≤80行,命名语义化,完整注释
- 所有外部IO/网络请求异常捕获,禁止裸panic
- 分支逻辑全覆盖,不遗漏兜底分支
- 常量抽离,不使用废弃API
- Rust内存安全,React函数组件+Hooks
- H5接口必须添加权限校验