# 架构说明 ## 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 固定侧边栏 + 顶栏 └── └── 动态视图 ``` ### 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 与设备完成配对确认。