2026-07-02 05:38:01 +08:00
|
|
|
|
# AGENTS.md - 充电柜项目工作手册
|
|
|
|
|
|
|
|
|
|
|
|
## 项目简介
|
|
|
|
|
|
|
|
|
|
|
|
充电柜矩阵系统(Power Matrix System / PMS)
|
2026-09-19 23:40:46 +08:00
|
|
|
|
产品名:安知充(anzhizhichong),品牌名:艾动智能,已有充电站平台在运行,充电柜是新增产品线。
|
2026-07-02 05:38:01 +08:00
|
|
|
|
仓数不固定:1柜控板+N仓控板,每仓控板管6仓,最小6仓。
|
|
|
|
|
|
|
|
|
|
|
|
## 设备标识
|
|
|
|
|
|
|
|
|
|
|
|
- **平台抽象ID**:`10-00000000`(绑定柜控板,二维码对应此ID)
|
|
|
|
|
|
- **设备ID**:4G SIM卡15位IMEI(10进制)
|
|
|
|
|
|
- **仓体全局ID**:`平台层ID-仓板ID-仓体ID`
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- **扫码入口**:微信小程序(通过小程序码进入充电流程,后端 API 统一前缀 `/api/pms/`)
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
## 技术栈
|
|
|
|
|
|
|
|
|
|
|
|
- 后端:Rust + Axum
|
|
|
|
|
|
- 前端:React + Arco Design Pro(React版)
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- 微信小程序用户端:uni-app + Vue 3 + Pinia(构建目标 mp-weixin)
|
2026-07-02 05:38:01 +08:00
|
|
|
|
- 数据库:MySQL(阿里云RDS),开发/生产共用
|
|
|
|
|
|
- 缓存:Redis(阿里云Redis),开发/生产共用
|
|
|
|
|
|
- 设备通讯:TCP长连接(柜控板4G模块,Air780E Cat.1)
|
|
|
|
|
|
- 权限:RBAC,精确到按钮级别(权限码如 device:door:unlock)
|
|
|
|
|
|
|
|
|
|
|
|
## 项目结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
D:\charging-cabinet\
|
|
|
|
|
|
├── tasks/ # 任务文件(opencode执行)
|
|
|
|
|
|
├── docs/ # 开发文档、协议文档
|
2026-07-02 14:58:47 +08:00
|
|
|
|
├── deploy/ # 部署配置文件
|
|
|
|
|
|
│ ├── nginx/ # Nginx配置
|
|
|
|
|
|
│ ├── *.service # systemd服务单元
|
|
|
|
|
|
│ └── deploy-*.sh # 部署脚本
|
2026-07-02 19:44:23 +08:00
|
|
|
|
├── tools/ # 开发工具
|
|
|
|
|
|
│ └── db.py # 数据库工具
|
2026-07-02 05:38:01 +08:00
|
|
|
|
├── software/
|
2026-07-02 14:58:47 +08:00
|
|
|
|
│ ├── api-server/ # Rust API服务
|
|
|
|
|
|
│ ├── device-server/ # Rust TCP设备服务
|
2026-09-19 23:40:46 +08:00
|
|
|
|
│ ├── h5-uniapp/ # 微信小程序用户端(uni-app + Vue 3,构建目标 mp-weixin)
|
2026-07-02 19:44:23 +08:00
|
|
|
|
│ └── web/ # React前端(WEB管理端)
|
2026-07-02 05:38:01 +08:00
|
|
|
|
├── hardware/ # 硬件资料(原理图/PCB等)
|
|
|
|
|
|
└── test/ # 测试用例/报告
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
> **技能库不入库** — 仓库内不存放任何 skills(`.opencode/skills`、`software/h5-uniapp/.agents/skills`、`software/h5-uniapp/agent/skills` 已于提交 `9829e9d` 移除)。开发期技能统一从全局技能目录加载:`~/.dsh/skills`(如 wechatide-skill)、`~/.agents/skills`(如 anysearch)。请勿在仓库内重新 vendor 技能副本。
|
|
|
|
|
|
|
2026-07-02 05:38:01 +08:00
|
|
|
|
## 部署架构
|
|
|
|
|
|
|
|
|
|
|
|
### 环境规划
|
|
|
|
|
|
|
2026-07-02 14:58:47 +08:00
|
|
|
|
| 环境 | 域名 | API服务 | TCP服务 | Redis DB | 部署路径 | URL路径 |
|
|
|
|
|
|
|------|------|---------|---------|----------|----------|---------|
|
|
|
|
|
|
| 正式环境 | app.anzhizhichong.com | :9000 | :9001 | db5 | /data/pms | /pms/ |
|
|
|
|
|
|
| 测试环境 | test.anzhizhichong.com | :3000 | :3001 | db6 | /data/pms_dev | /pms/ |
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
**服务端口分配(规划):**
|
2026-07-02 14:58:47 +08:00
|
|
|
|
- 正式环境:API 9000,TCP 9001
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- 测试环境:API 3000,TCP 3001
|
|
|
|
|
|
|
|
|
|
|
|
> ✅ **实际部署现状(2026-09 已按规划完成迁移)**:
|
|
|
|
|
|
> - **正式环境已迁到 API `:9000` / TCP `:9001`**(`/data/pms/*/.env`、systemd 单元、nginx
|
|
|
|
|
|
> `app.anzhizhichong.com.conf` 的 `proxy_pass` 均已改);单元已补 `EnvironmentFile=/data/pms/*/.env`
|
|
|
|
|
|
> (**新二进制强制要求环境变量,缺它起不来**)。
|
2026-09-20 00:01:01 +08:00
|
|
|
|
> - **测试环境按本表规划独立部署在同一台服务器上**(测试就是测试,与生产完全隔离):
|
|
|
|
|
|
> `pms-api-test :3000` + `pms-device-test :3001`,路径 `/data/pms_dev`,连 `pms_dev` 库 + Redis **db6**;
|
|
|
|
|
|
> `test.anzhizhichong.com` 的 nginx `/api/pms/` 指向 `:3000`。
|
|
|
|
|
|
> - ⚠️ **绝对不要把测试域名的 API 指到生产端口 `:9000`** —— 历史事故:测试 nginx 曾指向
|
|
|
|
|
|
> `:3000`,而当时 `:3000` 正是生产 API,等于**测试后台在操作生产数据**。
|
|
|
|
|
|
> 判断方法:`test` 域名 `/api/pms/` 返回 **401**(有测试后端)是对的;若返回 **502** 说明测试后端没起。
|
|
|
|
|
|
> - 两个环境必须同时满足:生产 `:9000/:9001`(db5/pms)、测试 `:3000/:3001`(db6/pms_dev),
|
|
|
|
|
|
> 端口或库号任一串了就是事故。
|
2026-07-02 14:58:47 +08:00
|
|
|
|
|
|
|
|
|
|
**部署路径:**
|
2026-07-02 15:43:59 +08:00
|
|
|
|
- 产品介绍页:/data/landing
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- 正式环境:/data/pms(api-server、device-server、web)
|
|
|
|
|
|
- 测试环境:/data/pms_dev(api-server、device-server、web)
|
2026-07-02 14:58:47 +08:00
|
|
|
|
|
|
|
|
|
|
**Nginx配置:**
|
|
|
|
|
|
- 正式: /etc/nginx/conf.d/app.anzhizhichong.com.conf
|
|
|
|
|
|
- 测试: /etc/nginx/conf.d/test.anzhizhichong.com.conf
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
### 服务器信息
|
|
|
|
|
|
|
|
|
|
|
|
- **外网IP**: 118.31.119.206
|
|
|
|
|
|
- **内网IP**: 172.25.42.239
|
|
|
|
|
|
- **系统**: CentOS Stream 9(2核/3.5G/49G)
|
|
|
|
|
|
- **ZeroTier**: 网络ID `63e843dd42f54b9c`,服务器节点 `12a91dd47d`,IP `10.8.0.252`
|
|
|
|
|
|
- **本地电脑ZeroTier IP**: `10.8.0.248`
|
|
|
|
|
|
- **已装软件**: Nginx 1.20.1、ZeroTier 1.16.2、socat(开机自启)
|
|
|
|
|
|
- **服务**: mysql-proxy(3306→RDS)、redis-proxy(6379→Redis)、Gitea(3080)、acme.sh
|
|
|
|
|
|
- **不装**: Docker/Node/Rust(本地编译传二进制)
|
|
|
|
|
|
- **SSH**: plink连接,首次需 `echo y` 接受host key
|
|
|
|
|
|
|
2026-07-02 14:58:47 +08:00
|
|
|
|
### 正式环境数据库
|
|
|
|
|
|
|
|
|
|
|
|
- MySQL: `rm-bp1bb7yiv5n8z4uvp.mysql.rds.aliyuncs.com`,user: root,password: Hbhyg731024,database: `pms`
|
|
|
|
|
|
- Redis: `r-bp1lsezvgzmgtwhbtg.redis.rds.aliyuncs.com`,password: Hbhyg731024,db: 5
|
|
|
|
|
|
|
2026-07-02 05:38:01 +08:00
|
|
|
|
### 本地开发环境
|
|
|
|
|
|
|
|
|
|
|
|
**数据库连接(通过ZeroTier):**
|
|
|
|
|
|
```
|
|
|
|
|
|
MySQL: 10.8.0.252:3306
|
|
|
|
|
|
user: root
|
|
|
|
|
|
password: Hbhyg731024@
|
|
|
|
|
|
database: pms_dev (新建,不动AZZCWeChat*)
|
|
|
|
|
|
|
|
|
|
|
|
Redis: 10.8.0.252:6379
|
|
|
|
|
|
password: Hbhyg731024@
|
2026-07-02 14:58:47 +08:00
|
|
|
|
db: 6 (测试环境用db6,不动db1-5)
|
2026-07-02 05:38:01 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**代理说明:**
|
|
|
|
|
|
- 服务器nginx stream转发MySQL(3306)和Redis(6379)到阿里云RDS
|
|
|
|
|
|
- systemd自启,`Restart=always` 自动恢复
|
|
|
|
|
|
- 本地直连 `10.8.0.252`,无需SSH隧道
|
|
|
|
|
|
|
2026-07-02 19:44:23 +08:00
|
|
|
|
### 数据库工具
|
|
|
|
|
|
|
|
|
|
|
|
本地可直接访问数据库(通过ZeroTier),使用 `tools/db.py`:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 列出所有表
|
|
|
|
|
|
python tools/db.py tables
|
|
|
|
|
|
|
|
|
|
|
|
# 统计各表数据量
|
|
|
|
|
|
python tools/db.py count
|
|
|
|
|
|
|
|
|
|
|
|
# 执行查询
|
|
|
|
|
|
python tools/db.py query "SELECT * FROM users"
|
|
|
|
|
|
|
|
|
|
|
|
# 执行写入
|
|
|
|
|
|
python tools/db.py exec "UPDATE users SET status=1 WHERE id=1"
|
|
|
|
|
|
|
|
|
|
|
|
# 导入SQL文件
|
|
|
|
|
|
python tools/db.py import data.sql
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-02 05:38:01 +08:00
|
|
|
|
### 证书
|
|
|
|
|
|
|
|
|
|
|
|
- 阿里云免费SSL证书(acme.sh自动续期)
|
|
|
|
|
|
- 存放路径: `/etc/nginx/ssl/{域名}.pem` + `/etc/nginx/ssl/{域名}.key`
|
|
|
|
|
|
- 已配置: app.anzhizhichong.com、test.anzhizhichong.com
|
|
|
|
|
|
|
|
|
|
|
|
### 内部服务
|
|
|
|
|
|
|
|
|
|
|
|
- **Gitea**: `https://app.anzhizhichong.com/doc/` (端口3080)
|
|
|
|
|
|
- 组织: anzhizhichong
|
|
|
|
|
|
- 仓库: charging-cabinet
|
|
|
|
|
|
- 用户: admin / skyp76
|
|
|
|
|
|
|
|
|
|
|
|
## 开发约束
|
|
|
|
|
|
|
|
|
|
|
|
### 禁止操作
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- **禁止修改设备与TCP服务的通信协议** — 设备端固件不可改,TCP 报文 JSON 格式、字段名、act 类型、签名机制(SHA256取8位)全部冻结;所有修复必须是服务端内部逻辑变更,设备侧零感知
|
2026-07-02 05:38:01 +08:00
|
|
|
|
- **禁止在服务器上编译Rust** — 2核/3.5G会OOM,本地编译传二进制
|
|
|
|
|
|
- **禁止使用socat fork模式** — 进程泄漏会耗尽内存,用nginx stream代理
|
2026-07-04 04:03:02 +08:00
|
|
|
|
- **禁止提交密码/密钥到仓库** — 使用环境变量或本地配置文件(已gitignore)
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
### 编码任务下发
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- 任务需求写到 `tasks/` 目录的md文件(编号递增,如 036-xxx.md)
|
|
|
|
|
|
- 用户手工将任务交给 opencode 执行(模型:sensenova/glm-5.2,配置在项目根 opencode.json)
|
|
|
|
|
|
- 完成后由 PM(本 agent)验收:cargo check 两服务 → 并行子代理审查代码 → 不通过则写返工任务文件
|
|
|
|
|
|
- 任务文件必须包含"⛔ 硬性约束"段落(设备协议不可改)和"质量约束"段落
|
|
|
|
|
|
- 验收不通过时,返工任务需包含具体断路点分析 + 修复方向建议
|
|
|
|
|
|
- 已完成的任务文件及时清理
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
### 质量约束模板
|
|
|
|
|
|
|
|
|
|
|
|
所有编码任务必须追加以下约束:
|
|
|
|
|
|
|
|
|
|
|
|
1. 完成代码后执行对应栈静态校验(Rust→cargo check,TS→tsc --noEmit),零报错零警告
|
|
|
|
|
|
2. 分层拆分,单函数≤80行,命名语义化,完整注释
|
|
|
|
|
|
3. 所有外部IO/网络请求异常捕获,禁止裸panic
|
|
|
|
|
|
4. 分支逻辑全覆盖,不遗漏兜底分支
|
|
|
|
|
|
5. 常量抽离,不使用废弃API
|
|
|
|
|
|
6. Rust:内存安全,合理管理所有权,禁用unsafe无合理理由
|
|
|
|
|
|
7. React:函数式组件+Hooks,不写Class组件
|
|
|
|
|
|
|
|
|
|
|
|
### 验收流程
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
1. `cargo check` 两个服务(api-server + device-server)零错误
|
|
|
|
|
|
2. 派并行子代理审查代码(按任务逐项核对修复要求 + 硬性约束)
|
|
|
|
|
|
3. 审查不通过 → 写返工任务文件(含断路点分析、修复方向、具体行号)
|
|
|
|
|
|
4. 审查通过 → 清理任务文件,更新文档
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Rust 编码规范
|
|
|
|
|
|
|
|
|
|
|
|
### 错误处理
|
|
|
|
|
|
|
|
|
|
|
|
- 使用 `thiserror` 定义业务错误枚举,`AppError` 统一包装
|
|
|
|
|
|
- 所有 `sqlx` / `redis` 调用必须 `.map_err(AppError::Database)?`
|
|
|
|
|
|
- 网络请求/外部IO 必须 `match` 或 `if let` 处理,禁止裸 `.unwrap()`
|
|
|
|
|
|
- 关键业务路径用 `tracing::error!` 记录错误上下文
|
|
|
|
|
|
|
|
|
|
|
|
### Async 模式
|
|
|
|
|
|
|
|
|
|
|
|
- 使用 `tokio` 运行时,数据库/Redis 操作用 `.await`
|
|
|
|
|
|
- 长时间任务用 `tokio::spawn` 异步处理,不阻塞主循环
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- Redis 队列消费:**禁止用 `BRPOP key 0`(无限阻塞),也不要依赖阻塞命令的"即时唤醒"** ——
|
|
|
|
|
|
本项目 Redis 经 nginx stream 代理访问阿里云 RDS,客户端又是多路复用的 `ConnectionManager`,
|
|
|
|
|
|
这种组合下 `BRPOP` 语义不可靠(实测 2026-09):
|
|
|
|
|
|
- `BRPOP key 0` → 代理掐掉空闲连接后**消费永久卡死**,进程活着、HTTP 正常、**零错误日志**;
|
|
|
|
|
|
- `BRPOP key N` → **"超时才醒"而非"数据到达即醒"**,超时设多大延迟就至少多大(实测下行指令 15~45 秒)。
|
|
|
|
|
|
**规范做法**:`RPOP` 非阻塞轮询 + 空队列时 `sleep` 200ms(生产者 `LPUSH`,FIFO 一致),
|
|
|
|
|
|
延迟稳定在 200ms 内;取到消息后 `tokio::spawn` 并发处理。
|
2026-07-04 04:03:02 +08:00
|
|
|
|
|
|
|
|
|
|
### 模块划分
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
src/
|
|
|
|
|
|
├── routes/ # HTTP 路由处理器(按业务拆分)
|
2026-09-19 23:40:46 +08:00
|
|
|
|
├── middleware/ # 中间件(认证、日志、CORS、scope_guard 组织归属校验)
|
2026-07-04 04:03:02 +08:00
|
|
|
|
├── db/ # 数据库连接池、迁移
|
2026-09-19 23:40:46 +08:00
|
|
|
|
├── commands/ # 设备指令下发逻辑(含 AppState 定义)
|
2026-07-04 04:03:02 +08:00
|
|
|
|
├── nodes/ # 设备服务节点管理
|
|
|
|
|
|
├── workers/ # 后台 Worker(report/reply)
|
|
|
|
|
|
└── error.rs # 统一错误定义
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 命名约定
|
|
|
|
|
|
|
|
|
|
|
|
- 路由处理器:`动词 + 名词`(如 `get_cabinets`、`create_organization`)
|
|
|
|
|
|
- 数据库查询结构体:`XxxRow`(如 `CabinetBasicRow`)
|
|
|
|
|
|
- 请求体:`XxxBody` 或 `XxxQuery`(如 `CreateCabinetBody`)
|
|
|
|
|
|
- 常量:`UPPER_SNAKE_CASE`(如 `JWT_SECRET_ENV`)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## React 编码规范
|
|
|
|
|
|
|
|
|
|
|
|
### 组件规范
|
|
|
|
|
|
|
|
|
|
|
|
- 纯函数组件 + Hooks,禁止 Class 组件
|
|
|
|
|
|
- 组件文件与路由页面一一对应,放 `src/pages/` 下
|
|
|
|
|
|
- 复用组件放 `src/components/`,按功能命名
|
|
|
|
|
|
- Props 接口定义在组件文件顶部,用 `interface` 声明
|
|
|
|
|
|
|
|
|
|
|
|
### 状态管理
|
|
|
|
|
|
|
|
|
|
|
|
- 页面级状态用 `useState` / `useEffect`
|
|
|
|
|
|
- 全局状态用 `zustand`(如用户信息、权限)
|
|
|
|
|
|
|
|
|
|
|
|
### 样式规范
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- 使用 Arco Design 组件库,优先使用组件自带的样式
|
|
|
|
|
|
- 内联 style 仅用于动态计算值,静态样式用 CSS class
|
2026-07-04 04:03:02 +08:00
|
|
|
|
- 表格列统一 `min-width` + `align="center"`,无 `fixed="right"`
|
|
|
|
|
|
- 分页:上下双分页,`page-sizes="[10,20,30,50]"`
|
|
|
|
|
|
|
|
|
|
|
|
### 图标映射
|
|
|
|
|
|
|
|
|
|
|
|
| 操作 | 图标 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| 编辑 | Edit |
|
|
|
|
|
|
| 删除 | Delete |
|
|
|
|
|
|
| 查看 | ViewIcon |
|
|
|
|
|
|
| 搜索 | Search |
|
|
|
|
|
|
| 重置 | Refresh |
|
|
|
|
|
|
| 新增 | Plus(保留文字) |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## API 设计规范
|
|
|
|
|
|
|
|
|
|
|
|
### 响应格式
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"code": 0, // 0=成功,非0=失败
|
|
|
|
|
|
"data": { ... }, // 业务数据
|
|
|
|
|
|
"message": "ok" // 提示信息
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 错误码
|
|
|
|
|
|
|
|
|
|
|
|
| code | 含义 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| 0 | 成功 |
|
|
|
|
|
|
| 400 | 请求参数错误 |
|
|
|
|
|
|
| 401 | 未认证(token缺失/无效) |
|
|
|
|
|
|
| 403 | 无权限 |
|
|
|
|
|
|
| 404 | 资源不存在 |
|
|
|
|
|
|
| 500 | 服务器内部错误 |
|
|
|
|
|
|
|
|
|
|
|
|
### 分页约定
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"code": 0,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"list": [...],
|
|
|
|
|
|
"total": 100,
|
|
|
|
|
|
"page": 1,
|
|
|
|
|
|
"page_size": 20
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### RESTful 约定
|
|
|
|
|
|
|
|
|
|
|
|
| 方法 | 路径 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| GET | /api/xxx | 列表查询 |
|
|
|
|
|
|
| GET | /api/xxx/:id | 详情 |
|
|
|
|
|
|
| POST | /api/xxx | 创建 |
|
|
|
|
|
|
| PUT | /api/xxx/:id | 更新 |
|
|
|
|
|
|
| DELETE | /api/xxx/:id | 删除 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Git 工作流
|
|
|
|
|
|
|
|
|
|
|
|
### 分支策略
|
|
|
|
|
|
|
|
|
|
|
|
- `main` — 正式环境,只接受 PR 合并
|
|
|
|
|
|
- `develop` — 开发分支,日常开发在此分支
|
|
|
|
|
|
- `feature/*` — 功能分支,从 develop 拉出,完成后合并回 develop
|
|
|
|
|
|
- `fix/*` — 修复分支,从 main 或 develop 拉出
|
|
|
|
|
|
|
|
|
|
|
|
### Commit 规范
|
|
|
|
|
|
|
|
|
|
|
|
格式:`<type>(<scope>): <subject>`
|
|
|
|
|
|
|
|
|
|
|
|
| type | 说明 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| feat | 新功能 |
|
|
|
|
|
|
| fix | 修复 bug |
|
|
|
|
|
|
| docs | 文档变更 |
|
|
|
|
|
|
| style | 代码格式(不影响逻辑) |
|
|
|
|
|
|
| refactor | 重构(不新增功能/修复bug) |
|
|
|
|
|
|
| test | 测试相关 |
|
|
|
|
|
|
| chore | 构建/工具变更 |
|
|
|
|
|
|
|
|
|
|
|
|
示例:
|
|
|
|
|
|
- `feat(cabinets): 添加设备RSSI信号显示`
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- `fix(mp): 修复登录后token未持久化问题`
|
2026-07-04 04:03:02 +08:00
|
|
|
|
- `docs(api): 更新充电记录接口文档`
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 测试标准
|
|
|
|
|
|
|
|
|
|
|
|
### 测试类型
|
|
|
|
|
|
|
|
|
|
|
|
| 类型 | 工具 | 要求 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| 单元测试 | Rust `#[test]` / Vitest | 核心业务逻辑必须覆盖 |
|
|
|
|
|
|
| 集成测试 | API 请求验证 | 关键接口(登录/充电/开门)必须验证 |
|
|
|
|
|
|
| 静态校验 | `cargo check` / `tsc --noEmit` | 零报错零警告 |
|
|
|
|
|
|
|
|
|
|
|
|
### 测试覆盖率
|
|
|
|
|
|
|
|
|
|
|
|
- 核心业务逻辑(充电控制、指令下发、权限校验):≥80%
|
|
|
|
|
|
- 数据库查询函数:≥60%
|
|
|
|
|
|
- 工具函数:≥90%
|
|
|
|
|
|
|
|
|
|
|
|
### 测试命名
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
test_<被测函数>_<场景>_<期望结果>
|
|
|
|
|
|
|
|
|
|
|
|
test_verify_password_correct_returns_true
|
|
|
|
|
|
test_verify_password_wrong_returns_false
|
|
|
|
|
|
test_check_permission_admin_returns_ok
|
|
|
|
|
|
test_check_permission_user_returns_forbidden
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 安全规范
|
|
|
|
|
|
|
|
|
|
|
|
### 认证授权
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- JWT Token 有效期:24小时,过期需重新登录
|
|
|
|
|
|
- JWT 密钥:启动时从 `JWT_SECRET` 环境变量读取,缺失则 panic(禁止硬编码回退),存储在 AppState.jwt_secret
|
2026-07-04 04:03:02 +08:00
|
|
|
|
- 密码存储:argon2 哈希,禁止明文或 MD5/SHA1
|
|
|
|
|
|
- RBAC 权限码:精确到按钮级别(如 `device:door:unlock`)
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- 组织数据隔离:所有写操作校验资源归属(scope_guard.rs),总管理员(role_level=2)豁免
|
2026-07-04 04:03:02 +08:00
|
|
|
|
- 敏感操作(删除/禁用/开门)需二次确认
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- TCP 连接认证:设备 login 成功(响应含 `spec` + `suc=1`)前,敏感指令(open/pow_on/pow_off/reset/auth_str)不投递
|
|
|
|
|
|
- 协议 `id` 只表示**动作目标**、**不含 IMEI**:**柜级省略** / **一段**=单板(`"01"`)/ **两段**=仓体级(`"01-3"`)
|
|
|
|
|
|
- **平台应答必带 `act`(与请求一致)+ `msg_id`(与请求一致)**;判"应答 vs 指令"**只看有没有 `suc`**
|
|
|
|
|
|
- **`dev_id` 只在 `auth_str` / `login` 携带**(用于绑定 TCP 连接),其余报文一律不带;设备身份由连接确定
|
|
|
|
|
|
- **未登录连接不予保持**:建连后 30 秒内未 `login` 成功即主动断开
|
2026-07-04 04:03:02 +08:00
|
|
|
|
|
|
|
|
|
|
### 输入校验
|
|
|
|
|
|
|
|
|
|
|
|
- 所有 API 入参必须校验(长度、格式、范围)
|
|
|
|
|
|
- SQL 查询使用参数化查询,禁止字符串拼接
|
|
|
|
|
|
- IMEI 校验:必须15位数字
|
|
|
|
|
|
- 手机号校验:11位数字,正则 `^1[3-9]\d{9}$`
|
|
|
|
|
|
|
|
|
|
|
|
### 数据安全
|
|
|
|
|
|
|
|
|
|
|
|
- 生产数据库密码不入代码,使用环境变量
|
|
|
|
|
|
- Redis 密码同上
|
|
|
|
|
|
- 日志禁止输出密码/Token/密钥
|
|
|
|
|
|
- 敏感字段(密码、auth_str)在序列化时跳过
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 日志规范
|
|
|
|
|
|
|
|
|
|
|
|
### 日志级别
|
|
|
|
|
|
|
|
|
|
|
|
| 级别 | 用途 | 示例 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| `ERROR` | 系统异常、业务错误 | 数据库连接失败、指令下发超时 |
|
|
|
|
|
|
| `WARN` | 潜在问题、可恢复异常 | 设备离线、重试成功 |
|
|
|
|
|
|
| `INFO` | 关键业务事件 | 设备登录、充电开始/结束、用户操作 |
|
|
|
|
|
|
| `DEBUG` | 调试信息 | API 请求参数、SQL 执行时间 |
|
|
|
|
|
|
|
|
|
|
|
|
### 日志格式
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
|
// 使用 tracing crate
|
|
|
|
|
|
tracing::info!(imei = %imei, board_id = board_id, "设备登录成功");
|
|
|
|
|
|
tracing::error!(err = %e, "数据库查询失败");
|
|
|
|
|
|
tracing::warn!(compartment_id = id, "仓体状态异常");
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 关键操作日志
|
|
|
|
|
|
|
|
|
|
|
|
以下操作必须记录:
|
|
|
|
|
|
|
|
|
|
|
|
| 操作 | 日志内容 |
|
|
|
|
|
|
|------|----------|
|
|
|
|
|
|
| 用户登录/登出 | 用户ID、手机号、IP |
|
|
|
|
|
|
| 设备指令下发 | IMEI、指令类型、仓体ID |
|
|
|
|
|
|
| 充电开始/结束 | 仓体ID、时长、电量 |
|
|
|
|
|
|
| 组织/项目变更 | 操作类型、ID、名称 |
|
|
|
|
|
|
| 权限变更 | 用户ID、权限码、操作人 |
|
|
|
|
|
|
| 数据删除 | 表名、ID、操作人 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 性能规范
|
|
|
|
|
|
|
|
|
|
|
|
### 数据库
|
|
|
|
|
|
|
|
|
|
|
|
- 查询必须走索引,禁止全表扫描(`EXPLAIN` 验证)
|
|
|
|
|
|
- 分页查询:避免深分页,用游标分页(`WHERE id > ?`)
|
|
|
|
|
|
- 批量操作:用 `INSERT ... VALUES (...), (...)` 而非逐条插入
|
|
|
|
|
|
- 连接池:MySQL 最大连接数 ≤ 50,Redis 连接数 ≤ 20
|
|
|
|
|
|
|
|
|
|
|
|
### Redis
|
|
|
|
|
|
|
|
|
|
|
|
- 缓存 key 规范:`{业务}:{标识}`(如 `device:{imei}`)
|
|
|
|
|
|
- TTL 设置:在线状态 300秒,缓存数据按业务设定
|
|
|
|
|
|
- 避免大 Key:单个 Hash ≤ 500 字段,List ≤ 1000 元素
|
|
|
|
|
|
- 批量操作:用 Pipeline 减少往返次数
|
|
|
|
|
|
|
|
|
|
|
|
### API 响应
|
|
|
|
|
|
|
|
|
|
|
|
- 列表接口响应时间 ≤ 500ms
|
|
|
|
|
|
- 详情接口响应时间 ≤ 200ms
|
|
|
|
|
|
- 批量操作接口响应时间 ≤ 1s
|
|
|
|
|
|
- 响应体大小:列表 ≤ 1MB,详情 ≤ 100KB
|
|
|
|
|
|
|
|
|
|
|
|
### 前端
|
|
|
|
|
|
|
|
|
|
|
|
- 首屏加载 ≤ 3s(3G网络)
|
|
|
|
|
|
- 交互响应 ≤ 100ms
|
|
|
|
|
|
- 列表滚动:虚拟滚动(≥100条数据)
|
|
|
|
|
|
- 图片:WebP 格式,懒加载
|
|
|
|
|
|
|
2026-07-02 14:58:47 +08:00
|
|
|
|
## 部署与维护
|
|
|
|
|
|
|
|
|
|
|
|
### SSH连接
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 外网IP(推荐)
|
|
|
|
|
|
echo y | plink -ssh root@118.31.119.206 -pw "Hbhyg731024@"
|
|
|
|
|
|
|
|
|
|
|
|
# ZeroTier内网IP
|
|
|
|
|
|
echo y | plink -ssh root@10.8.0.252 -pw "Hbhyg731024@"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 数据库连接
|
|
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
所有数据库操作通过 ZeroTier 直接连接 `10.8.0.252`,无需 SSH 隧道到服务器。
|
|
|
|
|
|
|
|
|
|
|
|
**MySQL(通过 ZeroTier):**
|
2026-07-02 14:58:47 +08:00
|
|
|
|
```bash
|
2026-07-04 04:03:02 +08:00
|
|
|
|
# 命令行
|
2026-07-02 14:58:47 +08:00
|
|
|
|
mysql -h 10.8.0.252 -u root -p
|
|
|
|
|
|
# 密码:Hbhyg731024@
|
|
|
|
|
|
# 测试环境库:pms_dev
|
|
|
|
|
|
# 正式环境库:pms
|
2026-07-04 04:03:02 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
# Python(Agent 常用方式)
|
|
|
|
|
|
import pymysql
|
|
|
|
|
|
conn = pymysql.connect(host='10.8.0.252', user='root', password='Hbhyg731024@', database='pms') # 正式环境
|
|
|
|
|
|
conn = pymysql.connect(host='10.8.0.252', user='root', password='Hbhyg731024@', database='pms_dev') # 测试环境
|
|
|
|
|
|
```
|
2026-07-02 14:58:47 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
**Redis(通过 ZeroTier):**
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 命令行
|
2026-07-02 14:58:47 +08:00
|
|
|
|
redis-cli -h 10.8.0.252 -a "Hbhyg731024@"
|
2026-07-04 04:03:02 +08:00
|
|
|
|
# 测试环境:select 6
|
|
|
|
|
|
# 正式环境:select 5
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
# Python(Agent 常用方式)
|
|
|
|
|
|
import redis
|
|
|
|
|
|
r = redis.Redis(host='10.8.0.252', password='Hbhyg731024@', db=5) # 正式环境
|
|
|
|
|
|
r = redis.Redis(host='10.8.0.252', password='Hbhyg731024@', db=6) # 测试环境
|
2026-07-02 14:58:47 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
**数据库工具 `tools/db.py`(默认连接测试库 pms_dev):**
|
|
|
|
|
|
```bash
|
|
|
|
|
|
python tools/db.py tables # 列出所有表
|
|
|
|
|
|
python tools/db.py count # 统计各表数据量
|
|
|
|
|
|
python tools/db.py query "SELECT * FROM users" # 查询
|
|
|
|
|
|
python tools/db.py exec "UPDATE users SET ..." # 写入
|
|
|
|
|
|
```
|
|
|
|
|
|
> 注意:`tools/db.py` 默认连的是 `pms_dev`(测试库),要操作正式库请用上面的 Python 直连方式。
|
|
|
|
|
|
|
2026-07-02 14:58:47 +08:00
|
|
|
|
### Nginx配置
|
|
|
|
|
|
|
|
|
|
|
|
**正式环境** (`/etc/nginx/conf.d/app.anzhizhichong.com.conf`):
|
|
|
|
|
|
```nginx
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 80;
|
|
|
|
|
|
server_name app.anzhizhichong.com;
|
|
|
|
|
|
return 301 https://$server_name$request_uri;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 443 ssl http2;
|
|
|
|
|
|
server_name app.anzhizhichong.com;
|
|
|
|
|
|
|
|
|
|
|
|
ssl_certificate /etc/nginx/ssl/app.anzhizhichong.com.pem;
|
|
|
|
|
|
ssl_certificate_key /etc/nginx/ssl/app.anzhizhichong.com.key;
|
|
|
|
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
|
|
|
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
|
|
|
|
|
2026-07-02 15:43:59 +08:00
|
|
|
|
# 产品介绍页
|
|
|
|
|
|
root /data/landing;
|
2026-07-02 14:58:47 +08:00
|
|
|
|
index index.html;
|
|
|
|
|
|
|
2026-07-02 15:18:45 +08:00
|
|
|
|
location /doc/ {
|
2026-07-02 14:58:47 +08:00
|
|
|
|
proxy_pass http://127.0.0.1:3080/;
|
|
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
|
|
client_max_body_size 500m;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
# PMS API 入口(微服务统一前缀 /api/{服务})
|
|
|
|
|
|
location /api/pms/ {
|
2026-09-19 23:40:46 +08:00
|
|
|
|
proxy_pass http://127.0.0.1:9000/api/;
|
2026-07-02 14:58:47 +08:00
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-02 15:43:59 +08:00
|
|
|
|
# PMS管理前端
|
2026-07-02 14:58:47 +08:00
|
|
|
|
location /pms/ {
|
2026-07-02 15:43:59 +08:00
|
|
|
|
alias /data/pms/web/;
|
2026-07-02 14:58:47 +08:00
|
|
|
|
try_files $uri $uri/ /pms/index.html;
|
|
|
|
|
|
}
|
2026-07-03 04:50:01 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
# 微信回调(免认证)
|
|
|
|
|
|
location /api/wechat/ {
|
|
|
|
|
|
proxy_pass http://127.0.0.1:9000;
|
|
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
2026-07-03 04:50:01 +08:00
|
|
|
|
}
|
2026-07-02 14:58:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**测试环境** (`/etc/nginx/conf.d/test.anzhizhichong.com.conf`):
|
|
|
|
|
|
```nginx
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 80;
|
|
|
|
|
|
server_name test.anzhizhichong.com;
|
|
|
|
|
|
return 301 https://$server_name$request_uri;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 443 ssl http2;
|
|
|
|
|
|
server_name test.anzhizhichong.com;
|
|
|
|
|
|
|
|
|
|
|
|
ssl_certificate /etc/nginx/ssl/test.anzhizhichong.com.pem;
|
|
|
|
|
|
ssl_certificate_key /etc/nginx/ssl/test.anzhizhichong.com.key;
|
|
|
|
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
|
|
|
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
|
|
|
|
|
|
|
|
|
|
root /data/pms_dev;
|
|
|
|
|
|
index index.html;
|
|
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
# PMS API 入口(微服务统一前缀 /api/{服务})
|
|
|
|
|
|
location /api/pms/ {
|
|
|
|
|
|
proxy_pass http://127.0.0.1:3000/api/;
|
2026-07-02 14:58:47 +08:00
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
location /pms/ {
|
|
|
|
|
|
try_files $uri $uri/ /pms/index.html;
|
|
|
|
|
|
}
|
2026-07-03 04:50:01 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
# 微信回调(免认证)
|
|
|
|
|
|
location /api/wechat/ {
|
|
|
|
|
|
proxy_pass http://127.0.0.1:3000;
|
|
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
2026-07-04 04:03:02 +08:00
|
|
|
|
}
|
2026-07-02 14:58:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> 测试环境SSL证书与正式环境共用(test是app证书的SAN域名),部署时复制pem+key即可。
|
|
|
|
|
|
|
|
|
|
|
|
### 部署步骤
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
> ✅ **推荐用一键脚本(在本地开发机执行)**:
|
|
|
|
|
|
> ```bash
|
|
|
|
|
|
> pwsh deploy/deploy-prod.ps1 # 完整部署:构建 → 校验产物 → 服务器备份 → 传 .new → 原子替换 → 重启 → 验收
|
|
|
|
|
|
> pwsh deploy/deploy-prod.ps1 -SkipBuild # 跳过构建,只重发现有产物
|
|
|
|
|
|
> pwsh deploy/deploy-prod.ps1 -DryRun # 演练,不做任何改动
|
|
|
|
|
|
> pwsh deploy/deploy-prod.ps1 -SkipWeb # 只发后端
|
|
|
|
|
|
> ```
|
|
|
|
|
|
> 脚本会自动做:产物路径校验、**上传后 `chmod +x`**、**传 `.new` 再原子 `mv`**(避开 ETXTBSY)、
|
|
|
|
|
|
> `nginx -t` 后才 reload、验收(服务/端口/域名/ERROR 数)、以及**用不存在的 IMEI 探测上报消费链路**
|
|
|
|
|
|
> (不碰真实设备数据),最后打印回滚方法。
|
|
|
|
|
|
>
|
|
|
|
|
|
> ⛔ **`deploy/deploy-prod.sh` 与 `deploy/deploy-test.sh` 已废弃**(执行即报错退出):
|
|
|
|
|
|
> 它们在 `target/release/` 找产物、会安装重复的 `pms-api-prod` 单元、并会用本地 `.env`
|
|
|
|
|
|
> 覆盖线上 `.env`(历史上该文件带行内注释,会让生产起不来)。下面手写步骤仅供参考。
|
|
|
|
|
|
|
2026-07-02 14:58:47 +08:00
|
|
|
|
**1. 本地编译**
|
|
|
|
|
|
```bash
|
2026-09-19 23:40:46 +08:00
|
|
|
|
# 后端(Rust)— Windows 交叉编译 Linux 目标
|
2026-07-02 14:58:47 +08:00
|
|
|
|
cd software/api-server
|
2026-09-19 23:40:46 +08:00
|
|
|
|
cargo zigbuild --release --target x86_64-unknown-linux-gnu
|
2026-07-02 14:58:47 +08:00
|
|
|
|
|
|
|
|
|
|
cd software/device-server
|
2026-09-19 23:40:46 +08:00
|
|
|
|
cargo zigbuild --release --target x86_64-unknown-linux-gnu
|
2026-07-02 14:58:47 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
# 前端(React 管理端)
|
2026-07-02 14:58:47 +08:00
|
|
|
|
cd software/web
|
|
|
|
|
|
npm run build
|
2026-09-19 23:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
# 微信小程序用户端(uni-app)
|
|
|
|
|
|
cd software/h5-uniapp
|
|
|
|
|
|
npm run build:mp-weixin
|
|
|
|
|
|
# 产物在 dist/build/mp-weixin/,用微信开发者工具导入上传
|
2026-07-02 14:58:47 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**2. 传输到服务器**
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 传输后端二进制(测试环境)
|
2026-09-19 23:40:46 +08:00
|
|
|
|
scp software/api-server/target/x86_64-unknown-linux-gnu/release/pms-api root@118.31.119.206:/data/pms_dev/api-server/
|
|
|
|
|
|
scp software/device-server/target/x86_64-unknown-linux-gnu/release/device-server root@118.31.119.206:/data/pms_dev/device-server/
|
2026-07-02 14:58:47 +08:00
|
|
|
|
|
|
|
|
|
|
# 传输后端二进制(正式环境)
|
2026-09-19 23:40:46 +08:00
|
|
|
|
scp software/api-server/target/x86_64-unknown-linux-gnu/release/pms-api root@118.31.119.206:/data/pms/api-server/
|
|
|
|
|
|
scp software/device-server/target/x86_64-unknown-linux-gnu/release/device-server root@118.31.119.206:/data/pms/device-server/
|
2026-07-02 14:58:47 +08:00
|
|
|
|
|
|
|
|
|
|
# 传输前端静态文件
|
|
|
|
|
|
scp -r software/web/dist/* root@118.31.119.206:/data/pms_dev/web/ # 测试环境
|
|
|
|
|
|
scp -r software/web/dist/* root@118.31.119.206:/data/pms/web/ # 正式环境
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
> ⚠️ 后端二进制必须用 `cargo zigbuild --release --target x86_64-unknown-linux-gnu` 交叉编译,服务器 2核/3.5G 会 OOM,禁止在服务器上编译 Rust。
|
|
|
|
|
|
>
|
|
|
|
|
|
> ⚠️ **`pscp`/`scp` 上传后必须 `chmod +x`**(否则 systemd 报 `203/EXEC`);
|
|
|
|
|
|
> 且**运行中的二进制不可直接覆盖**(Linux 报 `ETXTBSY`)—— 正确姿势:
|
|
|
|
|
|
> 传到 `xxx.new` → `chmod +x` → `systemctl stop` → `mv -f xxx.new xxx` → `systemctl start`。
|
|
|
|
|
|
>
|
|
|
|
|
|
> ⚠️ **`.env` 不支持行内注释**:`PORT=3000 # 注释` 会让配置解析失败、服务起不来,注释必须独占一行。
|
|
|
|
|
|
|
|
|
|
|
|
**2.1 微信小程序上传**
|
|
|
|
|
|
|
|
|
|
|
|
小程序构建产物 `dist/build/mp-weixin/` 不上传到服务器,而是通过微信开发者工具上传:
|
|
|
|
|
|
|
|
|
|
|
|
1. 打开微信开发者工具
|
|
|
|
|
|
2. 导入项目目录 `software/h5-uniapp/dist/build/mp-weixin`
|
|
|
|
|
|
3. 填入 AppID(已配置在 `manifest.json`)
|
|
|
|
|
|
4. 预览/真机调试 → 上传体验版/正式版
|
2026-07-03 04:50:01 +08:00
|
|
|
|
|
2026-07-02 14:58:47 +08:00
|
|
|
|
**3. 服务器重启服务**
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# SSH到服务器
|
|
|
|
|
|
echo y | plink -ssh root@118.31.119.206 -pw "Hbhyg731024@"
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
# 正式环境
|
|
|
|
|
|
systemctl restart pms-api
|
|
|
|
|
|
systemctl restart pms-device
|
2026-07-02 14:58:47 +08:00
|
|
|
|
|
|
|
|
|
|
# 重启Nginx
|
|
|
|
|
|
systemctl restart nginx
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 服务管理
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 查看服务状态
|
2026-09-19 23:40:46 +08:00
|
|
|
|
systemctl status pms-api # 正式环境API
|
|
|
|
|
|
systemctl status pms-device # 正式环境TCP
|
2026-07-02 14:58:47 +08:00
|
|
|
|
systemctl status nginx
|
|
|
|
|
|
|
|
|
|
|
|
# 查看日志
|
2026-09-19 23:40:46 +08:00
|
|
|
|
journalctl -u pms-api -f
|
|
|
|
|
|
journalctl -u pms-device -f
|
2026-07-02 14:58:47 +08:00
|
|
|
|
tail -f /var/log/nginx/error.log
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-02 05:38:01 +08:00
|
|
|
|
## 硬件架构
|
|
|
|
|
|
|
|
|
|
|
|
- 柜控板(ID128)×1 + 仓控板(ID1-N)×N,每仓控板管6个仓体
|
|
|
|
|
|
- 仓控板与电源:1TN RS485协议(翌工)
|
|
|
|
|
|
- 仓控板与BMS:星恒Modbus / 天能485 / 无协议(平台可配置)
|
|
|
|
|
|
- 仓控板与柜控板:自定义485协议(14+功能码)
|
|
|
|
|
|
- 柜控板与后台:4G TCP长连接(Air780E模块)
|
|
|
|
|
|
|
|
|
|
|
|
## 开发范围(当前阶段)
|
|
|
|
|
|
|
|
|
|
|
|
### 做
|
|
|
|
|
|
|
|
|
|
|
|
- 后台管理系统(Web端)
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- 微信小程序用户端(uni-app 构建 mp-weixin,通过微信开发者工具上传)
|
2026-07-02 05:38:01 +08:00
|
|
|
|
- 设备TCP通讯服务(协议文档已就绪)
|
|
|
|
|
|
|
|
|
|
|
|
### 不做
|
|
|
|
|
|
|
|
|
|
|
|
- 支付/计费(本系统不涉及)
|
|
|
|
|
|
|
|
|
|
|
|
## 组织架构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
组织(企业) → 项目(放置地点) → 设备(充电柜)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
设备卖给哪个企业就划拨给该组织,设备放置的具体地点称为项目,设备必须划拨到项目下。
|
|
|
|
|
|
|
|
|
|
|
|
## 数据模型
|
|
|
|
|
|
|
|
|
|
|
|
### 核心关系
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
organization(组织/企业)
|
|
|
|
|
|
├── project(项目/放置地点)
|
|
|
|
|
|
│ └── cabinet(充电柜)
|
|
|
|
|
|
│ ├── cabin_board(仓控板×N)
|
|
|
|
|
|
│ │ └── compartment(仓体×6)
|
|
|
|
|
|
│ ├── charge_record(充电记录)
|
|
|
|
|
|
│ └── energy_stat(能耗统计)
|
|
|
|
|
|
├── user(用户)
|
|
|
|
|
|
└── device_log(设备日志)
|
|
|
|
|
|
|
|
|
|
|
|
operation_log(操作日志)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 数据库表
|
|
|
|
|
|
|
|
|
|
|
|
| 表名 | 说明 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| organizations | 组织表 |
|
|
|
|
|
|
| projects | 项目表 |
|
|
|
|
|
|
| cabinets | 充电柜表(IMEI、abstract_id、auth_str) |
|
|
|
|
|
|
| cabin_boards | 仓控板表 |
|
|
|
|
|
|
| compartments | 仓体表 |
|
|
|
|
|
|
| users | 用户表(手机号登录) |
|
|
|
|
|
|
| charge_records | 充电记录表 |
|
|
|
|
|
|
| device_logs | 设备日志表 |
|
|
|
|
|
|
| energy_stats | 能耗统计表 |
|
|
|
|
|
|
| operation_logs | 操作日志表 |
|
|
|
|
|
|
|
2026-07-03 02:45:00 +08:00
|
|
|
|
### Redis 实时状态存储
|
|
|
|
|
|
|
|
|
|
|
|
设备实时状态通过 Redis Hash 层级存储,按 `status_post` 上报内容解析写入。
|
|
|
|
|
|
|
|
|
|
|
|
**层级结构:**
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
device:{imei} # 设备级 Hash
|
|
|
|
|
|
├─ online = "1" # 在线标记(TTL 300秒)
|
|
|
|
|
|
├─ rssi = "85" # 信号强度
|
|
|
|
|
|
├─ pow_fail_dc = "0/1" # 直流停电(12V掉电)
|
|
|
|
|
|
├─ pow_fail_ac = "0/1" # 交流停电(380V掉电)
|
|
|
|
|
|
└─ device:{imei}:board:{board_idx} # 板级 Hash(仓控板1~N)
|
|
|
|
|
|
├─ status_hex = "000100020003000400050006" # 原始hex状态
|
|
|
|
|
|
└─ device:{imei}:board:{board_idx}:ch:{0-5} # 通道级 Hash(6仓)
|
|
|
|
|
|
├─ on = "0/1" # 充电中
|
|
|
|
|
|
├─ full = "0/1" # 充满
|
|
|
|
|
|
├─ fault = "0/1" # 故障
|
|
|
|
|
|
└─ hex = "00010002" # 原始hex
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**写入时机:**
|
|
|
|
|
|
| 事件 | 写入位置 | 说明 |
|
|
|
|
|
|
|------|----------|------|
|
|
|
|
|
|
| `login` | `device:{imei}.online` | 标记在线,设置TTL 300秒 |
|
|
|
|
|
|
| `status_post` | `device:{imei}` + board + ch | 解析status字典,按层级写入 |
|
|
|
|
|
|
| `pow_fail` | `device:{imei}.pow_fail_dc/ac` | 更新停电状态 |
|
|
|
|
|
|
|
|
|
|
|
|
**通道状态映射(hex → 语义):**
|
|
|
|
|
|
- 空闲(idle)
|
|
|
|
|
|
- 插入(inserted)
|
|
|
|
|
|
- 充电中(charging)
|
|
|
|
|
|
- 充满(full)
|
|
|
|
|
|
- 故障(fault)
|
|
|
|
|
|
|
|
|
|
|
|
> ⚠️ hex编码格式待硬件团队最终确认,当前实现预留状态字段,按 bit 位解析。
|
|
|
|
|
|
|
|
|
|
|
|
**API 接口:**
|
|
|
|
|
|
| 接口 | 说明 | 查询方式 |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| `GET /api/cabinets/filter` | 设备列表(设备级状态) | Pipeline 批量 HGET,1次往返 |
|
|
|
|
|
|
| `GET /api/cabinets/realtime-status?imei=xxx` | 单设备详情(板级+通道级) | KEYS 扫描 + HMGET |
|
|
|
|
|
|
|
2026-07-02 05:38:01 +08:00
|
|
|
|
## TCP通讯协议
|
|
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
详见 **[docs/柜控板固件对接文档.md](docs/柜控板固件对接文档.md)**(v1.1,**唯一权威 TCP 文档**;既是我们的内部文档,也可直接对外交付)— 包含帧格式、通用规则(`act`/`suc`/`msg_id`/60 秒超时)、指令清单、签名算法与自测向量、安全码自愈流程、带注释报文示例。
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
## 数据单位规范
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
详见 [docs/API.md](docs/API.md#数据单位规范) — 所有数值字段使用整数存储,禁止浮点类型。
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
> ✅ 浮点问题已清理完毕:所有业务字段使用 i64/BIGINT,SQL 查询使用 CAST(... AS SIGNED)。仅 Excel 导出的 `write_number` API 参数为 f64(`i64 as f64` 转换,非业务浮点)。
|
|
|
|
|
|
|
|
|
|
|
|
## 微信小程序用户端
|
|
|
|
|
|
|
|
|
|
|
|
### 小程序配置
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
| 参数 | 值 |
|
|
|
|
|
|
|------|-----|
|
|
|
|
|
|
| AppID | `wx226061c550c52e85` |
|
|
|
|
|
|
| AppSecret | `f11dfee9dc73265b75bf54277ebb1047` |
|
|
|
|
|
|
| 账号 | `13951516272@163.com` |
|
|
|
|
|
|
| 消息加解密方式 | 明文模式 |
|
|
|
|
|
|
| 数据格式 | XML |
|
|
|
|
|
|
| 服务器地址(URL) | `https://app.anzhizhichong.com/api/wechat/callback` |
|
|
|
|
|
|
| Token(令牌) | `7GW16XdnFyak0mE32rc8ZPij` |
|
|
|
|
|
|
| EncodingAESKey | `kRijggSqeCFWi3JhfGyK0nzc6VmdygWWkoqyKPl0bYu` |
|
|
|
|
|
|
|
|
|
|
|
|
> ⚠️ 以上密钥仅记录在内网 Git 仓库 `生产环境.txt`,不入代码仓库。
|
|
|
|
|
|
|
|
|
|
|
|
### 构建命令
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd software/h5-uniapp
|
|
|
|
|
|
npm run build:mp-weixin
|
|
|
|
|
|
# 产物在 dist/build/mp-weixin/,用微信开发者工具导入上传
|
|
|
|
|
|
```
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
### API 域名配置
|
|
|
|
|
|
|
|
|
|
|
|
小程序请求后端 API 必须配置合法域名(HTTPS):
|
|
|
|
|
|
|
|
|
|
|
|
- 后台「开发管理 → 开发设置 → 服务器域名」
|
|
|
|
|
|
- request 合法域名:`https://app.anzhizhichong.com`
|
|
|
|
|
|
- 开发阶段可在 `manifest.json` 设 `urlCheck: false` 跳过校验
|
|
|
|
|
|
|
|
|
|
|
|
### API base URL
|
|
|
|
|
|
|
|
|
|
|
|
小程序用全 URL(`api/index.ts` 条件编译):
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
|
|
|
const BASE_URL = 'https://app.anzhizhichong.com/api/pms'
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
// #ifndef MP-WEIXIN
|
|
|
|
|
|
const BASE_URL = '/api/pms'
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
```
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
### 信号强度显示(RSSI)
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
设备卡片右上角使用 Lucide 信号图标显示 RSSI 信号强度,PC 管理端和小程序共用同一套逻辑。
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
**RSSI 映射:**
|
2026-07-02 17:09:54 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
| RSSI 范围 | 图标 (Lucide) | 信号格 | 颜色 |
|
|
|
|
|
|
|-----------|--------------|--------|------|
|
|
|
|
|
|
| ≥ 70 | signal | 5 格(含 M22) | #16a34a 绿色 |
|
|
|
|
|
|
| ≥ 55 | signal-high | 4 格(含 M17) | #2563eb 蓝色 |
|
|
|
|
|
|
| ≥ 40 | signal-medium | 3 格(含 M12) | #f59e0b 黄色 |
|
|
|
|
|
|
| ≥ 30 | signal-low | 2 格(含 M7) | #f97316 橙色 |
|
|
|
|
|
|
| < 30 或 null | signal-zero | 1 格(仅 M2) | #9ca3af 灰色(离线) |
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-09-19 23:40:46 +08:00
|
|
|
|
**实现方式(小程序):**
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
```vue
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" ...>
|
|
|
|
|
|
<path v-if="(rssi ?? -1) >= 30" d="M7 20v-4"/>
|
|
|
|
|
|
<path v-if="(rssi ?? -1) >= 40" d="M12 20v-8"/>
|
|
|
|
|
|
<path v-if="(rssi ?? -1) >= 55" d="M17 20V8"/>
|
|
|
|
|
|
<path v-if="(rssi ?? -1) >= 70" d="M22 4v16"/>
|
|
|
|
|
|
<path d="M2 20h.01"/>
|
|
|
|
|
|
</svg>
|
2026-07-02 17:07:01 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
- `v-if` 按 RSSI 阈值动态显示信号格
|
|
|
|
|
|
- `<text>` 不支持 `display: flex`,统一用 `<view>` 包裹
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
**实现方式(PC 管理端):**
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
React 组件 `SignalIcon`,同上逻辑用条件渲染 `<path>`。
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
**后端 RSSI 获取:**
|
2026-07-02 17:09:54 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
设备列表接口通过 Redis pipeline 批量 HGET 查询 `device:{imei}` 的 `rssi` 字段:
|
2026-07-02 17:07:01 +08:00
|
|
|
|
```
|
2026-07-04 04:03:02 +08:00
|
|
|
|
HSET device:{imei} online 1 rssi 85
|
2026-07-02 17:07:01 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
**相关文件:**
|
|
|
|
|
|
- PC: `software/web/src/pages/devices/CabinetGrid.tsx` — `SignalIcon` 组件
|
2026-09-19 23:40:46 +08:00
|
|
|
|
- 小程序: `software/h5-uniapp/src/pages/home/index.vue` — signalColor 函数 + 模板
|
|
|
|
|
|
- 小程序 API: `software/api-server/src/routes/h5.rs` — `get_cabinets` 加 Redis pipeline
|
2026-07-04 04:03:02 +08:00
|
|
|
|
- 类型: `software/h5-uniapp/src/api/index.ts` — `CabinetItem.rssi`
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
### 近期 UI 改动
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
1. **设备卡片头部**:去掉"在线/离线"文字 badge,改为信号图标 + RSSI 数值
|
|
|
|
|
|
2. **通道统计**:图标+数字一行排列,图标用 `<svg>` 内联 Lucide(不加容器,flex 居中)
|
|
|
|
|
|
3. **右上角扫码**:scan-line 图标(Lucide),灰色 #6b7280,居中垂直对齐
|
2026-07-02 17:07:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
### 开发约束(uni-app 特定)
|
2026-07-02 05:38:01 +08:00
|
|
|
|
|
2026-07-04 04:03:02 +08:00
|
|
|
|
1. **`<text>` 不支持 `display: flex`** — uni-app 的 `<text>` 组件是纯文本组件,不接受 flex 布局。需要 flex 的地方用 `<view>`
|
|
|
|
|
|
2. **SVG 图标居中**:`.stat-chip svg { display: block; flex-shrink: 0 }` 用 CSS 统一控制尺寸,比内联属性更可靠
|
|
|
|
|
|
3. **内联 SVG + v-if**:Vue 3 支持在 `<path>` 上用 `v-if` 条件渲染,适用于动态图标
|
|
|
|
|
|
4. **CSS 覆盖 SVG 尺寸**:用 `.xxx svg { width: Xpx; height: Xpx }` 统一控制图标大小,不在每个 `<svg>` 上写死
|
2026-09-19 23:40:46 +08:00
|
|
|
|
|
|
|
|
|
|
## 代码审查豁免
|
|
|
|
|
|
|
|
|
|
|
|
以下模式已确认无需修改:
|
|
|
|
|
|
|
|
|
|
|
|
| 审查项 | 原因 |
|
|
|
|
|
|
|--------|------|
|
|
|
|
|
|
| TCP keepalive 配置 | 我们是 TCP server,设备才是 client。设备每 60s 发 status_post 就是应用层保活,不需要在 server 侧设 keepalive。 |
|
|
|
|
|
|
| SHA256 签名只取 8 位 | MCU 硬件协议固定,不可改 |
|
|
|
|
|
|
| AGENTS.md 明文密码 | 内网 Git 仓库,agent 需要直连 |
|
|
|
|
|
|
| 小程序 token 存 Storage | 小程序无 HttpOnly cookie,纯前端场景 |
|
|
|
|
|
|
| 8-char sign 长度 | 硬件约束,设备一次只能获取一次 auth_str |
|
|
|
|
|
|
| ServerResponse / DeviceCommand 含额外内部字段(`_dev_id`、`_close`) | `_dev_id` = 目标设备 IMEI,**供 device-server 路由指令**(协议 `id` 已不含 IMEI,只表示动作目标);`_close` 指示发送后断开连接(`login` 失败时)。两者**下发前均由 device-server 摘除**(`split_internal_flags`),设备侧不可见 |
|