charging-cabinet/tasks/015-nemotron-review-fixes.md

220 lines
6.2 KiB
Markdown
Raw Normal View History

# 任务015Nemotron审查问题修复
## 目标
修复Nemotron审查发现的7个严重问题。
## 修复清单
### 1. 权限码种子数据缺失( 高优先级)
**位置:** `src/db/migrate.rs`
**问题:** `org:*``project:*``device:edit` 等权限码未在`seed_permissions`中初始化。
**修复方案:**
```rust
// 在 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?;
}
```
**角色权限分配:**
```rust
// 总管理员 — 全部权限
// 企业管理员 — 添加 org:*, project:*, device:edit 权限
// 普通用户 — 只保留 view 权限
```
### 2. H5接口数据隔离🔴 高优先级)
**位置:** `src/routes/h5.rs`
**问题:** `get_dashboard`返回全局统计数据,`get_cabinet_detail`/`get_compartment_detail`无权限校验。
**修复方案:**
```rust
/// 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.rs``src/h5/pages/home.tsx`
**问题:** 后端返回`{compartment_id, cabinet_id}`,前端期望`{project, cabinet}`
**修复方案(后端):**
```rust
// 修改告警查询,返回前端期望的字段
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**
```rust
// 确保所有SQL查询使用参数化绑定
// 检查并修复任何 format! 拼接SQL的地方
```
**4.2 数据库索引migrate.rs**
```rust
// 添加关键索引
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 消除重复代码**
```rust
// 提取公共的组织过滤逻辑到 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接口必须添加权限校验和组织数据隔离