7.3 KiB
7.3 KiB
代码审查报告
审查日期:2026-07-01
审查范围:software/server/src/(Rust + Axum)、software/web/src/(React + TypeScript)
审查工具:cargo check、cargo clippy、tsc --noEmit、人工审计
总体评价:7.5 / 10
项目整体架构清晰,分层合理,错误处理较为完善。Rust 端无裸 panic! / .unwrap(),前端无 as any。主要问题集中在:SQL 注入风险、数据隔离缺失、权限校验不完整、部分文件过长。
严重问题(🔴 必须修复)
问题 1:操作日志查询存在 SQL 注入风险
- 位置:
src/routes/operation_logs.rs:49-63 - 描述:
action、start_time、end_time参数通过format!直接拼接到 SQL WHERE 子句中,虽然做了'替换,但仍可被绕过(如1; DROP TABLE users--)。 - 风险:攻击者可注入任意 SQL 语句,导致数据泄露或破坏。
- 建议:改用
sqlx::query_as的参数化绑定,或将条件构建逻辑统一为build_where_clause模式(如charge_records.rs已实现的那样)。
问题 2:充电记录 LIMIT/OFFSET 通过 format! 拼接
- 位置:
src/routes/charge_records.rs:130 - 描述:
LIMIT {} OFFSET {}直接使用format!拼接i64参数。虽然参数类型是i64,但若未来参数类型变更或被误传字符串,将产生注入。 - 风险:中等(当前类型安全,但模式脆弱)。
- 建议:将
limit/offset也通过.bind()传入。
问题 3:无组织/项目级数据隔离
- 位置:
src/routes/organizations.rs全部路由、src/routes/charge_records.rs、src/routes/device_logs.rs、src/routes/energy_stats.rs - 描述:所有查询均无
WHERE organization_id = ?或WHERE project_id = ?过滤。企业管理员可以查看所有组织的数据,而非仅其管辖范围。 - 风险:高。多租户场景下,企业 A 的管理员可以看到企业 B 的所有设备和数据。
- 建议:在
CurrentUser中携带organization_id,在所有查询中自动附加WHERE organization_id = ?过滤。
问题 4:部分路由缺少权限校验
- 位置:
src/routes/charge_records.rs:158、src/routes/device_logs.rs:62、src/routes/energy_stats.rs:81-283、src/routes/downloads.rs:87、src/routes/organizations.rs:144-551 - 描述:这些路由处理器未调用
auth::check_permission(),仅依赖 JWT 认证中间件。任何登录用户均可访问充电记录、设备日志、能耗统计、下载中心等敏感数据。 - 风险:中。越权访问非管理类功能。
- 建议:为每个路由添加对应的
check_permission调用,如user:view、energy:view等。
问题 5:密码明文存储
- 位置:
src/routes/auth.rs:51 - 描述:
stored_password != input.password直接比较明文。代码注释也明确写道"后续应改为 bcrypt/argon2 哈希"。 - 风险:高。数据库泄露即所有用户密码暴露。
- 建议:集成
argon2或bcryptcrate,在登录时argon2::verify,在创建/重置时argon2::hash。
警告(🟡 建议修复)
警告 1:organizations.rs 文件过长(693 行)
- 位置:
src/routes/organizations.rs - 描述:单个文件 693 行,包含组织/项目/柜子 CRUD + 树状结构 + 柜子详情,职责过多。
- 建议:拆分为
organizations.rs、projects.rs、cabinets.rs三个文件。
警告 2:users.rs 文件过长(495 行)
- 位置:
src/routes/users.rs - 描述:包含用户 CRUD、密码重置、状态管理、操作日志记录,超过 80 行/函数约束(部分辅助函数虽短,但整体文件过大)。
- 建议:将
log_operation辅助函数提取到独立模块。
警告 3:JWT 默认密钥在生产环境可能泄露
- 位置:
src/middleware/auth.rs:28 - 描述:
JWT_SECRET_DEFAULT = "pms-dev-secret-change-me-in-production"如果环境变量未设置,将使用此默认值。 - 建议:启动时若检测到默认密钥,打印 WARN 日志;或在
config.rs中将 JWT_SECRET 设为必填。
警告 4:energy_stats 导出 LIMIT/OFFSET 同样存在 format! 拼接
- 位置:
src/routes/energy_stats.rs:341 - 描述:与 charge_records.rs 相同的问题,LIMIT/OFFSET 通过
format!拼接。 - 建议:统一使用参数化绑定。
警告 5:devices/index.tsx 文件过长(631 行)
- 位置:
src/pages/devices/index.tsx - 描述:单个 TSX 文件 631 行,包含组织树、柜子列表、CRUD 弹窗、右键菜单、批量添加等全部逻辑。
- 建议:提取
OrganizationalTree.tsx、CabinetGrid.tsx、AddCabinetModal.tsx为独立组件。
警告 6:Pre-existing 构建错误(IconFolderOpen)
- 位置:
src/pages/devices/index.tsx:33 - 描述:
IconFolderOpen从@arco-design/web-react/icon导入,但该版本未导出此图标。导致vite build失败。 - 风险:前端无法构建生产包。
- 建议:替换为
IconFolder或其他可用图标。
警告 7:clippy 警告 15 条
- 位置:多处
- 描述:主要包括
collapsible_if(可折叠嵌套 if)、type_complexity(复杂类型未抽取 type alias)、manual_unwrap_or_default、obfuscated_if_else等。 - 建议:运行
cargo clippy --fix自动修复大部分问题。
警告 8:前端 H5 页面中充电曲线为静态 Mock 数据
- 位置:
src/h5/pages/compartment-detail.tsx:156 - 描述:充电曲线使用硬编码的模拟数据
[30, 45, 55, ...],后端未提供实时充电曲线数据。 - 建议:待后端提供数据后替换为真实折线图。
优点
- Rust 错误处理规范:无裸
panic!/.unwrap(),使用Result和?运算符,业务逻辑层错误处理完善。 - 统一错误类型:
AppErrorenum 覆盖所有错误场景,IntoResponse实现统一返回格式。 - TCP 协议处理健壮:签名验证(SHA256 + 时间窗)、粘包处理(LF 分隔)、连接超时清理。
- RBAC 权限模型:JWT + 权限码 + 角色等级(总管理员自动拥有全部权限),设计合理。
- 前端类型安全:无
as any,完整的 TypeScript 接口定义。 - H5 用户端实现简洁:Tailwind CSS 自定义组件,零新增依赖,扫码入口自动路由。
- 异步导出流程完整:任务创建 → 后台 tokio::spawn 生成 Excel → 状态更新 → 下载。
总结
| 维度 | 评分 | 说明 |
|---|---|---|
| 编译/类型检查 | 9/10 | Rust clippy 15 条警告,前端 tsc 零错误(IconFolderOpen 预存问题) |
| 代码规范 | 7/10 | 部分文件过长,单函数基本在 80 行以内 |
| 错误处理 | 8/10 | Rust 端优秀,前端 try/catch 覆盖全面 |
| 安全性 | 5/10 | SQL 注入风险、明文密码、数据隔离缺失 |
| 架构设计 | 8/10 | 分层清晰,路由模块化,TCP 协议处理得当 |
优先级排序:
- 🔴 修复 SQL 注入(operation_logs、energy_stats、charge_records)
- 🔴 实现密码哈希(argon2/bcrypt)
- 🔴 添加组织级数据隔离
- 🟡 补充缺失的权限校验
- 🟡 修复 IconFolderOpen 构建错误
- 🟡 拆分大文件(organizations.rs、users.rs、devices/index.tsx)