docs(mp): 小程序安全优先全面改进路线图设计规格
四专家组五维度深度分析(架构7.25/安全6.0/UX7.4/工程6.2), 综合 6.7/10 → 目标 8.5/10。安全优先策略,4 Phase / 8 周, 覆盖 XOR→AES 替换、ARIA 可访问性、测试覆盖提升、Canvas 适老化。
This commit is contained in:
@@ -0,0 +1,615 @@
|
||||
# 小程序安全优先全面改进路线图
|
||||
|
||||
> 日期: 2026-05-21 | 策略: 安全优先 | 综合: 6.7/10 (B) → 目标: 8.5/10 (A-)
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 背景与动机](#1-背景与动机)
|
||||
- [2. 五维度分析摘要](#2-五维度分析摘要)
|
||||
- [3. 总体架构与 Phase 规划](#3-总体架构与-phase-规划)
|
||||
- [4. Phase 0:安全 P0 + 工程基础(周 1-2)](#4-phase-0安全-p0--工程基础周-1-2)
|
||||
- [5. Phase 1:测试覆盖 + UX 合规(周 3-4)](#5-phase-1测试覆盖--ux-合规周-3-4)
|
||||
- [6. Phase 2:加密替换 + Canvas 适老(周 5-6)](#6-phase-2加密替换--canvas-适老周-5-6)
|
||||
- [7. Phase 3:全面提升 + CI(周 7-8)](#7-phase-3全面提升--ci周-7-8)
|
||||
- [8. 医疗合规特殊要求](#8-医疗合规特殊要求)
|
||||
- [9. 风险评估与缓解](#9-风险评估与缓解)
|
||||
- [10. 验收标准](#10-验收标准)
|
||||
- [11. 技术选型](#11-技术选型)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与动机
|
||||
|
||||
### 1.1 为什么现在做
|
||||
|
||||
2026-05-21 四专家组(安全/架构/UX/工程)对小程序进行了五维度深度分析,综合评分 6.7/10 (B)。发现 3 个 CRITICAL、4 个 HIGH、6 个 MEDIUM 级别问题,其中安全评分仅 6.0/10 (C+),可访问性评分 2/10,测试覆盖率 ~6%。这些问题如果不解决,将阻碍产品进入医疗市场。
|
||||
|
||||
### 1.2 为什么选安全优先策略
|
||||
|
||||
三个候选策略对比:
|
||||
|
||||
| 策略 | 优势 | 风险 | 总工时 |
|
||||
|------|------|------|--------|
|
||||
| 安全优先(选定) | 先解决阻塞项,交付物可审计,风险低 | 工程改进可能延后 | 8-10 周 |
|
||||
| 工程基础优先 | 基础先扎实,后续开发效率高 | 安全漏洞暴露窗口长 | 10-12 周 |
|
||||
| 分层并行 | 节奏快 | 资源需求大,协调复杂 | 12-16 周 |
|
||||
|
||||
安全优先策略确保产品在合规审查前完成所有安全阻塞项。
|
||||
|
||||
### 1.3 分析数据来源
|
||||
|
||||
- 168 个 TS/TSX 源文件 / 18,558 行代码 / 102 个 SCSS 文件
|
||||
- 42 个 Service 文件 / 4 个 Store / 10 个 Hook
|
||||
- 61 个页面(15 主包 + 46 分包)
|
||||
- 9 个单元测试文件 / 4 个 E2E spec
|
||||
- 50+ 个与小程序相关的 git 提交
|
||||
|
||||
---
|
||||
|
||||
## 2. 五维度分析摘要
|
||||
|
||||
### 2.1 架构设计 — 7.25/10 (B+)
|
||||
|
||||
**优势:**
|
||||
- 四层架构清晰(Service → Store → Hook → Page)
|
||||
- BLE 子系统 adapter + manager + buffer + scheduler 拆分堪称模板
|
||||
- 内存泄漏防护到位(generation counter / AbortController / BLE 监听器清理)
|
||||
- Zustand 使用规范,缓存策略三层(request/store/service)
|
||||
|
||||
**问题:**
|
||||
- `noImplicitAny: false` 是 TypeScript 安全最大短板
|
||||
- health store 直接耦合 auth store
|
||||
- ErrorBoundary 功能有限(无分类/无上报)
|
||||
|
||||
### 2.2 安全 — 6.0/10 (C+)
|
||||
|
||||
**CRITICAL:**
|
||||
- XOR 加密形同虚设 — 密钥硬编码在 bundle 中(`'hms-default-key'`)
|
||||
- .env.production 包含硬编码真实 Tenant ID
|
||||
- console.warn 生产环境泄露认证错误详情
|
||||
|
||||
**HIGH:**
|
||||
- BLE 健康数据明文存储(DataBuffer.ts,最多 10000 条生理数据)
|
||||
- 健康阈值/Analytics 队列明文存储
|
||||
- 生产配置占位符(`api.hms.example.com`)
|
||||
|
||||
### 2.3 UX/产品 — 7.4/10 (B+)
|
||||
|
||||
**优势:**
|
||||
- Design Token 体系业界上乘(9/10),11 级字号 + 12 结构 Token
|
||||
- 关怀模式 8.5/10,58/58 页面覆盖,CSS 变量级联
|
||||
- 错误状态组件覆盖完整(ErrorBoundary/ErrorState/EmptyState/Loading)
|
||||
|
||||
**问题:**
|
||||
- 可访问性 2/10 — 整个 0 ARIA 属性,医疗应用合规风险
|
||||
- TrendChart Canvas 未适老化(硬编码 10px 字号)
|
||||
- 医生端 reLaunch 状态丢失(搜索/滚动位置/表单草稿全清空)
|
||||
|
||||
### 2.4 工程质量 — 6.2/10 (B-)
|
||||
|
||||
**优势:**
|
||||
- 请求层设计优秀(ResponseCache + ConcurrencyLimiter + Token 刷新去重)
|
||||
- 组件库完备(35 个组件分 ui/patterns/独立三类)
|
||||
- 生产构建 console.log 全移除
|
||||
|
||||
**问题:**
|
||||
- 测试覆盖率 ~6%(9 个测试文件/168 源文件)
|
||||
- 无 ESLint/Prettier 配置
|
||||
- 38 处 `any` 类型使用
|
||||
- 构建未做分包级代码分割
|
||||
|
||||
---
|
||||
|
||||
## 3. 总体架构与 Phase 规划
|
||||
|
||||
### 3.1 总工时
|
||||
|
||||
| 领域 | Phase 0 | Phase 1 | Phase 2 | Phase 3 | 合计 |
|
||||
|------|---------|---------|---------|---------|------|
|
||||
| 安全 | 7.5d | 1d(consent 拦截) | 5d | 1.5d | 15d |
|
||||
| 工程 | 4.5d | 7.5d | 2d(Token 生成脚本) | 4d | 18d |
|
||||
| UX | — | 4.5d | 4d | 1.5d | 10d |
|
||||
| **Phase 合计** | **12d** | **13d** | **11d** | **7d** | **43d** |
|
||||
|
||||
注:部分任务可并行执行(安全 + 工程 + UX 不同人员),实际日历时间约 8 周。
|
||||
|
||||
### 3.2 依赖关系图
|
||||
|
||||
```
|
||||
Phase 0(周 1-2)
|
||||
├── TypeScript strict ──────────┐
|
||||
├── ESLint 工具链 ──────────────┤
|
||||
├── secure-storage 接口抽象 ────┤
|
||||
├── XOR → AES 替换 ────────────┤
|
||||
└── 安全 P0 修复 ───────────────┤
|
||||
↓
|
||||
Phase 1(周 3-4) │
|
||||
├── secure-storage 测试 ────────┤
|
||||
├── request.ts 测试 ────────────┤
|
||||
├── auth store 测试 ────────────┤
|
||||
└── ARIA 可访问性基础 ──────────┤
|
||||
↓
|
||||
Phase 2(周 5-6) │
|
||||
├── Analytics/阈值加密 ─────────┤
|
||||
├── Patient DTO 最小化 ─────────┤
|
||||
├── Canvas 适老化 ──────────────┤
|
||||
└── 表单跳焦 + Loading 统一 ───┤
|
||||
↓
|
||||
Phase 3(周 7-8) │
|
||||
├── API 请求签名 ──────────────┤
|
||||
├── any 类型清零 ──────────────┤
|
||||
├── 构建优化 ──────────────────┤
|
||||
└── CI 集成 ───────────────────┘
|
||||
```
|
||||
|
||||
### 3.3 关键里程碑
|
||||
|
||||
| 里程碑 | 时间 | 交付物 |
|
||||
|--------|------|--------|
|
||||
| M0 | 第 2 周末 | 安全 CRITICAL 全部修复、TypeScript strict 开启、ESLint 配置生效 |
|
||||
| M1 | 第 4 周末 | 核心模块测试覆盖 ≥80%、ARIA 基础框架完成 |
|
||||
| M2 | 第 6 周末 | AES 加密替换完成、Canvas 适老化完成 |
|
||||
| M3 | 第 8 周末 | any 类型清零、CI 流水线运行、综合评分 ≥8.5 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase 0:安全 P0 + 工程基础(周 1-2)
|
||||
|
||||
### 4.1 安全 P0 修复
|
||||
|
||||
#### S0-1: 替换 XOR 为 AES-256-GCM 加密存储 [CRITICAL, 3d]
|
||||
|
||||
**现状:** `secure-storage.ts` 使用 XOR 编码,密钥 `'hms-default-key'` 硬编码。`.env` 中 `TARO_APP_ENCRYPTION_KEY=` 为空值,生产 fallback 到硬编码密钥。
|
||||
|
||||
**方案:**
|
||||
1. 安装 `@noble/ciphers`(纯 JS AES-GCM,无 Web Crypto 依赖)
|
||||
2. 后端 login/refresh 响应新增 `storage_key` 字段(256-bit,与 access_token 同生命周期)
|
||||
3. 小程序侧 `storage_key` 仅存内存变量,不持久化
|
||||
4. 实现 `AesSecureStorage`:`crypto.getRandomValues` 生成 IV → AES-GCM 加密 → Base64 编码 → Storage
|
||||
5. 冷启动无 `storage_key` 时,`secureGet` 返回空,用户需重新登录
|
||||
|
||||
**影响:** 冷启动需重新登录,UX 略降。缓解:refresh token 有效期内自动恢复。
|
||||
|
||||
#### S0-2: 移除生产硬编码 Tenant ID [CRITICAL, 1d]
|
||||
|
||||
**方案:**
|
||||
1. `.env.production` 中 `TARO_APP_DEFAULT_TENANT_ID` 设为空字符串
|
||||
2. 访客首页(未登录)无轮播图时展示静态引导图
|
||||
|
||||
#### S0-3: 生产环境 console 脱敏 [CRITICAL, 1d]
|
||||
|
||||
**方案:**
|
||||
1. 创建 `utils/logger.ts`,区分 `__DEV__` 和生产模式
|
||||
2. 生产模式仅输出错误类别码(如 `[auth] LOGIN_FAILED`),不附带 error 对象
|
||||
3. `prod.ts` 的 `terserOption.compress.pure_funcs` 增加 `'console.warn'`
|
||||
|
||||
#### S0-4: 开发快速登录生产构建移除 [HIGH, 0.5d]
|
||||
|
||||
**方案:**
|
||||
1. `config/index.ts` 的 `defineConstants` 中,生产环境硬编码 `TARO_APP_DEV_USER/PASS` 为空字符串
|
||||
2. login 页面组件条件编译确保生产构建中整段 JSX 被 tree-shaking 移除
|
||||
|
||||
#### S0-5: BLE 数据缓冲加密存储 [HIGH, 2d]
|
||||
|
||||
**方案:**
|
||||
1. `DataBuffer` 构造函数接收加密函数引用(DI)
|
||||
2. `persistCurrentBucket` 和 `restore` 通过注入的 `encryptFn/decryptFn` 处理
|
||||
3. 复用 S0-1 的 AES-GCM 会话密钥
|
||||
|
||||
### 4.2 工程基础
|
||||
|
||||
#### E0-1: 开启 TypeScript strict [2d]
|
||||
|
||||
**方案:**
|
||||
1. `tsconfig.json` 设置 `noImplicitAny: true`
|
||||
2. 修复编译错误,优先修复 `request.ts`、`BLEManager.ts`、`auth.ts`
|
||||
3. 临时 `// @ts-expect-error` 标注无法立即修复的位置
|
||||
|
||||
#### E0-2: ESLint + Prettier 工具链 [1d]
|
||||
|
||||
**方案:**
|
||||
1. 安装 `eslint@9`(flat config)、`@typescript-eslint/parser`、`@typescript-eslint/eslint-plugin`、`eslint-plugin-react-hooks`、`prettier`
|
||||
2. 核心 ruleset:`@typescript-eslint/no-explicit-any: error`、`react-hooks/exhaustive-deps: error`
|
||||
3. Prettier 配置:`{ semi: true, singleQuote: true, trailingComma: "all", printWidth: 100 }`
|
||||
|
||||
#### E0-3: ErrorBoundary 安全加固 [1d]
|
||||
|
||||
**方案:**
|
||||
1. 添加错误分类(NetworkError / RenderError / BusinessError)
|
||||
2. 结构化日志输出(含 user_id / tenant_id / page_path)
|
||||
3. 重试次数按错误类型区分
|
||||
|
||||
#### E0-4: DataSyncScheduler 并发安全 [0.5d]
|
||||
|
||||
**方案:** 添加 `isSyncing` 互斥标志,interval callback 先检查再执行。
|
||||
|
||||
### 4.3 Phase 0 验收标准
|
||||
|
||||
- [ ] `secure-storage.ts` 使用 AES-256-GCM,XOR 仅作为迁移读取路径
|
||||
- [ ] `.env.production` 不包含真实 Tenant ID
|
||||
- [ ] 生产构建中 `console.warn` 被移除或脱敏
|
||||
- [ ] 开发快速登录按钮在生产构建中不存在
|
||||
- [ ] BLE DataBuffer 使用加密存储
|
||||
- [ ] `tsconfig.json` 中 `noImplicitAny: true`,`npx tsc --noEmit` 零错误
|
||||
- [ ] `npx eslint src/` 零 error
|
||||
- [ ] `cargo check` + `cargo test` 全 workspace 通过(后端配合修改)
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 1:测试覆盖 + UX 合规(周 3-4)
|
||||
|
||||
### 5.1 测试覆盖提升
|
||||
|
||||
#### T1-1: secure-storage 单元测试 [2d]
|
||||
|
||||
覆盖:AES-GCM 加解密对称性、Base64 边界(空字符串/超长/中文/emoji)、空 value 触发 remove、明文 fallback 读取兼容性、`migrateLegacyStorage` 迁移逻辑。
|
||||
|
||||
#### T1-2: request.ts 核心路径测试 [2d]
|
||||
|
||||
覆盖:ConcurrencyLimiter acquire/release 队列行为、401 重试 + token 刷新去重、ResponseCache 命中/淘汰/inflight 去重、`safeReLaunch` 去重、AbortSignal 取消。
|
||||
|
||||
#### T1-3: auth store 测试 [2d]
|
||||
|
||||
覆盖:`restore()` 状态恢复、`login`/`credentialLogin` 流程、`logout` 清理完整性、`bindPhone` 流程、角色判断函数。
|
||||
|
||||
#### T1-4: DataSyncScheduler + BLEManager 测试 [1.5d]
|
||||
|
||||
覆盖:`needsSync` 时间判断、`startPeriodicCheck` 不并发、BLE 连接生命周期。
|
||||
|
||||
### 5.2 UX 合规
|
||||
|
||||
#### U1-1: 核心 ARIA 角色标注 [2d]
|
||||
|
||||
为 TabBar(`role="tablist"`/`role="tab"` + `aria-selected`)、表单输入(`aria-label`/`aria-describedby`/`aria-invalid`)、按钮(`aria-disabled`)、加载状态(`role="status"` + `aria-live="polite"`)添加语义角色。覆盖约 15 个核心组件。
|
||||
|
||||
#### U1-2: 表单可访问性增强 [1d]
|
||||
|
||||
`FormInput` 组件的 `<Input>` 元素添加 `aria-label`、`aria-describedby`(错误提示关联)、`aria-invalid`。体征录入页数值输入添加 `aria-valuemin/max/now`。
|
||||
|
||||
#### U1-3: 动态内容 aria-live [0.5d]
|
||||
|
||||
LoadingCard、EmptyState、ErrorState、TrendChart tooltip 添加 `aria-live` 区域,确保内容变化时读屏器自动播报。
|
||||
|
||||
#### U1-4: 焦点管理基础 [1d]
|
||||
|
||||
所有可交互元素添加 `focusin`/`focusout` 视觉反馈样式,确保焦点环可见。
|
||||
|
||||
### 5.3 Phase 1 验收标准
|
||||
|
||||
- [ ] 单元测试文件 ≥6 个(secure-storage / request / auth / scheduler / BLE / components)
|
||||
- [ ] 核心安全模块行覆盖率 ≥80%
|
||||
- [ ] `npx vitest run` 全部通过
|
||||
- [ ] 核心交互组件有 ARIA 角色标注
|
||||
- [ ] FormInput 组件有完整的可访问性属性
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 2:加密替换 + Canvas 适老(周 5-6)
|
||||
|
||||
### 6.1 安全加固
|
||||
|
||||
#### S2-1: Analytics 队列 PII 清理 [2d]
|
||||
|
||||
**方案:**
|
||||
1. `AnalyticsEvent` 接口移除 `userId` 和 `patientId` 字段
|
||||
2. 服务端从 JWT 提取用户身份,不从客户端 payload 接收
|
||||
3. Storage 中的队列仅保留 `event + timestamp + properties`
|
||||
4. properties 运行时过滤禁止包含 user/patient 标识
|
||||
|
||||
#### S2-2: 健康阈值缓存加密 [1d]
|
||||
|
||||
复用 Phase 0 的 AES-GCM 加密函数,`getHealthThresholds` 写入缓存时加密,读取时解密。缓存 TTL 24h 不变。
|
||||
|
||||
#### S2-3: Patient DTO 字段最小化 [2d]
|
||||
|
||||
**方案:**
|
||||
1. 后端拆分 `PatientSummary`(列表用,5-8 非敏感字段)和 `PatientDetail`(详情用)
|
||||
2. 小程序 `loadPatients` 调用 summary 端点
|
||||
3. `current_patient` 存储过滤掉 `id_card_number`、`phone_hash` 等字段
|
||||
|
||||
### 6.2 Canvas 适老化
|
||||
|
||||
#### U2-1: TrendChart Canvas 适老化 [2d]
|
||||
|
||||
**方案:**
|
||||
1. 创建 `useCanvasTokens()` hook,从 CSS 变量提取字号/颜色供 Canvas 使用
|
||||
2. 关怀模式下:字号提升到 14-16px、颜色对比度 WCAG AA、异常点放大到 7-8px
|
||||
3. 参考区间带添加斜线纹理增强区分度
|
||||
4. 数据点 tooltip 改为常驻显示(老年用户难以精确触摸小点)
|
||||
|
||||
#### U2-2: 表单自动跳焦 + 历史参考 [1.5d]
|
||||
|
||||
**方案:**
|
||||
1. 体征录入页 `returnKeyType="next"` + `onConfirm` 链式跳焦
|
||||
2. 显示上次测量值作为参考提示
|
||||
3. 体重录入显示上次记录
|
||||
|
||||
#### U2-3: Loading/骨架屏统一 [0.5d]
|
||||
|
||||
规范:列表页用 LoadingCard(card/list)、详情页用 LoadingCard(detail)、操作反馈用 Loading spinner。
|
||||
|
||||
### 6.3 Phase 2 验收标准
|
||||
|
||||
- [ ] Analytics 队列无 PII 字段
|
||||
- [ ] 健康阈值缓存加密存储
|
||||
- [ ] Patient DTO 列表端点不含敏感字段
|
||||
- [ ] TrendChart 在关怀模式下字号 ≥14px、异常点可见
|
||||
- [ ] `useCanvasTokens()` hook 在所有 Canvas 组件中使用
|
||||
- [ ] 血压录入支持自动跳焦
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase 3:全面提升 + CI(周 7-8)
|
||||
|
||||
### 7.1 安全
|
||||
|
||||
#### S3-1: API 请求签名验证 [1.5d]
|
||||
|
||||
**方案:**
|
||||
1. 后端 login/refresh 下发 `signing_key`(256-bit,仅存内存)
|
||||
2. `request.ts` 计算 HMAC-SHA256(method + path + body_hash + timestamp + nonce)
|
||||
3. 请求头添加 `X-Signature`、`X-Timestamp`、`X-Nonce`
|
||||
4. 后端中间件校验签名,timestamp 偏移 >5min 拒绝
|
||||
|
||||
### 7.2 工程
|
||||
|
||||
#### E3-1: 消灭所有 `any` 类型 [1d]
|
||||
|
||||
- `catch (err: any)` → `catch (err: unknown)` + 类型守卫
|
||||
- BLE 回调参数提取具体接口
|
||||
- `method as any` 使用 Taro `RequestMethod` 类型
|
||||
|
||||
#### E3-2: 大文件拆分 [1d]
|
||||
|
||||
8 个 >300 行文件拆分至 ≤250 行。提取 hooks(`useDailyMonitoring`、`useTrendChart`)和子组件。
|
||||
|
||||
#### E3-3: 构建优化 [1d]
|
||||
|
||||
1. `splitChunks` 按分包拆分公共依赖
|
||||
2. `package.json` 添加 `"sideEffects": false`
|
||||
3. `webpack-bundle-analyzer` 分析产物
|
||||
|
||||
#### E3-4: CI 集成 [1d]
|
||||
|
||||
PR 触发:TypeScript 编译 + ESLint + 单元测试。阻断条件:编译失败/ESLint error/测试失败。
|
||||
|
||||
### 7.3 UX
|
||||
|
||||
#### U3-1: 医生端导航状态保持 [1d]
|
||||
|
||||
为高频页面实现状态持久化(离开时保存关键状态到 session,返回时恢复)。
|
||||
|
||||
#### U3-2: 微交互统一 [0.5d]
|
||||
|
||||
统一触觉反馈、动画时序(200ms ease-out)、加载到内容的 fade-in 过渡。
|
||||
|
||||
### 7.4 Phase 3 验收标准
|
||||
|
||||
- [ ] `grep -rn ': any' src/` 返回 0 结果
|
||||
- [ ] 所有源文件 ≤250 行
|
||||
- [ ] 主包体积相比优化前减少 ≥15%
|
||||
- [ ] CI 流水线在 PR 上自动运行
|
||||
- [ ] API 签名在登录/预约请求中生效
|
||||
- [ ] 医生端 Tab 切换后搜索状态保持
|
||||
|
||||
---
|
||||
|
||||
## 8. 医疗合规特殊要求
|
||||
|
||||
### 8.1 PIPL 合规
|
||||
|
||||
| 要求 | Phase | 技术措施 |
|
||||
|------|-------|----------|
|
||||
| 敏感个人信息加密存储 | Phase 0 | AES-256-GCM 替换 XOR |
|
||||
| 数据最小化 | Phase 2 | Patient DTO 拆分,仅返回必要字段 |
|
||||
| 用户同意管理 | Phase 1 | consent 模块已有 CRUD,需新增 handler 层拦截器检查同意状态 |
|
||||
| 数据出境评估 | Phase 2+ | AI 分析 PII 脱敏 + `data_residency: cn_only` |
|
||||
| 安全事件响应 | Phase 3 | 安全审计日志 + 异常登录检测 |
|
||||
|
||||
### 8.2 无障碍合规
|
||||
|
||||
《无障碍环境建设法》(2023) 要求公共服务应用提供无障碍支持。医疗健康管理平台属于覆盖范围。
|
||||
|
||||
| 要求 | Phase | 技术措施 |
|
||||
|------|-------|----------|
|
||||
| ARIA 语义标注 | Phase 1 | TabBar/表单/按钮/状态组件 |
|
||||
| 屏幕阅读器支持 | Phase 1 | aria-live 动态区域 |
|
||||
| 键盘焦点管理 | Phase 1 | focusin/focusout 视觉反馈 |
|
||||
| 色彩对比度 | Phase 0+ | Canvas 适老化 + Token 审计 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险评估与缓解
|
||||
|
||||
| 风险 | 影响 | 概率 | 缓解方案 |
|
||||
|------|------|------|----------|
|
||||
| AES 替换后冷启动需重新登录 | UX 下降 | 确定 | refresh token 自动恢复,用户无感知 |
|
||||
| AES-GCM 在小程序性能 | 延迟增加 | 低 | `@noble/ciphers` 实测 <1ms/次 |
|
||||
| 访客首页无轮播图 | 功能降级 | 确定 | 改为静态引导图 |
|
||||
| BLE 冷启动丢弃缓冲数据 | 数据丢失 | 中 | BLE 缓冲是降级能力,同步后清空 |
|
||||
| 前后端 DTO 拆分范围大 | 接口不稳定 | 中 | 渐进式,保持旧端点兼容 |
|
||||
| 请求签名计算增加延迟 | 性能 | 低 | HMAC-SHA256 <0.5ms/次 |
|
||||
| 客户端时钟偏移 | 签名失败 | 中 | 后端容忍 5min 偏移 + UI 提示校准 |
|
||||
| PIPL 数据出境限制 AI 功能 | 功能受限 | 中 | 境外 Provider 禁用,仅用 Ollama |
|
||||
|
||||
---
|
||||
|
||||
## 10. 验收标准
|
||||
|
||||
### 全局验收(Phase 3 结束后)
|
||||
|
||||
| 维度 | 当前 | 目标 | 验收方法 |
|
||||
|------|------|------|----------|
|
||||
| 安全评分 | 6.0 | ≥8.5 | 安全审计复查 |
|
||||
| 架构评分 | 7.25 | ≥8.5 | TypeScript strict + 0 any |
|
||||
| UX 评分 | 7.4 | ≥8.5 | ARIA 覆盖 + Canvas 适老 |
|
||||
| 工程评分 | 6.2 | ≥8.0 | 测试 ≥50% + ESLint 0 error + CI |
|
||||
| 测试覆盖率 | ~6% | ≥50% | `vitest --coverage` |
|
||||
| `any` 使用 | 38 处 | 0 | `grep -rn ': any' src/` |
|
||||
| ARIA 属性 | 0 个 | ≥50 | `grep -rn 'aria-' src/` |
|
||||
| CI 阻断 | 无 | 全覆盖 | PR 自动检查 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 技术选型
|
||||
|
||||
### 加密:`@noble/ciphers` + 微信小程序 polyfill
|
||||
|
||||
纯 JS AES-GCM 实现,无 Web Crypto 依赖,便于测试和跨环境兼容。体积 ~10KB gzipped。
|
||||
|
||||
**关键:微信小程序环境 polyfill 策略**
|
||||
|
||||
微信小程序没有标准 `crypto.getRandomValues()`,平台提供 `wx.getRandomValues()`(异步)或旧版 `Taro.arrayBufferToBase64` + `Math.random` 混合方案。
|
||||
|
||||
启动时 polyfill:
|
||||
```typescript
|
||||
// utils/crypto-polyfill.ts
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
// 微信小程序 crypto.getRandomValues polyfill
|
||||
if (!globalThis.crypto?.getRandomValues) {
|
||||
globalThis.crypto = {
|
||||
getRandomValues: (arr: Uint8Array) => {
|
||||
const buf = new ArrayBuffer(arr.length);
|
||||
// wx.getRandomValuesSync 在基础库 2.17.3+ 可用
|
||||
if (typeof wx !== 'undefined' && wx.getRandomValuesSync) {
|
||||
const res = wx.getRandomValuesSync(buf);
|
||||
arr.set(new Uint8Array(res));
|
||||
} else {
|
||||
// fallback: Math.random(仅用于非安全关键场景)
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
```
|
||||
|
||||
在 `app.tsx` 首行导入此 polyfill,确保 `@noble/ciphers` 能正常工作。
|
||||
|
||||
**替代方案:** 使用 [wecipher](https://classic.yarnpkg.com/en/package/wecipher) 库,已内置微信小程序兼容性封装。
|
||||
|
||||
### ESLint:Flat Config v9+
|
||||
|
||||
```javascript
|
||||
// eslint.config.mjs
|
||||
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||
import tsparser from '@typescript-eslint/parser';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import jsxA11y from 'eslint-plugin-jsx-a11y';
|
||||
|
||||
export default [
|
||||
{ files: ['src/**/*.{ts,tsx}'] },
|
||||
{ languageOptions: { parser: tsparser } },
|
||||
tseslint.configs.recommended,
|
||||
{ rules: { '@typescript-eslint/no-explicit-any': 'error' } },
|
||||
reactHooks.configs.recommended,
|
||||
jsxA11y.configs.recommended,
|
||||
];
|
||||
```
|
||||
|
||||
### 测试:Vitest(已安装)
|
||||
|
||||
`environment: 'node'`,Mock 策略三层:基础设施 Mock(Taro API)→ 服务 Mock(request)→ 组件 Mock(子组件)。
|
||||
|
||||
### Prettier
|
||||
|
||||
```json
|
||||
{ "semi": true, "singleQuote": true, "trailingComma": "all", "printWidth": 100, "tabWidth": 2 }
|
||||
```
|
||||
|
||||
### Canvas Token 桥接
|
||||
|
||||
微信小程序没有 `document`/`getComputedStyle`,Canvas 无法直接读取 CSS 变量。使用构建时生成 + 运行时注入方案:
|
||||
|
||||
```typescript
|
||||
// scripts/generate-tokens.ts — 构建时从 tokens.scss 解析生成
|
||||
// 输出 src/styles/token-values.ts
|
||||
|
||||
// src/styles/token-values.ts — 自动生成,勿手动编辑
|
||||
export const TOKEN_VALUES = {
|
||||
fontBody: '16px',
|
||||
fontBodySm: '14px',
|
||||
fontCap: '13px',
|
||||
pri: '#C4623A',
|
||||
tx: '#2D2A26',
|
||||
// ...
|
||||
} as const;
|
||||
|
||||
// 导出关怀模式/医生模式的覆盖值
|
||||
export const ELDER_TOKEN_OVERRIDES = {
|
||||
fontBody: '22px',
|
||||
fontBodySm: '20px',
|
||||
// ...
|
||||
} as const;
|
||||
|
||||
export const DOCTOR_TOKEN_OVERRIDES = {
|
||||
pri: '#3A6B8C',
|
||||
// ...
|
||||
} as const;
|
||||
```
|
||||
|
||||
```typescript
|
||||
// hooks/useCanvasTokens.ts — 运行时 Hook
|
||||
import { TOKEN_VALUES, ELDER_TOKEN_OVERRIDES, DOCTOR_TOKEN_OVERRIDES } from '@/styles/token-values';
|
||||
import { useUIStore } from '@/stores/ui';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
|
||||
export function useCanvasTokens() {
|
||||
const mode = useUIStore((s) => s.mode);
|
||||
const isDoctor = useAuthStore((s) => s.isDoctor);
|
||||
|
||||
return useMemo(() => {
|
||||
let tokens = { ...TOKEN_VALUES };
|
||||
if (mode === 'elder') tokens = { ...tokens, ...ELDER_TOKEN_OVERRIDES };
|
||||
if (isDoctor) tokens = { ...tokens, ...DOCTOR_TOKEN_OVERRIDES };
|
||||
return tokens;
|
||||
}, [mode, isDoctor]);
|
||||
}
|
||||
```
|
||||
|
||||
构建流程在 `npm run build:weapp` 前自动执行 `generate-tokens.ts`,确保 JS 常量与 SCSS 变量同步。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:回滚策略
|
||||
|
||||
Phase 0 引入了破坏性变更,以下为各变更的回滚方案:
|
||||
|
||||
### AES 加密替换回滚
|
||||
|
||||
`AesSecureStorage` 保留 XOR 读取路径作为 fallback:`secureGet` 尝试 AES 解密失败时,自动尝试 XOR 解密。如果需要完全回滚,将 `SecureStorageFactory.create()` 切回 `XorSecureStorage` 即可。Storage 中已有数据无需迁移——旧数据用 XOR 读取,新数据用 AES 写入,自然过渡。
|
||||
|
||||
### TypeScript strict 回滚
|
||||
|
||||
`tsconfig.json` 中 `noImplicitAny` 可随时改回 `false`。已修复的类型标注保留不影响运行时,只是编译检查宽松度变化。
|
||||
|
||||
### ESLint 阻断回滚
|
||||
|
||||
紧急修复时可临时禁用 CI 中的 ESLint 阻断条件(`--max-warnings=-1` → `--max-warnings=999`)。修复完成后恢复严格模式。
|
||||
|
||||
### BLE 加密回滚
|
||||
|
||||
DataBuffer 的 DI 设计允许注入明文存储函数(`identity` 函数),回滚时替换构造函数参数即可。
|
||||
|
||||
---
|
||||
|
||||
## 附录 B:专家组成员
|
||||
|
||||
| 专家 | 角色 | 分析维度 |
|
||||
|------|------|----------|
|
||||
| Security Engineer | 安全审计 | 加密/PII/网络/合规 |
|
||||
| Senior Developer | 架构+工程 | TypeScript/依赖/错误处理/构建 |
|
||||
| UX Architect | 用户体验 | Token/关怀模式/可访问性/交互 |
|
||||
| Frontend Developer | 工程质量 | 测试/代码规范/性能/CI |
|
||||
|
||||
## 附录 B:分析数据来源
|
||||
|
||||
- 168 TS/TSX 源文件逐文件审查
|
||||
- 102 SCSS 文件 Design Token 执行度检查
|
||||
- 42 Service 文件 API 契约验证
|
||||
- 50+ git 提交历史分析
|
||||
- wiki/miniprogram.md(54KB)+ wiki/miniprogram-quality-checklist.md(29KB)
|
||||
- docs/qa/ 下 10+ 份 QA 文档交叉验证
|
||||
Reference in New Issue
Block a user