charging-cabinet/docs/审查报告-数据单位一致性-2026-07-06.md
12451 999885c38f feat(protocol): TCP v1.1 协议对齐 + 平台侧兜底 + 生产部署修复
协议(唯一权威文档:docs/柜控板固件对接文档.md)
- 应答必带 act(与请求一致)、msg_id 与请求一致;判"应答 vs 指令"只看有没有 suc
- dev_id 只在 auth_str/login 携带;其余报文用内部字段 _dev_id 路由(下发前由 device-server 摘除)
- id 只表示动作目标(柜级省略 / 一段=单板 / 两段=仓体级),不再含 IMEI
- msg_id 每连接从 1 起自增;60 秒交互超时;status_post 每通道 18 字符 hex 全量上报
- 未登录连接 30 秒内未 login 即断开;未登录只放行 auth_str/login

平台侧实现
- 上行解析按新结构重写(cabinet/boards/battery),删除废弃的 status/parsed_status
- 订单:唯一入口 = on;bat_in 只登记电池台账;4 条结束路径统一走 finalize_order(含充电记录落库)
- 新增设备日志保留策略(90 天,分批删除);未登记 IMEI 静默丢弃;停电标记按 status1 bit0 恢复清除
- 待确认第 13 项兜底:平台侧按 电压 × 电流 × 时间 积分计算订单电量(workers/energy.rs)
- 待确认第 18 项兜底:status_post bit7 连续 2 次无电池 → 判定电池被拔走并提前结单

修复(均为部署后才暴露的问题)
- 队列消费禁用 BRPOP:无限阻塞会因代理掐连接永久卡死(零错误日志);有限超时也只是
  "超时才醒",下行指令延迟 15~45 秒 → 改为 RPOP 非阻塞轮询,延迟降到 0.1~0.2 秒
- sync_iccid 把可空列 iccid 按 Option<String> 解码 → 整条 login 失败(ICCID 未上报的新柜子永远登录不上)
- 迁移读 information_schema.COLUMN_COMMENT 需 CAST(... AS CHAR)(MySQL 8 驱动报 VARBINARY,否则迁移 panic)
- 部署注意事项:.env 不支持行内注释;pscp 上传需 chmod +x;运行中二进制需 .new + mv 原子替换

部署
- 生产迁到 API 9000 / TCP 9001(systemd 单元补 EnvironmentFile、nginx 同步指向)
- 新增 deploy/deploy-prod.ps1(本地一键:构建→校验→备份→原子替换→重启→验收→无副作用探测)
- 废弃 deploy-prod.sh / deploy-test.sh(原脚本会在 target/release 找产物、装重复 systemd 单元、
  并用本地 .env 覆盖线上 .env)

质量
- 单测:api-server 39 个 / device-server 13 个(含协议示例逐值断言、跨模块 Redis key 契约)
- 两服务 cargo check 零错误零告警;web tsc --noEmit 通过
- test/tcp_e2e_smoke.py 端到端联调 13/13 通过
2026-09-19 23:41:09 +08:00

167 lines
7.3 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.

