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

222 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 全面代码审查报告
## 总体评价
**评分7.5 / 10**
项目整体代码质量良好,架构清晰,模块职责分明。后端 Rust 代码规范,通过了 `cargo clippy` 零警告检查;前端 TypeScript 代码通过 `tsc --noEmit` 零错误,`vite build` 构建成功。RBAC 权限模型完整TCP 协议处理健壮。主要不足集中在安全细节、性能优化和部分功能未完成方面。
## 统计
- 后端文件数29Rust
- 前端文件数35TypeScript/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"`。此密码过于简单,且散落在代码中。
- **风险**:安全风险,弱密码可能被暴力破解。
- **建议**:将默认密码抽离为配置常量,并强制用户首次登录时修改密码。至少应使用随机生成的初始密码。
### 问题4auth_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 KBgzip 后 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` 进行组织数据隔离,减少重复代码。
### 警告7H5 用户端 API 端点后端未实现
- **位置**`software/web/src/h5/api.ts`
- **描述**H5 API 调用了 `/h5/auth/login``/h5/dashboard``/h5/projects` 等端点但后端路由中未注册这些路径。H5 用户端目前无法正常工作。
- **建议**:明确 H5 后端 API 的开发计划,或在 H5 代码中标注为 WIPWork In Progress
### 警告8Dashboard 页面为占位符
- **位置**`software/web/src/pages/Dashboard.tsx`
- **描述**后台管理首页仅显示欢迎文字无任何数据展示。能耗管理页面已有总览数据Dashboard 应复用或引用。
- **建议**:实现 Dashboard 数据展示(如今日充电次数、在线设备数、告警数等),可复用 `energy-stats/summary` 接口。
## 建议(可优化)
### 建议1LIKE 查询中 `%` 通配符未转义
- **位置**`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 通配符转义。