123 lines
7.3 KiB
Markdown
123 lines
7.3 KiB
Markdown
# 代码审查报告
|
||
|
||
**审查日期**: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` 或 `bcrypt` crate,在登录时 `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, ...]`,后端未提供实时充电曲线数据。
|
||
- **建议**:待后端提供数据后替换为真实折线图。
|
||
|
||
---
|
||
|
||
## 优点
|
||
|
||
1. **Rust 错误处理规范**:无裸 `panic!` / `.unwrap()`,使用 `Result` 和 `?` 运算符,业务逻辑层错误处理完善。
|
||
2. **统一错误类型**:`AppError` enum 覆盖所有错误场景,`IntoResponse` 实现统一返回格式。
|
||
3. **TCP 协议处理健壮**:签名验证(SHA256 + 时间窗)、粘包处理(LF 分隔)、连接超时清理。
|
||
4. **RBAC 权限模型**:JWT + 权限码 + 角色等级(总管理员自动拥有全部权限),设计合理。
|
||
5. **前端类型安全**:无 `as any`,完整的 TypeScript 接口定义。
|
||
6. **H5 用户端实现简洁**:Tailwind CSS 自定义组件,零新增依赖,扫码入口自动路由。
|
||
7. **异步导出流程完整**:任务创建 → 后台 tokio::spawn 生成 Excel → 状态更新 → 下载。
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
| 维度 | 评分 | 说明 |
|
||
|------|------|------|
|
||
| 编译/类型检查 | 9/10 | Rust clippy 15 条警告,前端 tsc 零错误(IconFolderOpen 预存问题) |
|
||
| 代码规范 | 7/10 | 部分文件过长,单函数基本在 80 行以内 |
|
||
| 错误处理 | 8/10 | Rust 端优秀,前端 try/catch 覆盖全面 |
|
||
| 安全性 | 5/10 | SQL 注入风险、明文密码、数据隔离缺失 |
|
||
| 架构设计 | 8/10 | 分层清晰,路由模块化,TCP 协议处理得当 |
|
||
|
||
**优先级排序**:
|
||
1. 🔴 修复 SQL 注入(operation_logs、energy_stats、charge_records)
|
||
2. 🔴 实现密码哈希(argon2/bcrypt)
|
||
3. 🔴 添加组织级数据隔离
|
||
4. 🟡 补充缺失的权限校验
|
||
5. 🟡 修复 IconFolderOpen 构建错误
|
||
6. 🟡 拆分大文件(organizations.rs、users.rs、devices/index.tsx)
|