222 lines
13 KiB
Markdown
222 lines
13 KiB
Markdown
|
|
# 全面代码审查报告
|
|||
|
|
|
|||
|
|
## 总体评价
|
|||
|
|
|
|||
|
|
**评分:7.5 / 10**
|
|||
|
|
|
|||
|
|
项目整体代码质量良好,架构清晰,模块职责分明。后端 Rust 代码规范,通过了 `cargo clippy` 零警告检查;前端 TypeScript 代码通过 `tsc --noEmit` 零错误,`vite build` 构建成功。RBAC 权限模型完整,TCP 协议处理健壮。主要不足集中在安全细节、性能优化和部分功能未完成方面。
|
|||
|
|
|
|||
|
|
## 统计
|
|||
|
|
|
|||
|
|
- 后端文件数:29(Rust)
|
|||
|
|
- 前端文件数:35(TypeScript/TSX)
|
|||
|
|
- 后端代码行数:5,162
|
|||
|
|
- 前端代码行数:5,282
|
|||
|
|
- 总代码行数:10,444
|
|||
|
|
- 问题总数:19(严重 5 / 警告 8 / 建议 6)
|
|||
|
|
|
|||
|
|
## 静态检查结果
|
|||
|
|
|
|||
|
|
| 检查项 | 结果 |
|
|||
|
|
|--------|------|
|
|||
|
|
| `cargo check` | 通过 |
|
|||
|
|
| `cargo clippy` | 通过(零警告) |
|
|||
|
|
| `tsc --noEmit` | 通过(零错误) |
|
|||
|
|
| `vite build` | 通过(有 chunk 大小警告) |
|
|||
|
|
|
|||
|
|
## 严重问题(必须修复)
|
|||
|
|
|
|||
|
|
### 问题1:设备指令下发接口缺少权限校验
|
|||
|
|
- **位置**:`software/server/src/tcp/commands.rs:36-61`
|
|||
|
|
- **描述**:`send_command` 端点(`POST /api/devices/:dev_id/command`)虽然经过认证中间件,但未调用 `check_permission` 进行 RBAC 权限校验。任何已登录用户均可向设备下发任意指令(包括开门、供电等危险操作)。
|
|||
|
|
- **风险**:未授权用户可操控设备,造成安全隐患。
|
|||
|
|
- **建议**:在 `send_command` 函数开头添加权限校验,根据 `act` 字段判断所需权限码(如 `device:operate`、charge:open_door` 等)。
|
|||
|
|
|
|||
|
|
### 问题2:组织树接口缺少权限校验和数据隔离
|
|||
|
|
- **位置**:`software/server/src/routes/cabinet_tree.rs:14-92`
|
|||
|
|
- **描述**:`organization_tree` 端点未提取 `CurrentUser`,无权限校验,也无组织级数据隔离。企业管理员可通过此接口看到所有组织的数据。
|
|||
|
|
- **风险**:数据泄露,违反多租户隔离原则。
|
|||
|
|
- **建议**:添加 `CurrentUser` 提取器,校验 `device:view` 权限,并根据 `role_level` 和 `organization_id` 过滤返回数据。
|
|||
|
|
|
|||
|
|
### 问题3:默认密码 "123456" 硬编码
|
|||
|
|
- **位置**:`software/server/src/routes/users.rs:302`、`software/server/src/routes/users.rs:437`
|
|||
|
|
- **描述**:创建用户和重置密码时,默认密码硬编码为 `"123456"`。此密码过于简单,且散落在代码中。
|
|||
|
|
- **风险**:安全风险,弱密码可能被暴力破解。
|
|||
|
|
- **建议**:将默认密码抽离为配置常量,并强制用户首次登录时修改密码。至少应使用随机生成的初始密码。
|
|||
|
|
|
|||
|
|
### 问题4:auth_str 安全码明文打印到日志
|
|||
|
|
- **位置**:`software/server/src/tcp/handler.rs:61`
|
|||
|
|
- **描述**:`handle_auth_str` 函数将生成的安全码以明文写入日志:`tracing::info!("[auth_str] dev_id={} auth_str={}", dev_id, auth_str)`。
|
|||
|
|
- **风险**:安全码泄露,攻击者可通过日志获取签名密钥。
|
|||
|
|
- **建议**:移除日志中的 `auth_str` 值,仅记录 `dev_id`,或使用脱敏处理(如仅打印前2位)。
|
|||
|
|
|
|||
|
|
### 问题5:前端刷新后用户信息丢失
|
|||
|
|
- **位置**:`software/web/src/stores/auth.ts:71-76`
|
|||
|
|
- **描述**:`restore` 函数仅从 localStorage 恢复 token,但未调用 `/api/auth/me` 获取用户信息和权限码。刷新页面后,虽然 token 存在,但 `user` 为 null,导致前端权限判断(`hasPermission`)始终返回 false,所有权限按钮消失。
|
|||
|
|
- **风险**:用户体验严重受损,刷新后所有权限控制按钮消失。
|
|||
|
|
- **建议**:在 `restore` 中检测到 token 后,异步调用 `/api/auth/me` 恢复完整用户信息。
|
|||
|
|
|
|||
|
|
## 警告(建议修复)
|
|||
|
|
|
|||
|
|
### 警告1:前端打包体积过大
|
|||
|
|
- **位置**:`software/web/` 构建配置
|
|||
|
|
- **描述**:vite build 产物 JS 文件 1,314 KB(gzip 后 374 KB),超过 500 KB 警告阈值。未做代码分割。
|
|||
|
|
- **建议**:使用 `React.lazy()` + 动态 `import()` 对路由级组件进行代码分割,尤其是 H5 端和后台管理端应分离打包。
|
|||
|
|
|
|||
|
|
### 警告2:柜子详情接口存在 N+1 查询
|
|||
|
|
- **位置**:`software/server/src/routes/cabinets.rs:284-308`
|
|||
|
|
- **描述**:`get_cabinet_detail` 先查询仓控板列表,然后对每个仓控板单独查询仓体。若柜子有 N 块仓控板,则产生 N+1 次数据库查询。
|
|||
|
|
- **建议**:使用一条 SQL 通过 JOIN 查询所有仓控板和仓体,或在应用层批量查询。
|
|||
|
|
|
|||
|
|
### 警告3:角色列表接口存在 N+1 查询
|
|||
|
|
- **位置**:`software/server/src/routes/roles.rs:42-69`
|
|||
|
|
- **描述**:`list_roles` 对每个角色单独查询权限码,产生 N+1 次查询。
|
|||
|
|
- **建议**:使用一条 SQL 通过 JOIN 查询所有角色及其权限码。
|
|||
|
|
|
|||
|
|
### 警告4:多处使用 `as unknown as` 类型断言
|
|||
|
|
- **位置**:`software/web/src/pages/devices/index.tsx:81,103`、`OrganizationTree.tsx:169,171`
|
|||
|
|
- **描述**:前端代码中多处使用 `as unknown as` 进行类型强转,绕过了 TypeScript 类型检查。
|
|||
|
|
- **建议**:修正 API 响应类型定义,使其与实际返回数据结构一致,消除不必要的类型断言。
|
|||
|
|
|
|||
|
|
### 警告5:下载文件端点未实现
|
|||
|
|
- **位置**:`software/server/src/routes/downloads.rs:162`
|
|||
|
|
- **描述**:`get_download` 返回 `download_url: "/api/downloads/{id}/file"`,但此路由未在 `routes/mod.rs` 中注册,实际下载功能不可用。
|
|||
|
|
- **建议**:实现文件下载端点,使用 `axum::response::File` 或 `tower-http` 的 `ServeDir` 提供文件服务。
|
|||
|
|
|
|||
|
|
### 警告6:`org_condition` 辅助函数未被充分使用
|
|||
|
|
- **位置**:`software/server/src/middleware/auth.rs:255-270`
|
|||
|
|
- **描述**:`org_condition` 函数已定义但仅在 `charge_records.rs` 和 `device_logs.rs` 中使用。`cabinet_tree.rs`、`energy_stats.rs` 等模块自行实现了类似的组织过滤逻辑,代码重复。
|
|||
|
|
- **建议**:统一使用 `org_condition` 和 `org_condition_for_logs` 进行组织数据隔离,减少重复代码。
|
|||
|
|
|
|||
|
|
### 警告7:H5 用户端 API 端点后端未实现
|
|||
|
|
- **位置**:`software/web/src/h5/api.ts`
|
|||
|
|
- **描述**:H5 API 调用了 `/h5/auth/login`、`/h5/dashboard`、`/h5/projects` 等端点,但后端路由中未注册这些路径。H5 用户端目前无法正常工作。
|
|||
|
|
- **建议**:明确 H5 后端 API 的开发计划,或在 H5 代码中标注为 WIP(Work In Progress)。
|
|||
|
|
|
|||
|
|
### 警告8:Dashboard 页面为占位符
|
|||
|
|
- **位置**:`software/web/src/pages/Dashboard.tsx`
|
|||
|
|
- **描述**:后台管理首页仅显示欢迎文字,无任何数据展示。能耗管理页面已有总览数据,Dashboard 应复用或引用。
|
|||
|
|
- **建议**:实现 Dashboard 数据展示(如今日充电次数、在线设备数、告警数等),可复用 `energy-stats/summary` 接口。
|
|||
|
|
|
|||
|
|
## 建议(可优化)
|
|||
|
|
|
|||
|
|
### 建议1:LIKE 查询中 `%` 通配符未转义
|
|||
|
|
- **位置**:`software/server/src/routes/users.rs:111`
|
|||
|
|
- **描述**:`let like_pattern = format!("%{}%", keyword)` 未对关键字中的 `%` 和 `_` 进行转义,用户输入这些字符时可能产生非预期的模糊匹配。
|
|||
|
|
- **建议**:对 keyword 中的 `%` 和 `_` 进行转义后再拼接 LIKE 模式。
|
|||
|
|
|
|||
|
|
### 建议2:连接池最大连接数可配置化
|
|||
|
|
- **位置**:`software/server/src/db/mod.rs:11`
|
|||
|
|
- **描述**:MySQL 连接池 `max_connections(20)` 硬编码。
|
|||
|
|
- **建议**:通过环境变量 `DB_MAX_CONNECTIONS` 配置,便于不同环境调整。
|
|||
|
|
|
|||
|
|
### 建议3:充电曲线使用静态模拟数据
|
|||
|
|
- **位置**:`software/web/src/h5/pages/compartment-detail.tsx:174`
|
|||
|
|
- **描述**:通道详情页的充电曲线使用硬编码数据 `[30, 45, 55, ...]` 展示,非真实数据。
|
|||
|
|
- **建议**:标注为 TODO 或待后端提供充电曲线 API 后替换,当前可接受。
|
|||
|
|
|
|||
|
|
### 建议4:`log_operation` 函数可抽取为公共模块
|
|||
|
|
- **位置**:`software/server/src/routes/users.rs:63-85`
|
|||
|
|
- **描述**:操作日志记录函数 `log_operation` 定义在 `users.rs` 中,但其他模块(如 `roles.rs`、`cabinets.rs`)也需要记录操作日志。
|
|||
|
|
- **建议**:将 `log_operation` 抽取到公共模块(如 `middleware/auth.rs` 或新建 `utils/log.rs`),供所有路由模块复用。
|
|||
|
|
|
|||
|
|
### 建议5:前端 `eslint-disable` 注释
|
|||
|
|
- **位置**:`software/web/src/h5/pages/devices.tsx:62`、`device-detail.tsx:37`、`compartment-detail.tsx:43`
|
|||
|
|
- **描述**:H5 页面中有多处 `eslint-disable-next-line react-hooks/exhaustive-deps`,跳过了 Hook 依赖检查。
|
|||
|
|
- **建议**:补充正确的依赖项,或使用 `useCallback` 包裹函数以明确依赖关系。
|
|||
|
|
|
|||
|
|
### 建议6:数据库迁移缺少索引定义
|
|||
|
|
- **位置**:`software/server/src/db/migrate.rs`
|
|||
|
|
- **描述**:建表语句中未创建索引(除 `energy_stats` 的联合唯一键外)。`charge_records`、`device_logs`、`operation_logs` 等高频查询表缺少对 `cabinet_id`、`created_at` 等常用过滤字段的索引。
|
|||
|
|
- **建议**:在迁移脚本中添加索引,如:
|
|||
|
|
- `charge_records`: `cabinet_id`, `start_time`
|
|||
|
|
- `device_logs`: `cabinet_id`, `created_at`
|
|||
|
|
- `operation_logs`: `user_id`, `created_at`
|
|||
|
|
- `cabinets`: `project_id`
|
|||
|
|
|
|||
|
|
## 审查清单完成情况
|
|||
|
|
|
|||
|
|
### 1. 编译与类型检查
|
|||
|
|
- [x] `cargo check` 零报错零警告
|
|||
|
|
- [x] `cargo clippy` 零报错零警告
|
|||
|
|
- [x] `tsc --noEmit` 零报错零警告
|
|||
|
|
- [x] `vite build` 成功(有 chunk 大小警告)
|
|||
|
|
|
|||
|
|
### 2. 代码规范
|
|||
|
|
- [x] 单函数基本 <=80 行(少数函数略超,如 `list_users` 约 160 行,可拆分)
|
|||
|
|
- [x] 命名语义化,无无意义缩写
|
|||
|
|
- [x] 完整注释(模块级文档注释 + 函数文档注释)
|
|||
|
|
- [x] 常量抽离(如 `COST_PER_KWH`、`SIGN_START`、`TIMESTAMP_TOLERANCE_SECS`)
|
|||
|
|
- [x] 无废弃 API 使用
|
|||
|
|
|
|||
|
|
### 3. 错误处理
|
|||
|
|
- [x] 所有外部 IO/网络请求有异常捕获
|
|||
|
|
- [x] 无裸 `panic!`(`expect` 仅用于启动阶段的致命错误,如数据库连接失败)
|
|||
|
|
- [x] 分支逻辑基本全覆盖
|
|||
|
|
- [x] 错误信息有意义(使用中文描述,含上下文)
|
|||
|
|
|
|||
|
|
### 4. Rust 专项
|
|||
|
|
- [x] 内存安全,使用 sqlx 连接池无冗余拷贝
|
|||
|
|
- [x] 无 `unsafe` 块
|
|||
|
|
- [x] 所有权管理合理(`ConnectionPool` 使用 `Arc<RwLock<>>` 共享)
|
|||
|
|
- [x] 异步代码无阻塞操作
|
|||
|
|
|
|||
|
|
### 5. React 专项
|
|||
|
|
- [x] 函数式组件 + Hooks,无 Class 组件
|
|||
|
|
- [x] 状态管理使用 Zustand,分层清晰
|
|||
|
|
- [x] useEffect 有清理函数(如 DownloadCenter 的 setInterval)
|
|||
|
|
- [ ] 少量 `as unknown as` 类型断言(见警告4)
|
|||
|
|
|
|||
|
|
### 6. 安全
|
|||
|
|
- [x] SQL 注入防护(全部使用 `?` 参数化查询)
|
|||
|
|
- [x] 密码哈希使用 argon2
|
|||
|
|
- [x] JWT 密钥有环境变量覆盖机制 + 警告
|
|||
|
|
- [x] 输入参数校验(手机号 11 位、IMEI 15 位等)
|
|||
|
|
- [ ] 权限校验未覆盖所有 API 接口(见严重问题 1、2)
|
|||
|
|
- [ ] 数据隔离部分缺失(见严重问题 2)
|
|||
|
|
- [ ] 敏感参数脱敏不足(见严重问题 4)
|
|||
|
|
|
|||
|
|
### 7. 业务逻辑
|
|||
|
|
- [x] TCP 协议解析正确(签名验证、LF 粘包处理、超时清理)
|
|||
|
|
- [x] 权限模型正确(总管理员/企业管理员/普通用户三级)
|
|||
|
|
- [x] 异步导出流程完整(任务创建 -> 后台处理 -> 下载中心)
|
|||
|
|
- [x] 前端权限组件正确隐藏/禁用按钮
|
|||
|
|
|
|||
|
|
### 8. 架构
|
|||
|
|
- [x] 接口层、业务逻辑层、数据模型层分离
|
|||
|
|
- [x] 无循环依赖
|
|||
|
|
- [x] 模块职责清晰
|
|||
|
|
- [x] 路由注册完整
|
|||
|
|
|
|||
|
|
### 9. 性能
|
|||
|
|
- [ ] 数据库查询缺少索引(见建议 6)
|
|||
|
|
- [ ] 存在 N+1 查询(见警告 2、3)
|
|||
|
|
- [x] 前端列表有分页
|
|||
|
|
- [x] 大数据量导出异步处理
|
|||
|
|
|
|||
|
|
### 10. 可维护性
|
|||
|
|
- [x] 文件长度合理(最长 467 行 energy/index.tsx)
|
|||
|
|
- [x] 组件拆分合理
|
|||
|
|
- [x] 代码重复度低(组织过滤逻辑有重复,见警告 6)
|
|||
|
|
- [x] 配置外部化(环境变量 + dotenvy)
|
|||
|
|
|
|||
|
|
## 优点
|
|||
|
|
|
|||
|
|
1. **架构清晰**:后端按功能域拆分模块(auth、organizations、cabinets、users 等),每个文件职责单一,不超过 500 行。
|
|||
|
|
2. **安全性基础扎实**:密码使用 argon2 哈希、JWT 有过期时间、SQL 全部参数化、TCP 签名验证含时间窗口校验。
|
|||
|
|
3. **RBAC 权限模型完整**:权限码精确到按钮级别,前端 `Permission` 组件支持 hidden/disabled 两种模式,总管理员自动拥有全部权限。
|
|||
|
|
4. **TCP 协议处理健壮**:使用 `LinesCodec` 处理粘包、`mpsc` 通道分离读写、心跳超时自动清理、连接池 `Arc<RwLock>` 支持并发。
|
|||
|
|
5. **异步导出设计合理**:download_tasks 表管理任务状态、tokio::spawn 后台生成 Excel、前端轮询刷新、过期文件清理。
|
|||
|
|
6. **前端工程化规范**:Zustand 状态管理简洁、API 层统一封装、路由守卫完善、H5 端与后台管理端独立认证。
|
|||
|
|
7. **代码注释充分**:每个模块有模块级文档注释,关键函数有入参/返回值说明,复杂逻辑有行内注释。
|
|||
|
|
8. **数据库迁移自动化**:启动时自动建表 + 初始化权限码和角色,部署友好。
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
项目整体质量良好,代码规范、架构清晰、安全基础扎实。**优先修复建议**:
|
|||
|
|
|
|||
|
|
1. **P0(立即修复)**:设备指令下发接口添加权限校验(严重问题 1)、组织树接口添加权限校验和数据隔离(严重问题 2)。
|
|||
|
|
2. **P1(尽快修复)**:前端 restore 后加载用户信息(严重问题 5)、auth_str 日志脱敏(严重问题 4)、默认密码安全化(严重问题 3)。
|
|||
|
|
3. **P2(迭代优化)**:N+1 查询优化、数据库索引添加、前端代码分割、下载文件端点实现、Dashboard 数据填充。
|
|||
|
|
4. **P3(后续完善)**:H5 后端 API 开发、操作日志函数抽取公共模块、LIKE 通配符转义。
|