Miniprogram: - Add back buttons to all custom-nav pages - Add debug/mock buttons (BLE connect, wear check, treatment) controlled by __DEV__ flag (hidden in prod) Admin console: - Fix log page using nonexistent fields (operator_type, target_type) now correctly reads admin_id/user_id/action/detail from API - Fix subscription date fields (started_at→start_time, expired_at→expire_time) - Wire up subscription detail/extend/renew action buttons - Wire up device detail "查看完整日志" button - Add "创建订阅" button to subscription toolbar - Fix subscription status mapping (2=expired, 3=cancelled) Docs: - Add detailed architecture docs for server, miniprogram, admin console
15 KiB
15 KiB
小程序架构
概述
微信原生小程序(非 uni-app),使用 WXML + WXSS + JS 开发。通过 BLE 5.0 与光子美容仪设备通信,通过 HTTP 与腾讯云 SCF 后端通信。
AppID: wxc4045074ef298510
项目名: hox-beauty
基础库: 3.15.2
目录结构
miniprogram/
├── app.js / app.json / app.wxss # 应用入口
├── config/
│ └── env.js # 环境配置(API 地址)
├── pages/
│ ├── index/ # 首页(Tab)—— 设备状态、开始护理
│ ├── history/ # 记录(Tab)—— 护理历史列表
│ ├── profile/ # 我的(Tab)—— 个人中心
│ ├── login/ # 微信登录
│ ├── scan/ # 扫码绑定设备
│ ├── ble-connect/ # 蓝牙连接 + BLE 绑定
│ ├── bind-success/ # 绑定成功
│ ├── wear-check/ # 佩戴检测
│ ├── treatment-setup/# 护理模式选择
│ ├── auto-scan/ # 智能模式面部扫描
│ ├── treating/ # 护理进行中
│ ├── treatment-done/ # 护理完成
│ ├── subscribe-prompt/ # 订阅引导
│ ├── subscribe-plans/ # 订阅套餐选择
│ └── subscribe-success/ # 订阅成功
├── services/
│ ├── ble.js # BLE 通信协议核心
│ └── command-sync.js # 远程指令拉取与执行
├── utils/
│ ├── request.js # HTTP 请求封装 + token 刷新
│ └── mock.js # 开发用 mock(已禁用)
├── scripts/
│ └── test-ble-frame.js # BLE 帧构建/解析单元测试
├── project.config.json
└── sitemap.json
共 15 个页面,无自定义组件,全部使用微信原生组件。
应用生命周期
app.js
globalData:
| 字段 | 用途 |
|---|---|
userInfo |
用户信息对象(来自 /api/v1/user/profile) |
userId |
用户 ID 字符串 |
connectedDevice / currentDevice |
当前绑定的设备信息 |
currentTreatment |
最近一次护理结果(跨页传递) |
statusBarHeight |
系统状态栏高度(默认 44) |
onLaunch 流程:
wx.getSystemInfoSync()→ 读取statusBarHeightcheckLogin()→ 从 Storage 读取 token- 有 token → 调用
loadProfile()加载用户信息 - 无 token → 设置
userInfo = null(各页面自行跳转登录)
- 有 token → 调用
页面导航流程
主要业务路径
┌─────────┐
│ login │ ← 无 token 时跳转
└────┬────┘
│ wx.switchTab
▼
┌──────────────────────┐
│ index(首页 Tab) │
│ 设备状态 / 订阅状态 │
└──────┬───────────────┘
│
┌──────────┼──────────┐
│ 无设备 │ 有设备 │ 有设备但无订阅
▼ ▼ ▼
┌──────┐ ┌────────┐ ┌─────────────────┐
│ scan │ │ wear- │ │subscribe-prompt │
│ 扫码 │ │ check │ │ 订阅引导 │
└──┬───┘ └───┬────┘ └───────┬─────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌─────────────────┐
│ble-connect│ │treatment- │ │subscribe-plans │
│ 蓝牙配对 │ │setup 模式选择│ │ 套餐选择 │
└─────┬─────┘ └──────┬───────┘ └─────────────────┘
│ │
▼ ┌────┴────┐
┌───────────┐ │ │
│bind- │ 普通模式 智能模式
│success │ │ │
└───────────┘ │ ┌────┴─────┐
│ │auto-scan │
│ │ 面部扫描 │
│ └────┬─────┘
│ │
▼ ▼
┌──────────────────┐
│ treating │
│ 护理进行中 │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ treatment-done │
│ 护理完成 │
└──────────────────┘
Tab 页
| Tab | 页面 | 说明 |
|---|---|---|
| 首页 | index | 设备连接状态、开始护理入口 |
| 记录 | history | 护理历史列表(分页、下拉刷新) |
| 我的 | profile | 个人信息、订阅状态、设置 |
导航方式
- Tab 间切换:
wx.switchTab - 业务流程前进:
wx.navigateTo(压栈)或wx.redirectTo(替换) - 强制跳转:
wx.reLaunch(清空页面栈,用于登录跳转)
页面详情
login — 微信登录
- 调用
wx.getUserProfile()获取头像昵称(2022.10 后始终返回「微信用户」和灰色头像) - 调用
app.doLogin()→wx.login()获取 code →POST /api/v1/auth/login - 登录后保存
token和token_expiry到 Storage - 成功后
wx.switchTab到首页
index — 首页
GET /api/v1/device/list获取绑定设备列表GET /api/v1/subscription获取订阅状态- BLE:订阅
ble.on('status')更新电量和设备状态 - 连接设备后自动调用
ble.queryStatus() - 解绑操作:
ble.disconnect()+POST /api/v1/device/unbind
scan — 扫码绑定
wx.scanCode({ onlyFromCamera: true, scanType: ['qrCode'] })扫描设备二维码- 支持手动输入 16 位 hex 设备号
POST /api/v1/device/bind→ 获取bind_token- 跳转到
ble-connect页面
ble-connect — 蓝牙连接
- 状态机:
scanning → connecting → binding → done / error - BLE 扫描过滤名称含
HOX或LIGHTMASK的设备 - 连接后发送 BIND 命令,等待
bind_result通知 - 成功后
POST /api/v1/device/bind/confirm
wear-check — 佩戴检测
- 通过
ble.queryStatus()检查设备bind_status === 1 - 确认佩戴后进入模式选择
treatment-setup — 模式选择
- 普通模式(mode=0):全脸、10 分钟固定
- 智能模式(mode=1):需要有效订阅,可选区域
- 区域位掩码:左脸(0x01) 右脸(0x02) 额头(0x04) 下巴(0x08) 鼻部(0x10)
- 普通模式直接发
setParams+startTreatment开始护理 - 智能模式先跳到 auto-scan 扫描
treating — 护理中
- 订阅 6 个 BLE 事件:
status,treatment_complete,exception,disconnected,reconnected,reconnect_failed - 本地 1 秒定时器作为倒计时后备
- 接收设备
status报告校正剩余时间 - 每次 status 更新触发
commandSync.sync(deviceId)拉取远程指令 - 断连时暂停计时器,重连后恢复
- 护理结束触发条件:设备完成通知 / 本地定时器归零 / 手动停止 / 重连失败
treatment-done — 护理完成
POST /api/v1/treatment/sync同步记录到服务器- 展示护理时长、区域、模式等摘要
history — 护理记录
GET /api/v1/treatment/history?page=N&page_size=20分页加载- 支持下拉刷新和触底加载
- 累计统计:总次数、总时长、本月次数
profile — 我的
GET /api/v1/user/profile+GET /api/v1/subscription- 菜单入口:设备管理、护理记录、订阅管理、使用帮助、联系我们
- 退出登录:清除 token →
wx.reLaunch到 login
BLE 通信协议
GATT 服务和特征值
| 服务 UUID | 名称 | 特征值 |
|---|---|---|
| FFE0 | 设备信息服务 | FFE3 (设备信息 - 读) |
| FFE1 | 数据通信服务 | FFE4 (命令 - 写), FFE5 (状态 - 通知), FFE6 (绑定信息) |
| FFE2 | OTA 服务 | FFE7 (OTA控制), FFE8 (OTA数据), FFE9 (OTA状态) |
帧格式
┌──────┬──────┬────────┬──────┬─────────────────┬──────────┐
│ 0xAA │ 0x55 │ Length │ Type │ Payload │ Checksum │
│ 1B │ 1B │ 1B │ 1B │ Length bytes │ 1B (XOR) │
└──────┴──────┴────────┴──────┴─────────────────┴──────────┘
- Header:固定
0xAA 0x55 - Length:Payload 字节数
- Type:命令/通知类型
- Checksum:前面所有字节的 XOR 值
命令类型(小程序 → 设备)
| 类型 | 值 | Payload |
|---|---|---|
| SET_PARAMS | 0x01 | region_mask, wavelength, brightness, duration_ms(4B大端), mode, seq |
| START | 0x02 | region_mask, seq |
| STOP | 0x03 | seq |
| QUERY_STATUS | 0x04 | seq |
| BIND | 0x05 | 0x01, userId(8B), bindToken(8B), timestamp(4B大端), seq |
| UNBIND | 0x06 | 0x02, userId(8B), seq |
每条命令的 payload 末尾都有 seq 字节(0-255 循环),用于匹配 ACK 响应。
通知类型(设备 → 小程序)
| 类型 | 值 | Payload |
|---|---|---|
| STATUS_REPORT | 0x21 | mode_state, region_mask, wavelength, brightness, remaining_ms(4B), error_code, command_seq, battery, temperature, bind_status, subscription — 共 14 字节 |
| ACK | 0x22 | seq, error_code |
| TREATMENT_COMPLETE | 0x31 | session_id(8B), regions, total_duration_ms(4B), avg_pd |
| EXCEPTION | 0x32 | error_code, temperature |
| BIND_SUCCESS | 0x33 | result (0x00=成功) |
命令重试机制
writeCommandWithRetry(type, payload, maxRetries=3):
- 调用
writeCommand发送命令 - 失败时等待 500ms 后重试
- 最多重试 3 次
- 如果错误码为
0x0C(BLE 断连),立即失败不重试 - ACK 超时时间:5 秒
自动重连机制
- 检测到 BLE 断连时自动触发
- 等待 2 秒后第一次重连尝试
- 最多 3 次,间隔 2 秒
- 重连成功后重新发现服务、重新订阅特征值通知
- 手动调用
disconnect()时禁用自动重连
设备状态机
| 值 | 状态 | 说明 |
|---|---|---|
| 0x00 | IDLE | 空闲 |
| 0x01 | SCANNING | 面部扫描中 |
| 0x02 | ACTIVE | 护理中 |
| 0x03 | PAUSED | 已暂停 |
| 0x04 | COMPLETED | 已完成 |
| 0x05 | ERROR | 异常 |
| 0x06 | OTA | 固件升级中 |
区域位掩码
| 位 | 值 | 区域 |
|---|---|---|
| 0 | 0x01 | 左脸颊 |
| 1 | 0x02 | 右脸颊 |
| 2 | 0x04 | 额头 |
| 3 | 0x08 | 下巴 |
| 4 | 0x10 | 鼻部 |
| 5 | 0x20 | 左眼周 |
| 6 | 0x40 | 右眼周 |
| 全部 | 0x7F | 全脸 |
设备错误码
| 码 | 名称 | 说明 |
|---|---|---|
| 0x00 | SUCCESS | 成功 |
| 0x01 | ERR_REGION_INVALID | 区域无效 |
| 0x02 | ERR_REGION_EMPTY | 区域为空 |
| 0x03 | ERR_BRIGHTNESS_INVALID | 亮度无效 |
| 0x04 | ERR_DURATION_INVALID | 时长无效 |
| 0x05 | ERR_NOT_BOUND | 未绑定 |
| 0x06 | ERR_NO_SUBSCRIPTION | 无订阅 |
| 0x07 | ERR_TEMP_HIGH | 温度过高 |
| 0x08 | ERR_BATTERY_LOW | 电量不足 |
| 0x09 | ERR_ALREADY_RUNNING | 已在运行 |
| 0x0A | ERR_NOT_RUNNING | 未在运行 |
| 0x0B | ERR_OTA_FAILED | OTA 失败 |
| 0x0C | ERR_BLE_DISCONNECTED | 蓝牙断连 |
远程指令同步(command-sync.js)
服务端可以通过管理后台向设备下发远程指令,小程序在护理过程中定期拉取并执行:
commandSync.sync(deviceId)
│
├── GET /api/v1/device/command/pending?device_id=xxx
│ └── 返回待执行命令列表(status=1 → 标记为 status=2)
│
├── 逐条执行 BLE 命令(按 opcode 分发到对应函数)
│
└── POST /api/v1/device/command/result
└── 上报执行结果(成功/失败)
HTTP 请求层(request.js)
Token 自动刷新
- 每次请求前检查
token_expiry:如果距离过期不到 24 小时,先调用POST /api/v1/auth/refresh刷新 - 并发请求的刷新去重:使用
_refreshing标志和_refreshQueue队列,保证同一时间只有一个刷新请求 - 刷新成功后更新 Storage 中的
token和token_expiry
鉴权失败处理
- 响应 code 为 1001 或 1002 时:清除 token →
wx.reLaunch到 login 页
请求头
Authorization: Bearer <token>
Content-Type: application/json
X-App-Version: 1.0.0
X-Platform: wechat
环境配置
| 环境 | API 地址 |
|---|---|
| local | http://localhost:3000 |
| test | https://1426323813-ilxkhlxf4p.ap-guangzhou.tencentscf.com |
| prod | 待配置(发布阻断项) |
当前设置:ENV = 'test'
UI 模式
自定义导航栏
所有非 Tab 页使用 "navigationStyle": "custom",通过 statusBarHeight + 24px 的 padding-top 避开系统状态栏:
<view class="page-header" style="padding-top: {{statusBarHeight + 24}}px;">
品牌色
| 用途 | 色值 |
|---|---|
| 主色 | #E6508C(粉色) |
| 辅色 | #DCB982(金色) |
| 成功 | #52c41a(绿色) |
| 信息 | #006699(蓝色) |
| 错误 | #ff4d4f(红色) |
全局 CSS 类
定义在 app.wxss,包括:.page-header(带颜色变体)、.btn-primary(带颜色变体)、.btn-secondary、.status-badge、.device-card、.tip-card、工具类(.mt-20, .mb-30, .text-muted 等)。
数据流汇总
登录 → 绑定 → 护理 → 同步
1. 登录
wx.login() → code → POST /auth/login → token + user_info → Storage
2. 绑定设备
扫码/手动输入 → device_id → POST /device/bind → bind_token
→ BLE 扫描 + 连接 → BIND 命令(0x05) → 等待 BIND_SUCCESS(0x33)
→ POST /device/bind/confirm → 自动创建 7 天试用订阅
3. 护理
佩戴检测 → 模式选择 → SET_PARAMS(0x01) → START(0x02)
→ 护理中(实时 STATUS 更新 + 指令同步)
→ 完成(TREATMENT_COMPLETE 通知或本地定时器归零)
4. 记录同步
POST /treatment/sync { session_id, device_id, regions, duration, mode, ... }