# 架构说明

## 1. 系统架构总览

```
                        +-----------------+
                        |   腾讯云 COS    |
                        |  (头像/固件存储) |
                        +--------^--------+
                                 |
+------------------+    +--------+--------+    +------------------+
|                  |    |                  |    |                  |
|   微信小程序     +--->+   SCF 云函数     +<---+   管理后台       |
|   (用户端)       |    |   (Express)      |    |   (uni-app H5)   |
|                  +<---+                  +--->+                  |
+-------+----------+    +--------+--------+    +------------------+
        |                        |
        | BLE                    | mysql2
        v                        v
+-------+----------+    +--------+--------+
|   光子美容仪      |    |    MySQL        |
|   (蓝牙设备)      |    |   (腾讯云)      |
+------------------+    +-----------------+
```

三端关系：
- **小程序** 通过 HTTPS 调用后端 API，通过 BLE 连接硬件设备
- **管理后台** 通过同一套后端 API（`/api/v1/admin/*`）管理数据
- **后端** 部署为腾讯云 SCF 云函数，连接 MySQL 和 COS

## 2. 后端架构

### 2.1 目录结构

```
server/src/
├── index.js              # SCF 入口，导出 main_handler
├── app.js                # Express 应用，中间件 + 路由挂载
├── config.js             # 环境变量配置
├── middleware/
│   └── auth.js           # JWT 认证中间件
├── routes/
│   ├── auth.js           # 登录/刷新 token
│   ├── user.js           # 用户信息
│   ├── device.js         # 设备绑定/解绑/指令
│   ├── subscription.js   # 订阅计划/购买/试用
│   ├── treatment.js      # 护理记录同步/查询
│   ├── firmware.js       # 固件版本管理
│   └── admin.js          # 管理后台全部接口
├── dao/
│   ├── index.js          # barrel export
│   ├── user.dao.js       # users 表
│   ├── device.dao.js     # devices 表
│   ├── binding.dao.js    # device_bindings 表
│   ├── subscription.dao.js # subscriptions 表
│   ├── treatment.dao.js  # treatment_records 表
│   ├── command.dao.js    # device_commands 表
│   ├── device-event.dao.js # device_events 表
│   ├── admin.dao.js      # admin_accounts 表
│   ├── log.dao.js        # operation_logs 表
│   ├── settings.dao.js   # system_settings 表
│   └── firmware.dao.js   # firmware_versions 表
└── lib/
    ├── serverless.js     # SCF event → Express 适配器
    ├── db.js             # mysql2 连接池 (query/one/transaction)
    ├── auth.js           # JWT 签发/密码哈希/Bearer 读取
    ├── response.js       # 统一响应 { code, message, data }
    ├── settings-cache.js # system_settings 内存缓存 (TTL 60s)
    ├── wechat.js         # 微信 code2Session / 获取手机号
    ├── cos.js            # 腾讯云 COS 客户端
    └── utils.js          # 日期格式化工具
```

### 2.2 请求处理流程

```
SCF event
  │
  ▼
serverless.js        将 SCF event 转换为 http.IncomingMessage + ServerResponse
  │
  ▼
Express app(req, res)
  │
  ├── express.json()           解析 JSON body
  ├── CORS 中间件              设置跨域头，处理 OPTIONS
  ├── rate limiter             对 login/upload 路径限流
  ├── authMiddleware           提取 x-forwarded-for → req.ip
  │
  ├── /health                  健康检查
  ├── /api/v1/auth/*           → routes/auth.js
  ├── /api/v1/user/*           → routes/user.js       (requireUser)
  ├── /api/v1/device/*         → routes/device.js     (requireUser)
  ├── /api/v1/subscription/*   → routes/subscription.js
  ├── /api/v1/treatment/*      → routes/treatment.js  (requireUser)
  ├── /api/v1/admin/*          → routes/admin.js      (requireAdmin)
  ├── /api/v1/firmware/*       → routes/firmware.js
  │
  ├── 404 handler
  └── error handler            记录错误日志，返回 3001
```

### 2.3 认证机制

JWT 双密钥体系：

