charging-cabinet/tasks/014-mimo-review-fixes.md
2026-07-02 05:38:01 +08:00

7.7 KiB
Raw Blame History

任务014MIMO审查问题修复

目标

修复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()));
}

质量约束

  1. 完成代码后执行 cargo check + cargo clippy + tsc --noEmit,零报错零警告
  2. 分层拆分单函数≤80行命名语义化完整注释
  3. 所有外部IO/网络请求异常捕获禁止裸panic
  4. 分支逻辑全覆盖,不遗漏兜底分支
  5. 常量抽离不使用废弃API
  6. Rust内存安全React函数组件+Hooks
  7. H5接口必须添加权限校验