# 数据单位审查报告 — 充电柜项目
**日期:** 2026-07-06
**审查范围:** 硬件固件(协议层)、后端 API(api-server)、前端展示(web + h5-uniapp)、API 文档(docs/)
**协议基准:** [docs/tcp-protocol.md](./tcp-protocol.md) 全局单位规范(第23-35行)
---
## 1. 协议基准(tcp-protocol.md)
| 字段 | 单位 | 精度 | 示例 |
|------|------|------|------|
| `voltage` | mV | 1 | 52000 = 52.000V |
| `current` | mA | 1 | 10000 = 10.000A |
| `power` | mW | 1 | 500000 = 500.000W |
| `temp` | K×100 | 0.01K | 29865 = 298.65K = 25.5°C |
| `soc` | **‰** | 0.1% | 4500 = 45.00% |
| `soh` | **‰** | 0.1% | 9500 = 95.00% |
| `energy` | Wh | 1 | 累计充电量 |
---
## 2. 审查结果总览
### ✅ 正确的转换
| 字段 | 协议单位 | 前端转换 | 文件位置 | 状态 |
|------|---------|---------|---------|------|
| voltage | mV | `/1000 → V` | h5 `compartment-detail/index.vue:144` | ✅ |
| current | mA | `/1000 → A` | h5 `compartment-detail/index.vue:145` | ✅ |
| power | mW | `/1000 → W` | h5 `compartment-detail/index.vue:146` | ✅ |
| temp | K×100 | `/100 - 273.15 → °C` | h5 `device-detail/index.vue:85`、web `cabinet.tsx` | ✅ |
| soc | ‰ | `/10 → %` | h5 `compartment-detail/index.vue:142`、`device-detail/index.vue:86` | ✅ |
| energy | Wh | `/1000 → kWh` | h5 `compartment-detail/index.vue:147` `fmtEnergyKwh` | ✅ |
| energy(累计) | Wh | `/1000 → kWh` | h5 `compartment-detail/index.vue:143` `fmtEnergy` | ✅ |
| cost计算 | Wh × 0.0008 → 元 | — | api-server `energy_stats.rs:15` COST_PER_WH | ✅ |
### 🔴 不一致:SOC/SOH 单位标注错误
**问题:** `docs/API.md` 第206-207行标注错误。
```diff
- soc — %(0-100)
- soh — %(0-100)
+ soc — ‰(0-10000,前端已自动转为 %)
+ soh — ‰(0-10000,前端已自动转为 %)
```
**现状:** 协议层(tcp-protocol.md)明确定义 soc/soh 为 ‰(0-10000),但 API.md 标注为 %(0-100)。前端代码实际上做了正确的 ‰→% 转换(`/10`)。
**风险等级:** 🔴 高风险 — API 消费者(第三方、新的前端开发者)依据 API.md 会误认为值是 0-100,导致显示/逻辑错误。
**建议:** 修正 API.md 第206-207行,标注为 `‰(0-10000)`,并注明前端已做转换。
### 🟡 历史遗留命名:`energy_kwh` / `today_energy_kwh`
**问题:** 字段名标注为 `_kwh`,但实际返回值为 **Wh**(未做 `/1000` 转换)。
**涉及位置:**
| 位置 | 字段 | 实际值 | 问题 |
|------|------|-------|------|
| `api-server/src/routes/h5.rs:309` | `today_energy_kwh` | SUM(energy) → Wh | 注释写"kWh"但返回 Wh |
| `api-server/src/routes/h5.rs:776` | `energy_kwh` | `r.energy` → Wh | 字段名标 kWh,实际 Wh |
| `docs/API.md:137` | `today_energy_kwh` | 0 | 文档无明确单位说明 |
| `docs/API.md:421` | 多个能量字段 | — | **已有注释**:`¹ 注意:`energy_kwh` / `today_energy_kwh` 为历史遗留命名,实际值仍为 **Wh**,前端仍需 `/ 1000`。` |
**现状:** API.md 第421行已有明确注释说明这是历史遗留命名。前端 H5 通过 `fmtEnergy` 和 `fmtEnergyKwh` 正确执行 `/1000` 转换。
**风险等级:** 🟡 中风险 — API.md 已有注释说明,但字段名具有误导性。新接手开发者容易按字段名直接使用而不做 `/1000`。
**建议:** 保留现状(已有注释),但可在 API 文档对应字段示例中加 `// 实际单位: Wh, 需 /1000 转 kWh`。
### 🟡 `fmtEnergy` 缺少单位标签
**位置:** `h5-uniapp/src/pages/compartment-detail/index.vue:143`
```typescript
const fmtEnergy = (v: number | null | undefined) => v != null ? (v / 1000).toFixed(2) : '-'
```
**问题:** 转换后未标注单位(kWh),与 `fmtEnergyKwh`(L147,有 `kWh` 标签)不一致。
**风险等级:** 🟡 低风险 — 数值本身正确,只是缺少显示标签。
**建议:** 加 `' kWh'` 后缀,与 `fmtEnergyKwh` 对齐。
---
## 3. DB 层验证
**migrate.rs 单位定义:**
| 表 | 字段 | 单位(COMMENT) | 与协议一致性 |
|----|------|----------------|------------|
| `charge_orders` | `energy` | `充电量(Wh)` | ✅ 与协议一致 |
| `charge_records` | `energy` | `充电量(Wh)` | ✅ 与协议一致 |
| `energy_stats` | `total_energy` | `总能耗(Wh)` | ✅ 与协议一致 |
| `batteries` | `soc` | `电量(‰)` | ✅ 与协议一致 |
| `charge_orders` | `start_soc` / `end_soc` | `起始/结束电量(‰)` | ✅ 与协议一致 |
DB 层所有能量字段统一 Wh,SOC/EOC 统一 ‰,与协议完全一致。✅
---
## 4. 审查结论
### 正确率:91%
| 维度 | 正确 | 问题 |
|------|------|------|
| 协议 → DB 单位 | ✅ 100% | 无 |
| DB → 后端 API | ⚠️ 2/3 正确 | 2个字段名误导(energy_kwh / today_energy_kwh) |
| API → 前端展示 | ✅ 100% | 前端所有转换正确 |
| API 文档 | ⚠️ 83% | SOC/EOC 单位标注错误 + energy_kwh 命名误导 |
### 需修复
| 优先级 | 问题 | 文件 | 修改 |
|--------|------|------|------|
| 🔴 | SOC/EOC 单位标注 | `docs/API.md:206-207` | `%(0-100)` → `‰(0-10000)` |
| 🟡 | `fmtEnergy` 缺单位标签 | `h5-uniapp/compartment-detail/index.vue:143` | 加 `' kWh'` 后缀 |
### 已有保障(无需修复)
- `docs/API.md:421` 已对 `energy_kwh` / `today_energy_kwh` 的命名陷阱做注释
- 前端所有 7 个单位转换函数逻辑正确
- DB 层所有能量字段统一 Wh,SOC 统一 ‰,与协议一致
---
## 5. 审查路径图
```
设备协议 (tcp-protocol.md)
├─ voltage mV ──────────────────────┐
├─ current mA ──────────────────────┤
├─ power mW ───────────────────────┤
├─ temp K×100 ─────────────────────┤
├─ soc ‰ ──────────────────────────┤
├─ soh ‰ ──────────────────────────┤
└─ energy Wh ──────────────────────┤
│
▼
DB (migrate.rs)
energy BIGINT (Wh) ✅
soc INT (‰) ✅
temp 由 BMS 直接传入
│
▼
api-server (h5.rs / cabinets.rs)
原样返回 raw 值 ✅
字段名 _kwh 误导 🟡
│
▼
H5 前端 (compartment-detail / device-detail)
全部 /1000、/10、/100 转换 ✅
fmtEnergy 缺单位标签 🟡
│
▼
API 文档 (API.md)
SOC 标 % 实际 ‰ 🔴
energy_kwh 注释齐全 ✅ (已有说明)
```
---
*审查基于代码静态分析,未运行实际设备验证。建议结合真实设备数据对 SOC 和 energy 展示做端到端验证。*