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接口必须添加权限校验和组织数据隔离
|