207 lines
8.7 KiB
Markdown
207 lines
8.7 KiB
Markdown
|
|
# 全面代码审查报告
|
|||
|
|
|
|||
|
|
## 总体评价
|
|||
|
|
|
|||
|
|
**评分: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()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 警告(🟡 建议修复)
|
|||
|
|
|
|||
|
|
### 警告1:JWT_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` 统一。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 建议3:TCP 连接 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 个问题后,项目即可达到生产就绪状态。其余警告和建议可作为后续迭代优化项。
|