| 角色  | 签发函数    | 密钥               | payload 字段               |
|-------|-------------|---------------------|----------------------------|
| user  | signUser()  | config.jwt.secret      | type, user_id, openid      |
| admin | signAdmin() | config.jwt.adminSecret | type, admin_id, username, role |

中间件层级：
- `authMiddleware` — 全局，仅提取 IP，不验证 token
- `requireUser` — 路由级，验证 user token，查库确认用户存在且 status=1，挂载 `req.user`
- `requireAdmin` — 路由级，验证 admin token，查库确认管理员存在且 status=1，挂载 `req.admin`

token 刷新：客户端在 token 剩余 <24h 时自动调用 `/auth/refresh`，过期后有 1 天宽限期。

### 2.4 DAO 层

每个 DAO 文件只操作一张表，通过 `db.js` 提供的 `query/one/transaction` 与数据库交互：

| DAO 文件              | 负责表              | 核心操作                         |
|-----------------------|---------------------|----------------------------------|
| user.dao.js           | users               | CRUD、openid 查找、分页列表       |
| device.dao.js         | devices             | 设备列表、按用户绑定关系查询       |
| binding.dao.js        | device_bindings     | 创建/确认/取消绑定、活跃绑定查询   |
| subscription.dao.js   | subscriptions       | 试用创建、购买(extend)、查有效订阅  |
| treatment.dao.js      | treatment_records   | 创建记录、按用户分页、更新设备状态  |
| command.dao.js        | device_commands     | 创建指令、拉取待执行、标记完成      |
| device-event.dao.js   | device_events       | 设备异常事件记录                   |
| admin.dao.js          | admin_accounts      | 管理员查找/验证                    |
| log.dao.js            | operation_logs      | 操作日志写入与分页查询             |
| settings.dao.js       | system_settings     | 全量读取系统配置                   |
| firmware.dao.js       | firmware_versions   | 固件版本列表/最新版查询            |

### 2.5 工具库

| 文件              | 职责                                                        |
|-------------------|-------------------------------------------------------------|
| response.js       | `ok(data)` / `fail(code, msg)` 统一响应格式                  |
| settings-cache.js | system_settings 内存缓存，TTL 60秒，`invalidateCache()` 手动失效 |
| wechat.js         | 微信登录 `code2Session`、获取手机号 `getPhoneNumber`          |
| cos.js            | 腾讯云 COS 签名 URL 生成                                     |
| utils.js          | `toMysqlDate()` 日期格式化（UTC+8）                          |
| wxpay.js          | (规划中) 微信支付下单、回调签名验证                            |

## 3. 小程序架构

### 3.1 目录结构

```
miniprogram/
├── app.js / app.json / app.wxss    # 应用入口
├── config/
│   └── env.js                      # API_BASE 配置
├── pages/                          # 18 个页面
│   ├── login/                      # 微信登录
│   ├── register/                   # 补充信息
│   ├── index/                      # 首页 (tab)
│   ├── scan/                       # 扫码绑定
│   ├── auto-scan/                  # 自动扫描蓝牙设备
│   ├── ble-connect/                # BLE 连接
│   ├── bind-success/               # 绑定成功
│   ├── subscribe-prompt/           # 订阅提示
│   ├── subscribe-plans/            # 选择计划
│   ├── subscribe-success/          # 订阅成功
│   ├── wear-check/                 # 佩戴检测
│   ├── treatment-setup/            # 护理参数设置
│   ├── treating/                   # 护理进行中
│   ├── treatment-done/             # 护理完成
│   ├── history/                    # 护理记录 (tab)
│   ├── profile/                    # 我的 (tab)
│   ├── help/                       # 帮助
│   └── contact/                    # 联系我们
├── services/
│   ├── ble.js                      # proxy → ble/index.js
│   ├── ble/                        # BLE 模块（见 3.3）
│   └── command-sync.js             # 远程指令拉取与执行
└── utils/
    ├── request.js                  # HTTP 请求封装
    └── api.js                      # 命名 API 函数
```

### 3.2 页面导航流程

完整用户旅程（从登录到护理完成）：

