charging-cabinet/tasks/review-report.md

207 lines
8.7 KiB
Markdown
Raw Normal View History

# 全面代码审查报告
## 总体评价
**评分7.5/10**
整体架构清晰Axum 后端 + React 前端技术栈选型合理。代码分层明确安全基础扎实argon2 哈希、参数化 SQL、RBAC 权限模型、组织数据隔离。TCP 通讯协议实现完整。主要扣分点权限种子数据缺失导致非超管用户功能不可用、SQL JOIN 错误、部分函数过长。
## 统计
| 指标 | 数值 |
|------|------|
| 后端 Rust 文件数 | 29 |
| 后端代码行数 | 5,198 |
| 前端 TSX 文件数 | 27 |
| 前端代码行数 | 4,535 |
| 总代码行数 | 9,733 |
| 静态检查 | `cargo check``cargo clippy``tsc --noEmit``vite build` ✅ |
| 问题总数 | 11严重 3 / 警告 5 / 建议 3 |
---
## 严重问题( 必须修复)
### 问题1权限种子数据缺失 — 非超管用户功能不可用
**位置**`software/server/src/db/migrate.rs:186-219`
**描述**`seed_permissions` 中缺少以下权限码,但代码中多处通过 `check_permission` 使用:
| 缺失权限码 | 使用位置 |
|------------|---------|
| `device:edit` | `cabinets.rs:166` 更新柜子、`cabinets.rs:237` 重新生成安全码 |
| `org:view` | `organizations.rs:154` |
| `org:create` | `organizations.rs:172` |
| `org:edit` | `organizations.rs:198` |
| `org:delete` | `organizations.rs:227` |
| `project:view` | `projects.rs:20` |
| `project:create` | `projects.rs:40` |
| `project:edit` | `projects.rs:78` |
| `project:delete` | `projects.rs:107` |
**风险**:任何 role_level < 2 的用户企业管理员普通用户调用这些接口时`check_permission` 会返回 `Err(Forbidden)`即使角色已分配这些权限因为数据库中根本不存在这些权限码无法关联)。
**建议**:在 `seed_permissions` 中补充上述 9 个权限码,并为 `org_admin``normal_user` 角色分配相应权限。
---
### 问题2充电记录计数 SQL JOIN 错误
**位置**`software/server/src/routes/charge_records.rs:187-191`
**描述**:列表查询的 COUNT 子句:
```sql
SELECT COUNT(*) FROM charge_records cr
LEFT JOIN cabin_boards cb ON cr.compartment_id = cb.id
```
`cr.compartment_id``compartments` 表的主键,而非 `cabin_boards` 表的主键。错误的 JOIN 条件会在 `compartment_id` 恰好等于某个 `cabin_boards.id` 时产生错误匹配,导致 COUNT 结果不准确。
对比同文件第 136-149 行的列表查询,使用了正确的三表 JOIN
```sql
LEFT JOIN compartments comp ON cr.compartment_id = comp.id
LEFT JOIN cabin_boards cb ON comp.cabin_board_id = cb.id
```
**风险**:当 `cabin_board_id` 过滤条件生效时,计数结果将严重失准。无过滤时因 LEFT JOIN 不过滤行,影响较小但仍有潜在错误匹配。
**建议**:修正 COUNT 查询的 JOIN 逻辑,与列表查询保持一致。
---
### 问题3`protocol.rs` 中 `expect()` 可能导致生产环境 panic
**位置**`software/server/src/tcp/protocol.rs:58``protocol.rs:80`
**描述**
```rust
pub fn to_json(&self) -> String {
serde_json::to_string(self).expect("ServerResponse 序列化不应失败")
}
```
虽然 `ServerResponse``DeviceCommand` 的字段理论上不会导致序列化失败(都是标准类型),但 `expect()``serde_json` 遇到非 UTF-8 字符或自引用类型时会 panic。TCP 连接的 panic 如果未被 tokio 的 panic handler 捕获,可能导致整个 TCP 服务崩溃。
**风险**生产环境下若序列化异常TCP 服务可能崩溃,影响所有在线设备的通讯。
**建议**:将 `expect()` 替换为 `unwrap_or_default()` 或返回 `Result`
```rust
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
```
---
## 警告(🟡 建议修复)
### 警告1JWT_SECRET_DEFAULT 重复定义
**位置**`config.rs:4``middleware/auth.rs:33`
**描述**:两处分别定义了相同的常量:
```rust
const JWT_SECRET_DEFAULT: &str = "pms-dev-secret-change-me-in-production";
```
`config.rs` 中的 `validate()` 方法检查是否使用默认密钥并打印警告,而 `middleware/auth.rs` 中每次认证时也读取环境变量并回退到默认值。两处定义不一致可能导致维护问题。
**建议**:将常量统一到 `config.rs` 并导出,`middleware/auth.rs` 通过 `AppState` 获取密钥,而非每次从环境变量读取。
---
### 警告2默认密码 "123456" 硬编码且作为回退值
**位置**`config.rs:7``users.rs:303``users.rs:438``users.tsx:67``users.tsx:74`
**描述**:创建用户和重置密码时,如果未提供密码则回退到 `DEFAULT_PASSWORD`"123456")。前端用户管理页面也将默认密码硬编码为 "123456"。
**风险**:管理员可能忘记修改默认密码,导致新用户以弱密码登录。
**建议**:创建用户时强制要求提供密码;或在首次登录时强制修改密码。前端不应预填默认密码。
---
### 警告3`list_users` 函数过长160行
**位置**`users.rs:96-256`
**描述**:该函数包含 4 种查询分支(有/无 keyword × 有/无 org_filter每个分支独立构建 SQL。加上组织名称批量查询和结果映射总行数达 160 行。
**建议**:使用动态条件构建器统一查询逻辑,减少分支重复。
---
### 警告4`list_roles` 和 `list_permissions` 缺少权限校验
**位置**`roles.rs:36-72``roles.rs:232-248`
**描述**:这两个接口虽然接受 `CurrentUser` 参数(确保已认证),但未调用 `check_permission`。任何已认证用户都可以查看所有角色和权限码。
**建议**:添加 `role:view` 权限校验(`list_permissions` 同理)。
---
### 警告5前端 `as unknown as` 类型断言
**位置**`devices/index.tsx:81``devices/index.tsx:103``devices/index.tsx:260``AddCabinetModal.tsx:44`
**描述**4 处使用了 `as unknown as TargetType` 双重类型断言来绕过类型检查。这通常意味着 API 返回类型定义不完整或 API 层封装有问题。
**建议**:在 API 层定义完整的响应类型,避免在组件中使用类型断言。
---
## 建议( 可优化)
### 建议1`cabinet_tree.rs` 全量加载数据
**位置**`cabinet_tree.rs:46-57`
**描述**:组织树接口一次性加载所有 projects 和 cabinets然后在内存中过滤。数据量大时可能有性能问题。
**建议**:根据用户角色,使用 SQL WHERE 条件在数据库层过滤,只返回用户有权查看的数据。
---
### 建告2`energy_stats.rs` 组织过滤代码重复
**位置**`energy_stats.rs:90-99``energy_stats.rs:174-183``energy_stats.rs:230-236``energy_stats.rs:289-298`
**描述**4 个函数中重复了几乎相同的组织过滤逻辑(判断 role_level、构建 org_filter/org_binds
**建议**:抽取为辅助函数,与 `auth.rs` 中的 `org_condition` 统一。
---
### 建议3TCP 连接 JSON 解析失败时无响应
**位置**`tcp/server.rs:88-94`
**描述**:设备发送的消息 JSON 解析失败时,`continue` 跳过不回复。这在安全上是合理的(不给恶意设备反馈),但合法设备可能因编码问题发送非标准 JSON 而无法得知原因。
**建议**:保持当前行为(安全优先),但增加更详细的 debug 日志(当前已有 warn 级别日志,可接受)。
---
## 优点
1. **架构清晰**Axum 路由分层合理,`AppState` 共享模式规范,中间件/路由/业务逻辑分离良好
2. **安全基础扎实**:全部使用参数化 SQL 查询零字符串拼接、argon2 密码哈希、JWT + RBAC 权限模型、组织级数据隔离
3. **TCP 协议实现完整**签名验证、心跳检测、超时清理、LF 消息边界处理正确
4. **异步导出流程**:任务创建→后台生成→状态轮询→下载,模式规范
5. **错误处理统一**`AppError` 枚举 + `IntoResponse` 实现HTTP 状态码映射正确
6. **数据库迁移**:自动建表 + 种子数据,部署友好
7. **前端代码整洁**:函数式组件 + Hooks无 Class 组件,无 `as any` 类型断言
8. **注释充分**:模块级文档注释完整,函数级注释覆盖入参和行为说明
---
## 总结
项目整体质量良好,架构和安全方面表现出色。**必须立即修复的 3 个严重问题**
1. **权限种子数据缺失** 最高优先级)— 直接导致非超管用户功能不可用,影响整个 RBAC 体系
2. **充电记录计数 SQL 错误** 高优先级)— 导致数据展示不准确
3. **protocol.rs 中的 expect()** 中优先级)— 潜在的生产环境 panic 风险
修复这 3 个问题后,项目即可达到生产就绪状态。其余警告和建议可作为后续迭代优化项。