charging-cabinet/tasks/review-report.md
2026-07-02 05:38:01 +08:00

8.7 KiB
Raw Blame 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_adminnormal_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_idcompartments 表的主键,而非 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 逻辑,与列表查询保持一致。


问题3protocol.rsexpect() 可能导致生产环境 panic

位置software/server/src/tcp/protocol.rs:58protocol.rs:80

描述

pub fn to_json(&self) -> String {
    serde_json::to_string(self).expect("ServerResponse 序列化不应失败")
}

虽然 ServerResponseDeviceCommand 的字段理论上不会导致序列化失败(都是标准类型),但 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()
}

警告(🟡 建议修复)

警告1JWT_SECRET_DEFAULT 重复定义

位置config.rs:4middleware/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:7users.rs:303users.rs:438users.tsx:67users.tsx:74

描述:创建用户和重置密码时,如果未提供密码则回退到 DEFAULT_PASSWORD"123456")。前端用户管理页面也将默认密码硬编码为 "123456"。

风险:管理员可能忘记修改默认密码,导致新用户以弱密码登录。

建议:创建用户时强制要求提供密码;或在首次登录时强制修改密码。前端不应预填默认密码。


警告3list_users 函数过长160行

位置users.rs:96-256

描述:该函数包含 4 种查询分支(有/无 keyword × 有/无 org_filter每个分支独立构建 SQL。加上组织名称批量查询和结果映射总行数达 160 行。

建议:使用动态条件构建器统一查询逻辑,减少分支重复。


警告4list_roleslist_permissions 缺少权限校验

位置roles.rs:36-72roles.rs:232-248

描述:这两个接口虽然接受 CurrentUser 参数(确保已认证),但未调用 check_permission。任何已认证用户都可以查看所有角色和权限码。

建议:添加 role:view 权限校验(list_permissions 同理)。


警告5前端 as unknown as 类型断言

位置devices/index.tsx:81devices/index.tsx:103devices/index.tsx:260AddCabinetModal.tsx:44

描述4 处使用了 as unknown as TargetType 双重类型断言来绕过类型检查。这通常意味着 API 返回类型定义不完整或 API 层封装有问题。

建议:在 API 层定义完整的响应类型,避免在组件中使用类型断言。


建议( 可优化)

建议1cabinet_tree.rs 全量加载数据

位置cabinet_tree.rs:46-57

描述:组织树接口一次性加载所有 projects 和 cabinets然后在内存中过滤。数据量大时可能有性能问题。

建议:根据用户角色,使用 SQL WHERE 条件在数据库层过滤,只返回用户有权查看的数据。


建告2energy_stats.rs 组织过滤代码重复

位置energy_stats.rs:90-99energy_stats.rs:174-183energy_stats.rs:230-236energy_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 个问题后,项目即可达到生产就绪状态。其余警告和建议可作为后续迭代优化项。