```
login ──→ register(新用户) ──→ index(首页)
                                  │
                          ┌───────┴──────┐
                          ▼              ▼
                       scan          auto-scan
                     (扫码绑定)    (自动扫描BLE)
                          │              │
                          └──────┬───────┘
                                 ▼
                           ble-connect
                          (蓝牙连接设备)
                                 │
                                 ▼
                           bind-success
                          (绑定成功)
                                 │
                          ┌──────┴──────┐
                          ▼             ▼
                   subscribe-prompt  (已订阅则跳过)
                   (提示订阅)
                          │
                          ▼
                   subscribe-plans
                   (选择计划)
                          │
                          ▼
                   subscribe-success ──→ wear-check
                                        (佩戴检测)
                                             │
                                             ▼
                                       treatment-setup
                                       (设置护理参数)
                                             │
                                             ▼
                                         treating
                                        (护理中)
                                             │
                                             ▼
                                       treatment-done
                                       (护理完成,同步记录)
```

底部 Tab 页：首页(index) | 记录(history) | 我的(profile)

### 3.3 BLE 模块拆分

```
services/ble.js          # proxy，直接 re-export ble/index.js
    │
services/ble/
    ├── index.js          # barrel，统一导出所有 API
    ├── protocol.js       # 协议常量 + 帧编解码
    ├── connection.js     # 扫描/连接/断开/自动重连
    └── commands.js       # 指令发送 + ACK 管理
```

**协议模式**：`PROTOCOL_MODE = 'vendor_33'`，使用自定义 BLE 服务（FFE0/FFE1/FFE2）而非标准 GATT profile。

**关键设计决策**：
- connection.js 维护共享状态（`_deviceId`, `_connected`, `_chars`），commands.js 通过 getter 访问
- 事件系统在 connection.js 中实现（`on/off/emit`），心跳和状态通知作为独立事件分发
- command-sync.js 负责从服务器拉取待执行指令，逐条执行后上报结果

### 3.4 API 调用层

```
页面代码
  │
  │  var api = require('../../utils/api')
  │  api.getProfile()
  │
  ▼
api.js            命名函数，封装路径和参数
  │
  │  http.get('/api/v1/user/profile')
  │
  ▼
request.js        统一封装 wx.request
  │
  ├── 自动附加 Authorization / X-App-Version / X-Platform
  ├── token 过期前 <24h 自动刷新
  ├── code 1001/1002 → 清除 token → reLaunch 到登录页
  └── 统一错误格式 { code, message }
```

## 4. 管理后台架构

### 4.1 SPA 架构

基于 uni-app (Vue 2) 构建的 H5 单页应用。不使用 vue-router，而是通过动态组件实现页面切换：

```
AdminLayout                  固定侧边栏 + 顶栏
  └── <keep-alive>
        └── <component :is="currentComponent">    动态视图
```

### 4.2 VIEW_MAP 与 KEEP_ALIVE_VIEWS

`pages/admin/index.vue` 中定义了两个核心映射：

```js
VIEW_MAP = {
  dashboard:      DashboardView,
  device:         DeviceListView,
  'device-detail': DeviceDetailView,
  user:           UserListView,
  'user-detail':  UserDetailView,
  subscription:   SubscriptionView,
  record:         RecordView,
  log:            LogView,
  settings:       SettingsView
}

KEEP_ALIVE_VIEWS = [
  'DeviceListView', 'UserListView',
  'SubscriptionView', 'RecordView', 'LogView'
]
```

列表页被 keep-alive 缓存，从详情页返回时保留滚动位置和筛选状态。详情页（device-detail, user-detail）通过 `viewKey` 携带 ID，保证每次进入重新挂载。

导航通过 `$emit('navigate', viewName, props)` 冒泡到 index.vue，由 `onNavigate` 更新 `currentView` 和 `viewProps`。

### 4.3 API 调用模式

```
View 组件
  │
  │  import { get, post } from '../utils/request'
  │  get('/api/v1/admin/users')
  │
  ▼
utils/request.js      基于 uni.request 封装
  │
  ├── 自动附加 admin_token (Bearer)
  ├── code 1001/1002 → 清除 token → reLaunch 到登录页
  └── 统一 resolve(data) / reject(error)
```

状态管理：Pinia store (`store/user.js`) 管理 admin token 和登录信息。

### 4.4 构建和部署

