263 lines
16 KiB
Markdown
263 lines
16 KiB
Markdown
|
|
# 全面代码审查报告
|
|||
|
|
|
|||
|
|
## 总体评价
|
|||
|
|
|
|||
|
|
**评分:7.5 / 10**
|
|||
|
|
|
|||
|
|
项目整体架构清晰,模块拆分合理,代码质量较好。后端采用 Rust + Axum 技术栈,安全性较高;前端 React + Arco Design 组件化开发规范。权限模型(RBAC + 数据隔离)设计完善,TCP 通讯协议实现完整。主要问题集中在:部分编译错误风险、H5 后端接口缺失、文件下载端点未实现、以及若干安全/性能细节需改进。
|
|||
|
|
|
|||
|
|
## 统计
|
|||
|
|
|
|||
|
|
- 后端文件数:29(Rust)
|
|||
|
|
- 前端文件数:36(TypeScript/React)
|
|||
|
|
- 总代码行数:约 6,800 行(后端约 3,500 行,前端约 3,300 行)
|
|||
|
|
- 问题总数:23(严重 5 / 警告 10 / 建议 8)
|
|||
|
|
|
|||
|
|
## 审查清单完成情况
|
|||
|
|
|
|||
|
|
### 1. 编译与类型检查
|
|||
|
|
- [ ] `cargo check` — 未能执行(环境中无 Rust 工具链),**代码审查发现潜在编译问题**(见严重问题 #1、#2)
|
|||
|
|
- [ ] `cargo clippy` — 未能执行
|
|||
|
|
- [ ] `tsc --noEmit` — 未能执行
|
|||
|
|
- [ ] `vite build` — 未能执行
|
|||
|
|
|
|||
|
|
### 2. 代码规范
|
|||
|
|
- [x] 单函数 ≤ 80 行 — 大部分符合,`users.rs::list_users` 约 160 行超标
|
|||
|
|
- [x] 命名语义化 — 整体良好,无无意义缩写
|
|||
|
|
- [x] 完整注释 — Rust 文档注释完善,前端 JSDoc 基本覆盖
|
|||
|
|
- [x] 常量抽离 — `COST_PER_KWH`、`JWT_SECRET_DEFAULT` 等已抽离
|
|||
|
|
- [x] 无废弃 API — 未发现
|
|||
|
|
|
|||
|
|
### 3. 错误处理
|
|||
|
|
- [x] 所有外部 IO/网络请求有异常捕获 — 全面覆盖
|
|||
|
|
- [x] 无裸 `panic!` 或 `unwrap()` — `expect()` 仅用于启动阶段关键操作
|
|||
|
|
- [x] 分支逻辑全覆盖 — match 语句均有兜底分支
|
|||
|
|
- [x] 错误信息有意义 — 中文错误信息便于调试
|
|||
|
|
|
|||
|
|
### 4. Rust 专项
|
|||
|
|
- [x] 内存安全,无冗余拷贝 — 合理使用引用和 Clone
|
|||
|
|
- [x] 无 `unsafe` 块 — 确认无
|
|||
|
|
- [x] 所有权管理合理 — ConnectionPool 使用 Arc<RwLock<>> 模式
|
|||
|
|
- [x] 异步代码无阻塞操作 — 确认无阻塞调用
|
|||
|
|
|
|||
|
|
### 5. React 专项
|
|||
|
|
- [x] 函数式组件 + Hooks — 全部为函数式组件
|
|||
|
|
- [x] 状态分层管理 — Zustand 管理认证状态,页面级 useState
|
|||
|
|
- [x] 无内存泄漏 — useEffect 清理函数正确(DownloadCenter、compartment-detail)
|
|||
|
|
- [ ] 无 `as any` 类型断言 — 存在 `as never`、`as unknown as` 类型断言
|
|||
|
|
|
|||
|
|
### 6. 安全
|
|||
|
|
- [x] SQL 注入防护 — 全部使用参数化查询 `?` 占位符
|
|||
|
|
- [x] 密码哈希 — argon2(行业领先)
|
|||
|
|
- [x] JWT 密钥安全 — 支持环境变量,启动时校验默认值告警
|
|||
|
|
- [x] 输入参数校验 — 手机号、IMEI、角色值等均有校验
|
|||
|
|
- [x] 权限校验覆盖 — 所有 API 接口均有 `check_permission`
|
|||
|
|
- [x] 数据隔离 — `org_condition` / `org_condition_for_logs` 实现组织级过滤
|
|||
|
|
- [ ] 敏感参数脱敏 — `auth_str` 在 API 响应中明文返回
|
|||
|
|
|
|||
|
|
### 7. 业务逻辑
|
|||
|
|
- [x] TCP 协议解析正确 — 签名验证、LF 粘包处理、超时清理均已实现
|
|||
|
|
- [x] 权限模型正确 — 总管理员/企业管理员/普通用户三级
|
|||
|
|
- [x] 异步导出流程完整 — 任务创建 → tokio 后台处理 → 下载中心查询
|
|||
|
|
- [x] 前端权限组件正确 — Permission 组件支持 hidden/disabled 两种模式
|
|||
|
|
|
|||
|
|
### 8. 架构
|
|||
|
|
- [x] 接口层、业务逻辑层、数据模型层分离 — routes/ 处理请求,handler/ 处理业务逻辑
|
|||
|
|
- [x] 无循环依赖 — 模块间单向依赖
|
|||
|
|
- [x] 模块职责清晰 — 按功能域拆分(organizations/projects/cabinets 等)
|
|||
|
|
- [x] 路由注册完整 — `routes/mod.rs` 聚合所有子路由
|
|||
|
|
|
|||
|
|
### 9. 性能
|
|||
|
|
- [ ] 数据库查询有索引 — 迁移脚本缺少关键索引(见警告 #6)
|
|||
|
|
- [x] 无 N+1 查询问题 — 柜子详情的仓板/仓体查询在可接受范围(N≤6)
|
|||
|
|
- [x] 前端列表有分页 — 所有列表页均实现分页
|
|||
|
|
- [x] 大数据量导出异步处理 — tokio::spawn 后台生成 Excel
|
|||
|
|
|
|||
|
|
### 10. 可维护性
|
|||
|
|
- [ ] 文件长度合理(≤ 300 行)— `users.rs`(553 行)、`energy/index.tsx`(468 行)超标
|
|||
|
|
- [x] 组件拆分合理 — OrganizationTree、CabinetGrid、DownloadCenter 等独立组件
|
|||
|
|
- [ ] 代码重复度低 — 导出逻辑、状态映射存在重复
|
|||
|
|
- [x] 配置外部化 — 环境变量 + `.env` 文件
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 严重问题(必须修复)
|
|||
|
|
|
|||
|
|
### 问题 1:`list_users` 查询列数与解构不匹配
|
|||
|
|
- **位置**:`software/server/src/routes/users.rs:158`
|
|||
|
|
- **描述**:`query_as` 查询返回 7 列 `(id, phone, name, role, organization_id, status, created_at)`,但行后 `UpdateUserInput` 结构体定义出现在文件中间(第 41 行),且 `query_as` 没有对应的 7 元素元组类型声明。第 158 行的类型注解为 `Vec<(i64, String, Option<String>, i32, Option<i64>, i32, String)>`(7 元素),但此处 `query_as` 调用没有使用 `FromRow` 派生结构体,而是直接使用元组。sqlx 对元组元素数量有上限(通常支持到 6 个),7 元素元组可能导致编译错误。
|
|||
|
|
- **风险**:编译失败,无法构建后端服务
|
|||
|
|
- **建议**:定义 `#[derive(sqlx::FromRow)]` 结构体替代元组,或将查询拆分为两步
|
|||
|
|
|
|||
|
|
### 问题 2:充电记录 COUNT 查询 JOIN 表不匹配
|
|||
|
|
- **位置**:`software/server/src/routes/charge_records.rs:187-193`
|
|||
|
|
- **描述**:计数 SQL 为 `SELECT COUNT(*) FROM charge_records cr LEFT JOIN cabin_boards cb ON cr.compartment_id = cb.id`,但 WHERE 条件中引用了 `cb.id`(期望是 cabin_boards.id)和 `cr.compartment_id`。实际 `fetch_charge_rows` 中 JOIN 了 `compartments comp ON cr.compartment_id = comp.id` 再 JOIN `cabin_boards cb ON comp.cabin_board_id = cb.id`。COUNT 查询缺少 `compartments` 表的 JOIN,导致 `cabin_board_id` 过滤条件无法正确工作。
|
|||
|
|
- **风险**:按仓板过滤时,分页总数计算错误,前端显示数据不完整
|
|||
|
|
- **建议**:COUNT 查询使用与列表查询相同的 JOIN 链路:`cr → compartments → cabin_boards`
|
|||
|
|
|
|||
|
|
### 问题 3:H5 后端接口全部缺失
|
|||
|
|
- **位置**:`software/web/src/h5/api.ts` 全部接口
|
|||
|
|
- **描述**:H5 用户端调用了 `/h5/auth/login`、`/h5/dashboard`、`/h5/projects`、`/h5/cabinets`、`/h5/cabinets/:id`、`/h5/compartments/:id`、`/h5/charge/start`、`/h5/charge/stop`、`/h5/door/open` 等接口,但后端 `routes/mod.rs` 中未注册任何 `/h5/` 路由。
|
|||
|
|
- **风险**:H5 用户端完全无法使用,所有请求返回 404
|
|||
|
|
- **建议**:在后端实现 H5 路由模块,或明确标注为待开发并在前端添加提示
|
|||
|
|
|
|||
|
|
### 问题 4:文件下载端点未实现
|
|||
|
|
- **位置**:`software/server/src/routes/downloads.rs:162`
|
|||
|
|
- **描述**:`get_download` 函数返回 `download_url: "/api/downloads/{id}/file"`,但 `routes/mod.rs` 中没有注册该路由。用户点击下载后无法获取文件。
|
|||
|
|
- **风险**:导出功能不完整,用户无法下载已生成的 Excel 文件
|
|||
|
|
- **建议**:添加文件下载端点,使用 `axum::response::File` 或 `tower-http::ServeDir` 提供文件服务
|
|||
|
|
|
|||
|
|
### 问题 5:`authenticated` 标志未强制校验
|
|||
|
|
- **位置**:`software/server/src/tcp/handler.rs:146`
|
|||
|
|
- **描述**:`DeviceConnection` 有 `authenticated` 字段,`handle_login` 成功后调用 `pool.set_authenticated(dev_id)`。但 `handle_status_post` 中检查的是 `pool.is_online(dev_id)`,该方法仅检查 `authenticated` 标志。然而,在 `server.rs:98-104` 中,设备在发送 `auth_str` 或 `login` 时就被注册到连接池(`pool.register`),此时 `authenticated` 为 `false`。问题在于 `is_online` 返回 `authenticated` 为 `false` 时,`status_post` 确实会拒绝(返回"设备未登录"),但 `auth_str` 请求可以无限次调用而不限流,可能被用于资源耗尽攻击。
|
|||
|
|
- **风险**:安全码获取接口无频率限制,可能被恶意设备利用
|
|||
|
|
- **建议**:对 `auth_str` 请求添加频率限制或 IP 限制
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 警告(建议修复)
|
|||
|
|
|
|||
|
|
### 警告 1:`list_users` 函数过长
|
|||
|
|
- **位置**:`software/server/src/routes/users.rs:96-256`
|
|||
|
|
- **描述**:函数约 160 行,包含多种条件分支(有无关键字搜索 x 有无组织过滤),大量重复的 SQL 构建逻辑。
|
|||
|
|
- **建议**:拆分为 `build_user_count_query` 和 `build_user_list_query` 辅助函数,或使用动态 SQL 构建器
|
|||
|
|
|
|||
|
|
### 警告 2:能耗导出缺少组织数据隔离
|
|||
|
|
- **位置**:`software/server/src/routes/energy_stats.rs:460-553`
|
|||
|
|
- **描述**:`generate_energy_excel` 后台任务中使用 `build_export_clause` 构建查询条件,但未追加组织过滤条件。企业管理员可能导出全部组织的能耗数据。
|
|||
|
|
- **建议**:在导出查询中追加 `org_condition` 过滤
|
|||
|
|
|
|||
|
|
### 警告 3:前端多处 `as unknown as` / `as never` 类型断言
|
|||
|
|
- **位置**:`OrganizationTree.tsx:169`、`devices/index.tsx:81,103`、`AddCabinetModal.tsx:44`
|
|||
|
|
- **描述**:使用 `as never`、`as unknown as` 绕过 TypeScript 类型检查,掩盖了 API 响应类型与实际数据的不匹配。
|
|||
|
|
- **建议**:修正 API 泛型参数类型,使响应类型与 Arco Tree/Table 组件期望的类型一致
|
|||
|
|
|
|||
|
|
### 警告 4:数据库迁移缺少索引
|
|||
|
|
- **位置**:`software/server/src/db/migrate.rs`
|
|||
|
|
- **描述**:以下高频查询列缺少索引:
|
|||
|
|
- `charge_records.cabinet_id`、`charge_records.start_time`
|
|||
|
|
- `device_logs.cabinet_id`、`device_logs.created_at`
|
|||
|
|
- `operation_logs.user_id`、`operation_logs.created_at`
|
|||
|
|
- `users.organization_id`、`cabinets.project_id`
|
|||
|
|
- **建议**:在迁移脚本中为上述列添加 `INDEX`
|
|||
|
|
|
|||
|
|
### 警告 5:导出逻辑大量重复
|
|||
|
|
- **位置**:`charge_records.rs:307-395` 与 `energy_stats.rs:460-553`
|
|||
|
|
- **描述**:两个导出函数的结构几乎相同:更新状态 → 获取参数 → 查询数据 → 更新进度 → 生成 Excel → 保存文件 → 更新完成状态。
|
|||
|
|
- **建议**:抽取通用的 `run_export_task` 泛型函数,传入数据查询和 Excel 生成闭包
|
|||
|
|
|
|||
|
|
### 警告 6:H5 认证恢复无服务端校验
|
|||
|
|
- **位置**:`software/web/src/h5/auth.ts:48-55`
|
|||
|
|
- **描述**:`restore()` 从 localStorage 读取 token 后直接设置 `isAuthenticated: true`,不验证 token 有效性。与后台管理端的 `restore`(调用 `/auth/me` 验证)行为不一致。
|
|||
|
|
- **建议**:H5 端也添加服务端 token 校验(需先实现 H5 后端接口)
|
|||
|
|
|
|||
|
|
### 警告 7:`regenerate_auth` 返回明文安全码
|
|||
|
|
- **位置**:`software/server/src/routes/cabinets.rs:232-255`
|
|||
|
|
- **描述**:重新生成安全码后通过 API 响应返回明文 `auth_str`。虽然管理员需要知道新安全码以配置设备,但这增加了泄露风险。
|
|||
|
|
- **建议**:考虑仅显示一次或添加操作确认,并在操作日志中记录
|
|||
|
|
|
|||
|
|
### 警告 8:`roles.rs::list_roles` 存在 N+1 查询
|
|||
|
|
- **位置**:`software/server/src/routes/roles.rs:36-72`
|
|||
|
|
- **描述**:先查询所有角色,再为每个角色单独查询权限码。角色数量通常为个位数,当前可接受,但不够优雅。
|
|||
|
|
- **建议**:使用单条 SQL 通过 `GROUP_CONCAT` 或 `JOIN` 一次性获取角色及其权限码
|
|||
|
|
|
|||
|
|
### 警告 9:导出文件路径使用相对路径
|
|||
|
|
- **位置**:`charge_records.rs:378`、`energy_stats.rs:536`
|
|||
|
|
- **描述**:`let file_dir = "./exports"` 使用相对路径,依赖进程工作目录。
|
|||
|
|
- **建议**:使用配置项或绝对路径(如 `cfg.export_dir`)
|
|||
|
|
|
|||
|
|
### 警告 10:清理下载接口缺少用户权限校验
|
|||
|
|
- **位置**:`software/server/src/routes/downloads.rs:176-220`
|
|||
|
|
- **描述**:`cleanup_downloads` 清理所有过期任务的文件,不区分用户。虽然有 `charge_record:export` 权限检查,但普通用户不应能清理其他用户的文件。
|
|||
|
|
- **建议**:添加 `WHERE user_id = ?` 过滤,或限制为总管理员操作
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 建议(可优化)
|
|||
|
|
|
|||
|
|
### 建议 1:Dashboard 页面为空壳
|
|||
|
|
- **位置**:`software/web/src/pages/Dashboard.tsx`
|
|||
|
|
- **描述**:仅显示欢迎文字,无实际数据展示。
|
|||
|
|
- **建议**:可复用能耗总览数据或设备在线统计,提供有价值的首页视图
|
|||
|
|
|
|||
|
|
### 建议 2:状态映射常量重复定义
|
|||
|
|
- **位置**:`cabinet.tsx:24-37`、`CabinetGrid.tsx:22-27`、`charge-records/index.tsx:52-56`
|
|||
|
|
- **描述**:柜子状态和仓体状态的映射在多个文件中重复定义。
|
|||
|
|
- **建议**:抽取到 `constants/status.ts` 统一管理
|
|||
|
|
|
|||
|
|
### 建议 3:`devices/index.tsx` 状态变量过多
|
|||
|
|
- **位置**:`software/web/src/pages/devices/index.tsx`
|
|||
|
|
- **描述**:组件内有 15+ 个 `useState`,管理弹窗、编辑、右键菜单等多种状态。
|
|||
|
|
- **建议**:使用 `useReducer` 或拆分为子组件(如 `useOrgModal`、`useContextMenu`)
|
|||
|
|
|
|||
|
|
### 建议 4:`seed_permissions` 可批量插入
|
|||
|
|
- **位置**:`software/server/src/db/migrate.rs:186-223`
|
|||
|
|
- **描述**:逐条 `INSERT IGNORE` 21 个权限码,可优化为单条批量 INSERT。
|
|||
|
|
- **建议**:使用 `INSERT IGNORE INTO permissions (code, name) VALUES (?, ?), (?, ?), ...` 批量插入
|
|||
|
|
|
|||
|
|
### 建议 5:电费单价应可配置
|
|||
|
|
- **位置**:`software/server/src/routes/energy_stats.rs:15`
|
|||
|
|
- **描述**:`COST_PER_KWH` 硬编码为 0.80,不同地区/时段电价不同。
|
|||
|
|
- **建议**:移至配置文件或数据库配置表
|
|||
|
|
|
|||
|
|
### 建议 6:`energy/index.tsx` 文件过长
|
|||
|
|
- **位置**:`software/web/src/pages/energy/index.tsx`(468 行)
|
|||
|
|
- **描述**:包含总览卡片、排行表格、项目统计、趋势图表等全部逻辑。
|
|||
|
|
- **建议**:拆分为 `SummaryCards`、`CabinetRanking`、`ProjectStats`、`TrendChart` 子组件
|
|||
|
|
|
|||
|
|
### 建议 7:H5 `routes.tsx` 导入顺序不规范
|
|||
|
|
- **位置**:`software/web/src/h5/routes.tsx:47-50`
|
|||
|
|
- **描述**:`ScanRedirect` 组件的 `import` 语句出现在文件中间(第 47 行),违反 ES Module 导入应在文件顶部的约定。
|
|||
|
|
- **建议**:将所有 import 移到文件顶部
|
|||
|
|
|
|||
|
|
### 建议 8:充电曲线使用硬编码模拟数据
|
|||
|
|
- **位置**:`software/web/src/h5/pages/compartment-detail.tsx:173-175`
|
|||
|
|
- **描述**:充电曲线图表使用固定数组 `[30, 45, 55, ...]` 模拟,不是真实数据。
|
|||
|
|
- **建议**:标注为 TODO 或待后端提供充电曲线 API 后替换
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 优点
|
|||
|
|
|
|||
|
|
1. **架构设计清晰**:后端按功能域拆分模块(organizations/projects/cabinets/users/roles 等),路由注册集中管理,模块职责单一
|
|||
|
|
2. **安全性基础扎实**:argon2 密码哈希、JWT 认证、RBAC 权限模型、参数化 SQL 查询、组织级数据隔离,安全层面考虑全面
|
|||
|
|
3. **TCP 通讯实现完整**:LF 分隔粘包处理、签名验证(SHA256 + 时间窗口)、心跳超时清理、连接池管理,协议层实现可靠
|
|||
|
|
4. **异步导出设计合理**:download_tasks 表跟踪任务状态、tokio 后台生成 Excel、前端轮询刷新,完整的异步导出流程
|
|||
|
|
5. **错误处理规范**:统一 `AppError` 错误类型,所有 IO 操作有异常捕获,无裸 panic,错误信息有意义
|
|||
|
|
6. **前端组件化好**:Permission 权限组件支持 hidden/disabled 模式,OrganizationTree 递归树组件,DownloadCenter 可复用弹窗
|
|||
|
|
7. **操作日志完善**:关键操作(创建/编辑/删除用户等)均记录操作日志,便于审计
|
|||
|
|
8. **H5 移动端体验好**:Tailwind CSS 样式统一,底部导航、确认弹窗、加载/错误状态组件完善
|
|||
|
|
9. **数据库设计合理**:外键约束、级联删除、UNIQUE 约束、COMMENT 注释,表结构规范
|
|||
|
|
10. **配置管理灵活**:环境变量 + `.env` 文件,敏感配置有默认值告警
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
### 优先修复建议(按紧急程度排序)
|
|||
|
|
|
|||
|
|
1. **P0 — 立即修复**:
|
|||
|
|
- 修复 `list_users` 的 7 元素元组编译问题(严重 #1)
|
|||
|
|
- 修复充电记录 COUNT 查询 JOIN 缺失(严重 #2)
|
|||
|
|
- 实现文件下载端点 `/api/downloads/:id/file`(严重 #4)
|
|||
|
|
|
|||
|
|
2. **P1 — 近期完成**:
|
|||
|
|
- 实现 H5 后端接口模块(严重 #3),或在前端标注为"待开发"
|
|||
|
|
- 添加数据库关键索引(警告 #4)
|
|||
|
|
- 修复能耗导出缺少组织过滤(警告 #2)
|
|||
|
|
|
|||
|
|
3. **P2 — 迭代优化**:
|
|||
|
|
- 拆分 `list_users` 函数(警告 #1)
|
|||
|
|
- 消除前端类型断言(警告 #3)
|
|||
|
|
- 统一导出逻辑减少重复(警告 #5)
|
|||
|
|
- 导出文件路径配置化(警告 #9)
|
|||
|
|
|
|||
|
|
4. **P3 — 持续改进**:
|
|||
|
|
- Dashboard 首页数据展示(建议 #1)
|
|||
|
|
- 状态映射常量统一(建议 #2)
|
|||
|
|
- 电费单价可配置化(建议 #5)
|
|||
|
|
|
|||
|
|
### 结论
|
|||
|
|
|
|||
|
|
项目代码整体质量 **良好**,架构设计规范,安全和错误处理基础扎实。主要风险点在于:部分代码可能存在编译问题需要验证、H5 后端接口完全缺失、文件下载端点未实现。建议优先修复 P0 级问题后进行 `cargo check` 和 `tsc --noEmit` 验证,确保代码可正常编译。
|