8.7 KiB
全面代码审查报告
总体评价
评分: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 子句:
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:
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
描述:
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:
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
描述:两处分别定义了相同的常量:
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 级别日志,可接受)。
优点
- 架构清晰:Axum 路由分层合理,
AppState共享模式规范,中间件/路由/业务逻辑分离良好 - 安全基础扎实:全部使用参数化 SQL 查询(零字符串拼接)、argon2 密码哈希、JWT + RBAC 权限模型、组织级数据隔离
- TCP 协议实现完整:签名验证、心跳检测、超时清理、LF 消息边界处理正确
- 异步导出流程:任务创建→后台生成→状态轮询→下载,模式规范
- 错误处理统一:
AppError枚举 +IntoResponse实现,HTTP 状态码映射正确 - 数据库迁移:自动建表 + 种子数据,部署友好
- 前端代码整洁:函数式组件 + Hooks,无 Class 组件,无
as any类型断言 - 注释充分:模块级文档注释完整,函数级注释覆盖入参和行为说明
总结
项目整体质量良好,架构和安全方面表现出色。必须立即修复的 3 个严重问题:
- 权限种子数据缺失( 最高优先级)— 直接导致非超管用户功能不可用,影响整个 RBAC 体系
- 充电记录计数 SQL 错误( 高优先级)— 导致数据展示不准确
- protocol.rs 中的 expect()( 中优先级)— 潜在的生产环境 panic 风险
修复这 3 个问题后,项目即可达到生产就绪状态。其余警告和建议可作为后续迭代优化项。