- 框架：uni-app，编译目标 H5
- 部署方式：构建产物上传至腾讯云 COS 静态托管，或直接部署到 SCF
- 菜单项：仪表盘、设备管理、用户管理、订阅管理、护理记录、操作日志、系统设置

## 5. 数据流

### 5.1 支付流程

```
用户点击购买                       后端                        微信支付
     │                              │                            │
     │  api.purchase(plan)          │                            │
     ├─────────────────────────────>│                            │
     │                              │  (当前: 返回 order_id)     │
     │  { order_id, payment_params }│                            │
     │<─────────────────────────────│                            │
     │                              │                            │
     │  === 正式支付流程(规划中) ====│                            │
     │  wx.requestPayment(params)   │                            │
     │  ────────────────────────────┼───────────────────────────>│
     │                              │    支付回调 (notify)        │
     │                              │<───────────────────────────│
     │                              │  验签 → 更新 subscription  │
     │                              │  subscriptionDao.purchase() │
     │                              │    (extend 模式，叠加天数)  │
     │                              │                            │
     │  === 开发环境替代方案 ========│                            │
     │  api.mockPurchase(plan)      │                            │
     ├─────────────────────────────>│                            │
     │                              │  直接激活订阅               │
     │  { status: 'active' }        │                            │
     │<─────────────────────────────│                            │
```

订阅模型：`subscriptionDao.purchase()` 采用 extend 模式 —— 如已有有效订阅，在现有到期日基础上叠加天数，而非覆盖。

### 5.2 护理记录同步流程

```
treating 页面                    BLE 设备                    后端
     │                              │                         │
     │  ble.setParams({...})        │                         │
     ├─────────────────────────────>│                         │
     │            ACK               │                         │
     │<─────────────────────────────│                         │
     │  ble.startTreatment()        │                         │
     ├─────────────────────────────>│                         │
     │       status notify (周期)   │                         │
     │<─────────────────────────────│                         │
     │       ...护理进行中...        │                         │
     │  ble.stopTreatment()         │                         │
     ├─────────────────────────────>│                         │
     │  treatment_complete notify   │                         │
     │<─────────────────────────────│                         │
     │                              │                         │
     │  跳转 treatment-done 页面                              │
     │                                                        │
     │  api.syncTreatment({                                   │
     │    device_id, session_id,                              │
     │    start_time, end_time,                               │
     │    regions, duration_ms,                               │
     │    mode, wavelength,                                   │
     │    battery, temperature,                               │
     │    pd_values ...                                       │
     │  })                                                    │
     ├───────────────────────────────────────────────────────>│
     │                                treatmentDao.create()   │
     │                                treatmentDao.           │
     │                                  updateDevice()        │
     │  { record_id }                                         │
     │<───────────────────────────────────────────────────────│
```

### 5.3 设备绑定流程

```
用户                         小程序                        后端
 │                             │                            │
 │  扫码/自动扫描获取 device_id│                            │
 │─────────────────────────────│                            │
 │                             │  api.bindDevice(deviceId)  │
 │                             ├───────────────────────────>│
 │                             │                            │  检查：用户未绑定其他设备
 │                             │                            │  检查：设备存在于 devices 表
 │                             │                            │  取消旧的 pending 绑定
 │                             │                            │  创建 pending 绑定 + bind_token
 │                             │  { device_id, bind_token } │
 │                             │<───────────────────────────│
 │                             │                            │
 │                             │  BLE 连接设备               │
 │                             │  ble.connect(deviceId)      │
 │                             │  ble.bindDevice(bind_token) │
 │                             │        ──BLE──>  设备       │
 │                             │        <──ACK──  设备       │
 │                             │                            │
 │                             │  api.confirmBind(           │
 │                             │    deviceId, bind_token)    │
 │                             ├───────────────────────────>│
 │                             │                            │  bindingDao.confirmBind()
 │                             │                            │  pending → active
 │                             │  { message: 'success' }    │
 │                             │<───────────────────────────│
 │                             │                            │
 │  跳转 bind-success 页面     │                            │
 │<────────────────────────────│                            │
```

绑定约束：一个用户同时只能绑定一台设备；绑定前需先通过 BLE 与设备完成配对确认。
