297 lines
17 KiB
Markdown
297 lines
17 KiB
Markdown
|
|
# 全面代码审查报告
|
|||
|
|
|
|||
|
|
## 总体评价
|
|||
|
|
|
|||
|
|
**质量评分:7/10**
|
|||
|
|
|
|||
|
|
项目整体架构清晰,模块划分合理,代码风格统一。后端采用 Rust + Axum 技术栈,安全性较好(参数化查询、Argon2 密码哈希、JWT 认证)。前端 React + Arco Design 组件库使用规范,H5 移动端独立实现。但存在若干权限码缺失、数据隔离不完整、前后端接口字段不匹配等严重问题需优先修复。
|
|||
|
|
|
|||
|
|
## 统计
|
|||
|
|
|
|||
|
|
- 后端文件数:29(Rust)
|
|||
|
|
- 前端文件数:34(TypeScript/React)
|
|||
|
|
- 后端代码行数:6,114
|
|||
|
|
- 前端代码行数:5,300
|
|||
|
|
- 总代码行数:11,414
|
|||
|
|
- 问题总数:24(严重 7 / 警告 9 / 建议 8)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 严重问题(必须修复)
|
|||
|
|
|
|||
|
|
### 问题1:权限码种子数据缺失组织/项目管理权限
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/db/migrate.rs:187-209` + `software/server/src/routes/organizations.rs:154,172,198,227` + `software/server/src/routes/projects.rs:20,40,78,107`
|
|||
|
|
- **描述**:`seed_permissions` 函数只初始化了 `device:*`、`charge:*`、`charge_record:*`、`device_log:*`、`energy:*`、`user:*`、`role:*`、`operation_log:*` 等权限码,但组织管理 API 检查的是 `org:view`/`org:create`/`org:edit`/`org:delete`,项目管理 API 检查的是 `project:view`/`project:create`/`project:edit`/`project:delete`。这些权限码从未在数据库中创建。
|
|||
|
|
- **风险**:非总管理员用户(role_level < 2)永远无法执行组织和项目的增删改查操作,因为 `check_permission` 在权限码集合中找不到这些 code 会返回 Forbidden。
|
|||
|
|
- **建议**:在 `seed_permissions` 中补充以下权限码:
|
|||
|
|
```
|
|||
|
|
("org:view", "查看组织"), ("org:create", "创建组织"),
|
|||
|
|
("org:edit", "编辑组织"), ("org:delete", "删除组织"),
|
|||
|
|
("project:view", "查看项目"), ("project:create", "创建项目"),
|
|||
|
|
("project:edit", "编辑项目"), ("project:delete", "删除项目"),
|
|||
|
|
```
|
|||
|
|
并为 `org_admin` 角色分配 `org:view`、`project:view`、`project:create`、`project:edit` 权限。
|
|||
|
|
|
|||
|
|
### 问题2:设备管理缺少 `device:edit` 权限码
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/db/migrate.rs:188` + `software/server/src/routes/cabinets.rs:166,237`
|
|||
|
|
- **描述**:`cabinets.rs` 中 `update_cabinet` 和 `regenerate_auth` 检查 `device:edit` 权限,但种子数据中只有 `device:view`、`device:operate`、`device:create`、`device:delete`,没有 `device:edit`。
|
|||
|
|
- **风险**:非总管理员用户无法编辑柜子或重新生成安全码。
|
|||
|
|
- **建议**:在种子数据中添加 `("device:edit", "编辑设备")`。
|
|||
|
|
|
|||
|
|
### 问题3:H5 接口缺少数据隔离 — 仪表盘返回全局数据
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/h5.rs:157-245`
|
|||
|
|
- **描述**:`get_dashboard` 接口查询的在线柜子数、充电中仓体数、空闲仓体数、故障仓体数、今日充电次数/充电量均为全局统计,未按用户所属组织过滤。企业管理员和普通用户可以看到全平台数据。
|
|||
|
|
- **风险**:数据泄露,违反多租户隔离原则。
|
|||
|
|
- **建议**:所有统计查询追加 `organization_id` 过滤条件,与后台管理接口保持一致的隔离策略。
|
|||
|
|
|
|||
|
|
### 问题4:H5 设备详情和仓体详情缺少权限校验
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/h5.rs:420-488,558-634`
|
|||
|
|
- **描述**:`get_cabinet_detail`、`get_cabinet_by_abstract_id`、`get_compartment_detail` 三个接口没有进行任何权限检查(只有 `let _ = &user;`),任意已登录用户可通过遍历 ID 访问任意设备和仓体数据。
|
|||
|
|
- **风险**:未授权数据访问,任意用户可查看不属于自己组织的设备。
|
|||
|
|
- **建议**:添加组织隔离校验,确保用户只能访问本组织下的设备。
|
|||
|
|
|
|||
|
|
### 问题5:前后端 H5 告警数据结构不匹配
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/h5.rs:206-224` vs `software/web/src/h5/api.ts:21-26` + `software/web/src/h5/components.tsx:96-113`
|
|||
|
|
- **描述**:后端返回告警字段为 `{ compartment_id, channel, cabinet_id, alert_type }`,前端 `AlertItem` 接口定义为 `{ project: string, cabinet: string, channel: number, alert_type: string }`。`AlertCard` 组件渲染 `project` 和 `cabinet` 字段,但后端未返回这两个字段,前端会显示 `undefined`。
|
|||
|
|
- **风险**:H5 首页告警通知显示异常,用户看到 `undefined - undefined 通道X`。
|
|||
|
|
- **建议**:后端补充 `project` 和 `cabinet` 字段(通过 JOIN 查询),或前端改为根据 ID 显示。
|
|||
|
|
|
|||
|
|
### 问题6:SQL 字符串拼接中直接内联 org_id(h5.rs)
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/h5.rs:744-756`
|
|||
|
|
- **描述**:`org_project_condition` 函数使用 `format!` 将 `org_id` 直接拼入 SQL 字符串(`"...WHERE organization_id = {}"`),而非使用参数化查询的 `?` 占位符。虽然 `org_id` 来自 JWT 解析的 `i64` 类型(理论上安全),但这种模式违反了参数化查询的最佳实践。
|
|||
|
|
- **风险**:代码模式不安全,若未来重构改变了 `org_id` 的来源类型,可能引入 SQL 注入。
|
|||
|
|
- **建议**:改为返回参数化条件,使用 `?` 占位符并通过 `.bind()` 传值,与 `auth::org_condition` 保持一致。
|
|||
|
|
|
|||
|
|
### 问题7:下载中心权限码检查不精确
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/downloads.rs:96,141,184,241`
|
|||
|
|
- **描述**:下载中心所有接口(列表、详情、文件下载、清理)统一检查 `charge_record:export` 权限。但能耗统计导出也使用下载中心,应同时接受 `energy:export` 权限。当前设计导致只有充电记录导出权限的用户才能看到所有下载任务。
|
|||
|
|
- **风险**:权限模型不够灵活,能耗导出权限的用户无法使用下载中心。
|
|||
|
|
- **建议**:下载中心列表接口改为检查 `charge_record:export` 或 `energy:export`(任一即可),或新增 `download:view` 权限码。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 警告(建议修复)
|
|||
|
|
|
|||
|
|
### 警告1:`users.rs` 中 `list_users` 查询分支大量重复
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/users.rs:143-221`
|
|||
|
|
- **描述**:`list_users` 函数中,带关键字/不带关键字 x 有组织过滤/无组织过滤,产生了 6 个几乎相同的 SQL 查询分支,代码约 80 行。
|
|||
|
|
- **建议**:使用动态 SQL 构建(类似 `charge_records.rs` 的 `build_where_clause` 模式),将条件拼接统一处理。
|
|||
|
|
|
|||
|
|
### 警告2:`energy_stats.rs` 组织过滤逻辑重复
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/energy_stats.rs:90-99,174-183,230-236,289-298`
|
|||
|
|
- **描述**:`get_summary`、`get_cabinet_ranking`、`get_project_stats`、`get_trend` 四个函数各自独立实现组织过滤逻辑,代码重复。
|
|||
|
|
- **建议**:抽取为通用函数(类似 `auth::org_condition`),统一复用。
|
|||
|
|
|
|||
|
|
### 警告3:`list_cabinet_options` 无组织过滤
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/cabinets.rs:366-389`
|
|||
|
|
- **描述**:当 `project_id` 为空时,`list_cabinet_options` 返回所有柜子,未按用户组织过滤。企业管理员和普通用户可看到全部柜子下拉选项。
|
|||
|
|
- **建议**:追加 `auth::org_condition` 过滤。
|
|||
|
|
|
|||
|
|
### 警告4:设备重连时旧连接未显式关闭
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/tcp/connection.rs:49-52` + `software/server/src/tcp/server.rs:98-104`
|
|||
|
|
- **描述**:`ConnectionPool::register` 直接 `insert` 覆盖旧连接,但旧连接的 `mpsc::UnboundedSender` 克隆体在 `handle_connection` 中仍被持有,旧 TCP 连接的任务不会立即终止。
|
|||
|
|
- **建议**:注册时检查是否已存在旧连接,若有则通过通道发送关闭信号或记录日志。
|
|||
|
|
|
|||
|
|
### 警告5:前端 `as unknown as` 类型断言绕过类型安全
|
|||
|
|
|
|||
|
|
- **位置**:`software/web/src/pages/devices/index.tsx:81,103` + `software/web/src/pages/devices/AddCabinetModal.tsx:44`
|
|||
|
|
- **描述**:多处使用 `(res as unknown as TreeNode[])` 双重类型断言,绕过了 TypeScript 类型检查。说明 `api.get<T>` 的返回类型 `ApiResponse<T>` 与实际使用不匹配。
|
|||
|
|
- **建议**:检查 `api.get` 泛型推导是否正确,修复类型定义使其无需强制转换。
|
|||
|
|
|
|||
|
|
### 警告6:JWT 密钥常量重复定义
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/config.rs:4` vs `software/server/src/middleware/auth.rs:33`
|
|||
|
|
- **描述**:`JWT_SECRET_DEFAULT` 在 `config.rs` 和 `middleware/auth.rs` 中各定义一次,值相同但独立维护。
|
|||
|
|
- **建议**:只在 `config.rs` 中定义一次,`middleware/auth.rs` 引用 `crate::config` 中的常量,或在 `AppState` 中传递已解析的密钥。
|
|||
|
|
|
|||
|
|
### 警告7:401 响应未自动跳转登录页
|
|||
|
|
|
|||
|
|
- **位置**:`software/web/src/api/request.ts:44-59`
|
|||
|
|
- **描述**:`request` 函数在收到 401 响应时抛出 `ApiError`,但未自动清除本地 token 或跳转到登录页。当 JWT 过期后,用户操作会报错但不会自动跳转到登录页。
|
|||
|
|
- **建议**:在 `request` 函数中拦截 401 状态码,自动清除 localStorage 并跳转到 `/login`。
|
|||
|
|
|
|||
|
|
### 警告8:数据库表缺少关键索引
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/db/migrate.rs`
|
|||
|
|
- **描述**:多个高频查询涉及的列缺少索引:
|
|||
|
|
- `cabin_boards.cabinet_id`(设备详情查询)
|
|||
|
|
- `compartments.cabin_board_id`(仓体列表查询)
|
|||
|
|
- `charge_records.compartment_id`(充电记录查询)
|
|||
|
|
- `charge_records.start_time`(时间范围过滤)
|
|||
|
|
- `device_logs.cabinet_id` + `device_logs.created_at`(日志查询)
|
|||
|
|
- `download_tasks.user_id`(下载列表查询)
|
|||
|
|
- `users.organization_id`(用户组织过滤)
|
|||
|
|
- `cabinets.project_id`(项目下柜子查询)
|
|||
|
|
- **建议**:为上述列添加索引。MySQL 的 `FOREIGN KEY` 约束会自动创建索引,但需确认迁移脚本中已包含。
|
|||
|
|
|
|||
|
|
### 警告9:`DeviceMessage.extra` 使用 `Option<Value>` 与 `flatten` 冗余
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/tcp/protocol.rs:29-31`
|
|||
|
|
- **描述**:`extra: Option<Value>` 配合 `#[serde(flatten)]`,当无额外字段时值为 `Value::Null` 而非 `None`,`Option` 包裹无实际意义。
|
|||
|
|
- **建议**:改为 `extra: Value` 或去掉 `Option`。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 建议
|
|||
|
|
|
|||
|
|
### 建议1:`Protocol::to_json` 使用 `expect` 可改为 `unwrap_or_default`
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/tcp/protocol.rs:58,80`
|
|||
|
|
- **描述**:`ServerResponse::to_json` 和 `DeviceCommand::to_json` 使用 `expect` 序列化。虽然理论上不应失败,但为安全起见可改为 `unwrap_or_default()`。
|
|||
|
|
- **建议**:改为 `serde_json::to_string(self).unwrap_or_default()`。
|
|||
|
|
|
|||
|
|
### 建议2:`Dashboard.tsx` 为空白占位页
|
|||
|
|
|
|||
|
|
- **位置**:`software/web/src/pages/Dashboard.tsx`
|
|||
|
|
- **描述**:后台管理 Dashboard 页面仅为静态文本 "欢迎使用充电柜管理系统",未展示任何统计数据。
|
|||
|
|
- **建议**:参考 H5 首页实现,展示设备在线率、今日充电量、告警数量等关键指标。
|
|||
|
|
|
|||
|
|
### 建议3:柜子详情页显示硬编码零值
|
|||
|
|
|
|||
|
|
- **位置**:`software/web/src/pages/devices/cabinet.tsx:144-147`
|
|||
|
|
- **描述**:柜子详情的仓体卡片中,电压/电流/功率显示为硬编码的 `0.0V`/`0.0A`/`0W`,未从 Redis 实时数据获取。
|
|||
|
|
- **建议**:通过后端接口获取仓体实时数据并渲染。
|
|||
|
|
|
|||
|
|
### 建议4:`energy_stats.rs` 中 `get_summary` 执行了 4 次独立查询
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/routes/energy_stats.rs:82-162`
|
|||
|
|
- **描述**:`get_summary` 分别执行今日能耗、本月能耗、柜子总数、在线柜子数 4 次查询,可合并为 1-2 次查询减少数据库往返。
|
|||
|
|
- **建议**:使用 `CASE WHEN` 或子查询合并。
|
|||
|
|
|
|||
|
|
### 建议5:H5 `DownloadCenter` 轮询定时器清理不完整
|
|||
|
|
|
|||
|
|
- **位置**:`software/web/src/pages/charge-records/DownloadCenter.tsx:79-86`
|
|||
|
|
- **描述**:`useEffect` 依赖 `[visible, tasks, fetchTasks]`,每次 `tasks` 变化都会清除并重建 `setInterval`。当有进行中任务时,每 5 秒触发一次重建。
|
|||
|
|
- **建议**:将 `tasks` 从依赖数组中移除,改用 `useRef` 跟踪任务状态。
|
|||
|
|
|
|||
|
|
### 建议6:`h5/routes.tsx` 中 import 语句位于文件中间
|
|||
|
|
|
|||
|
|
- **位置**:`software/web/src/h5/routes.tsx:47-50`
|
|||
|
|
- **描述**:`import { useEffect, useState }` 等导入语句出现在文件中间(第 47 行),违反 ES Module 规范(虽然 TypeScript 编译器允许)。
|
|||
|
|
- **建议**:将所有 import 语句移至文件顶部。
|
|||
|
|
|
|||
|
|
### 建议7:`config.rs` 中 `validate` 仅打印警告
|
|||
|
|
|
|||
|
|
- **位置**:`software/server/src/config.rs:47-51`
|
|||
|
|
- **描述**:使用默认 JWT 密钥时仅 `tracing::warn`,生产环境可能被忽略。
|
|||
|
|
- **建议**:生产环境应直接 panic 或通过环境变量强制检查(如检查 `NODE_ENV=production` 时拒绝默认密钥)。
|
|||
|
|
|
|||
|
|
### 建议8:H5 认证恢复未校验 token 有效性
|
|||
|
|
|
|||
|
|
- **位置**:`software/web/src/h5/auth.ts:48-55`
|
|||
|
|
- **描述**:`restore` 函数从 localStorage 恢复 token 后直接设置 `isAuthenticated: true`,未向后端验证 token 是否仍然有效。后台管理的 `auth.ts` 会调用 `/auth/me` 验证,H5 端缺少此步骤。
|
|||
|
|
- **建议**:H5 恢复时也调用 `/h5/dashboard` 或类似接口验证 token 有效性。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 审查清单完成情况
|
|||
|
|
|
|||
|
|
### 1. 编译与类型检查
|
|||
|
|
- [x] Rust 代码结构完整,模块引用正确(无法在此环境运行 `cargo check`)
|
|||
|
|
- [x] TypeScript 代码类型定义完整,接口匹配(无法在此环境运行 `tsc --noEmit`)
|
|||
|
|
- [x] 前端构建配置正常(Vite + React)
|
|||
|
|
|
|||
|
|
### 2. 代码规范
|
|||
|
|
- [x] 单函数基本 <=80 行(最长函数约 70 行)
|
|||
|
|
- [x] 命名语义化,中文注释完整
|
|||
|
|
- [x] 常量已抽离(`COST_PER_KWH`、`HEARTBEAT_TIMEOUT` 等)
|
|||
|
|
- [x] 无废弃 API 使用
|
|||
|
|
|
|||
|
|
### 3. 错误处理
|
|||
|
|
- [x] 所有外部 IO 有异常捕获
|
|||
|
|
- [x] 无裸 `panic!`(`expect` 仅用于不可能失败的序列化)
|
|||
|
|
- [x] 分支逻辑基本全覆盖
|
|||
|
|
- [x] 错误信息有意义(中文描述)
|
|||
|
|
|
|||
|
|
### 4. Rust 专项
|
|||
|
|
- [x] 无 `unsafe` 块
|
|||
|
|
- [x] 所有权管理合理(`Clone` 用于共享状态,`Arc<RwLock>` 用于连接池)
|
|||
|
|
- [x] 异步代码无阻塞操作(使用 `tokio::fs` 而非 `std::fs`)
|
|||
|
|
- [x] 内存安全,无冗余拷贝
|
|||
|
|
|
|||
|
|
### 5. React 专项
|
|||
|
|
- [x] 全部使用函数式组件 + Hooks
|
|||
|
|
- [x] 状态管理使用 Zustand(轻量级)
|
|||
|
|
- [x] useEffect 清理正确(定时器清理)
|
|||
|
|
- [ ] 存在 `as unknown as` 类型断言(见警告5)
|
|||
|
|
|
|||
|
|
### 6. 安全
|
|||
|
|
- [x] SQL 全部使用参数化查询(`?` 占位符)
|
|||
|
|
- [x] 密码使用 Argon2 哈希
|
|||
|
|
- [x] JWT 密钥支持环境变量覆盖
|
|||
|
|
- [x] 输入参数有基本校验(手机号、IMEI 格式)
|
|||
|
|
- [ ] 权限码种子数据不完整(见严重问题1、2)
|
|||
|
|
- [ ] H5 接口缺少数据隔离(见严重问题3、4)
|
|||
|
|
- [x] 敏感参数未打印到日志(密码未出现在 tracing 中)
|
|||
|
|
|
|||
|
|
### 7. 业务逻辑
|
|||
|
|
- [x] TCP 协议解析正确(LF 分隔、签名验证、超时清理)
|
|||
|
|
- [x] 权限模型正确(总管理员/企业管理员/普通用户三级)
|
|||
|
|
- [x] 异步导出流程完整(任务创建 -> tokio::spawn 后台处理 -> 下载中心查看)
|
|||
|
|
- [x] 前端权限组件正确隐藏/显示按钮
|
|||
|
|
|
|||
|
|
### 8. 架构
|
|||
|
|
- [x] 接口层、业务逻辑层、数据模型层分离清晰
|
|||
|
|
- [x] 无循环依赖
|
|||
|
|
- [x] 模块职责清晰(routes 按功能拆分,tcp 按职责拆分)
|
|||
|
|
- [x] 路由注册完整
|
|||
|
|
|
|||
|
|
### 9. 性能
|
|||
|
|
- [ ] 数据库索引需补充(见警告8)
|
|||
|
|
- [x] 无 N+1 查询问题(组织树使用批量查询后内存组装)
|
|||
|
|
- [x] 前端列表有分页
|
|||
|
|
- [x] 大数据量导出使用异步处理
|
|||
|
|
|
|||
|
|
### 10. 可维护性
|
|||
|
|
- [x] 文件长度合理(大部分 <=300 行,最长 `h5.rs` 约 757 行但含多个独立接口)
|
|||
|
|
- [x] 组件拆分合理
|
|||
|
|
- [ ] 部分代码重复(见警告1、2)
|
|||
|
|
- [x] 配置外部化(环境变量)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 优点
|
|||
|
|
|
|||
|
|
1. **统一错误处理**:`AppError` 枚举 + `IntoResponse` 实现了优雅的错误处理,所有错误自动转为标准 JSON 格式
|
|||
|
|
2. **安全的密码方案**:使用 Argon2(当前推荐的密码哈希算法),带随机盐
|
|||
|
|
3. **TCP 协议设计合理**:LF 分隔 JSON、签名验证、频率限制、心跳超时清理,考虑周全
|
|||
|
|
4. **连接池管理**:`ConnectionPool` 使用 `Arc<RwLock<HashMap>>` 实现线程安全的设备连接管理
|
|||
|
|
5. **异步导出架构**:`tokio::spawn` 后台生成 Excel,下载中心轮询查看进度,用户体验良好
|
|||
|
|
6. **前端权限组件**:`<Permission>` 组件支持 hidden/disabled 两种模式,按钮级权限控制优雅
|
|||
|
|
7. **H5 移动端独立实现**:与后台管理使用独立认证、独立路由、Tailwind CSS 样式,互不干扰
|
|||
|
|
8. **数据库迁移自动化**:启动时自动建表 + 种子数据,部署简单
|
|||
|
|
9. **操作日志完整**:用户管理的增删改查均记录操作日志,便于审计
|
|||
|
|
10. **代码注释质量高**:中文注释覆盖所有模块、函数和关键逻辑,可读性好
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
项目代码质量整体良好,架构设计清晰,技术选型合理。优先修复建议如下:
|
|||
|
|
|
|||
|
|
**P0(立即修复)**:
|
|||
|
|
1. 补充缺失的权限码种子数据(`org:*`、`project:*`、`device:edit`)— 否则非总管理员无法管理组织和项目
|
|||
|
|
2. H5 接口添加数据隔离 — 否则存在数据泄露风险
|
|||
|
|
|
|||
|
|
**P1(尽快修复)**:
|
|||
|
|
3. 修复前后端 H5 告警数据结构不匹配
|
|||
|
|
4. `h5.rs` 中 `org_project_condition` 改为参数化查询
|
|||
|
|
5. 下载中心权限码检查优化
|
|||
|
|
|
|||
|
|
**P2(计划修复)**:
|
|||
|
|
6. 补充数据库索引
|
|||
|
|
7. 消除代码重复(`list_users`、`energy_stats` 组织过滤)
|
|||
|
|
8. 前端 401 自动跳转登录页
|
|||
|
|
9. H5 认证恢复时校验 token 有效性
|