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

6.2 KiB
Raw Blame History

任务015Nemotron审查问题修复

目标

修复Nemotron审查发现的7个严重问题。

修复清单

1. 权限码种子数据缺失( 高优先级)

位置: src/db/migrate.rs

问题: org:*project:*device:edit 等权限码未在seed_permissions中初始化。

修复方案:

// 在 seed_permissions 函数中添加缺失的权限码
let permissions = vec![
    // ... 已有的权限码
    
    // 组织管理
    ("org:view", "查看组织"),
    ("org:create", "创建组织"),
    ("org:edit", "编辑组织"),
    ("org:delete", "删除组织"),
    
    // 项目管理
    ("project:view", "查看项目"),
    ("project:create", "创建项目"),
    ("project:edit", "编辑项目"),
    ("project:delete", "删除项目"),
    
    // 设备管理(补充)
    ("device:edit", "编辑设备"),
];

// 插入数据库(使用 INSERT IGNORE 避免重复)
for (code, name) in permissions {
    sqlx::query(
        "INSERT IGNORE INTO permissions (code, name) VALUES (?, ?)"
    )
    .bind(code)
    .bind(name)
    .execute(pool)
    .await?;
}

角色权限分配:

// 总管理员 — 全部权限
// 企业管理员 — 添加 org:*, project:*, device:edit 权限
// 普通用户 — 只保留 view 权限

2. H5接口数据隔离🔴 高优先级)

位置: src/routes/h5.rs

问题: get_dashboard返回全局统计数据,get_cabinet_detail/get_compartment_detail无权限校验。

修复方案:

/// GET /api/h5/dashboard — H5首页数据带组织隔离
pub async fn get_dashboard(
    user: CurrentUser,
    State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
    auth::check_permission(&user, "device:view")?;
    
    let db = &state.mysql;
    
    // 根据角色过滤数据
    let org_filter = if user.role_level >= 2 {
        None  // 总管理员看全部
    } else {
        user.organization_id  // 企业管理员只看本组织
    };
    
    // 查询柜子统计(带组织过滤)
    let cabinet_stats = if let Some(org_id) = org_filter {
        sqlx::query_as::<_, (i64, i64, i64, i64)>(
            r#"SELECT 
                COUNT(*) as total,
                SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as online,
                SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as charging,
                SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) as fault
               FROM cabinets c
               INNER JOIN projects p ON c.project_id = p.id
               WHERE p.organization_id = ?"#
        )
        .bind(org_id)
        .fetch_one(db)
        .await?
    } else {
        // 总管理员查询全部
        // ...
    };
    
    Ok(Json(json!({
        "total": cabinet_stats.0,
        "online": cabinet_stats.1,
        "charging": cabinet_stats.2,
        "fault": cabinet_stats.3
    })))
}

/// 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> {
    auth::check_permission(&user, "device:view")?;
    
    let db = &state.mysql;
    
    // 验证用户是否有权访问该设备
    if user.role_level < 2 {
        if let Some(org_id) = user.organization_id {
            let access: Option<(i64,)> = sqlx::query_as(
                r#"SELECT c.id FROM cabinets c
                   INNER JOIN projects p ON c.project_id = p.id
                   WHERE c.id = ? AND p.organization_id = ?"#
            )
            .bind(id)
            .bind(org_id)
            .fetch_optional(db)
            .await?;
            
            if access.is_none() {
                return Err(AppError::Forbidden("无权访问该设备".into()));
            }
        } else {
            return Err(AppError::Forbidden("无组织关联".into()));
        }
    }
    
    // ... 原有查询逻辑
}

3. 前后端H5告警字段匹配🔴 中优先级)

位置: src/routes/h5.rssrc/h5/pages/home.tsx

问题: 后端返回{compartment_id, cabinet_id},前端期望{project, cabinet}

修复方案(后端):

// 修改告警查询,返回前端期望的字段
let alerts = sqlx::query_as::<_, (String, String, String)>(
    r#"SELECT 
           p.name as project,
           cab.name as cabinet,
           dl.content as message
       FROM device_logs dl
       INNER JOIN cabinets cab ON dl.cabinet_id = cab.id
       INNER JOIN projects p ON cab.project_id = p.id
       WHERE dl.log_type = 3  -- 告警
       ORDER BY dl.created_at DESC
       LIMIT 10"#
)
.fetch_all(db)
.await?;

Ok(Json(json!({
    "alerts": alerts.iter().map(|(project, cabinet, message)| {
        json!({
            "project": project,
            "cabinet": cabinet,
            "message": message
        })
    }).collect::<Vec<_>>()
})))

4. 其他严重问题修复

4.1 SQL参数化charge_records.rs

// 确保所有SQL查询使用参数化绑定
// 检查并修复任何 format! 拼接SQL的地方

4.2 数据库索引migrate.rs

// 添加关键索引
sqlx::query("CREATE INDEX idx_cabinets_project ON cabinets(project_id)")
    .execute(pool).await?;
sqlx::query("CREATE INDEX idx_charge_records_cabinet ON charge_records(cabinet_id)")
    .execute(pool).await?;
sqlx::query("CREATE INDEX idx_device_logs_cabinet ON device_logs(cabinet_id)")
    .execute(pool).await?;

4.3 消除重复代码

// 提取公共的组织过滤逻辑到 middleware/auth.rs
pub fn org_condition(user: &CurrentUser) -> String {
    if user.role_level >= 2 {
        String::new()
    } else if let Some(org_id) = user.organization_id {
        format!("AND organization_id = {}", org_id)
    } else {
        "AND 1=0".to_string()
    }
}

质量约束

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