220 lines
6.2 KiB
Markdown
220 lines
6.2 KiB
Markdown
# 任务015:Nemotron审查问题修复
|
||
|
||
## 目标
|
||
|
||
修复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接口必须添加权限校验和组织数据隔离
|