From 43795b2fb726b2acf119f707da6b3673a8848f64 Mon Sep 17 00:00:00 2001 From: iven Date: Thu, 21 May 2026 15:25:57 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E4=BF=AE=E6=AD=A3=20spec=20revie?= =?UTF-8?q?w=20=E5=8F=8D=E9=A6=88=E7=9A=84=206=20=E4=B8=AA=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - secureGet 修复移除无意义的 length > 0 验证 - P0-2 补充 auth.ts logout 中 current_patient_id 清理链路 - P1-1 补充 consultation.ts service 层类型修改 - P1-2 改为复用 input/index.tsx 已有的 num() 校验 - H4 修正医生端描述(非卡死,缺重试) - C7 修正开发登录保护方案(IS_SIMULATOR 体验版泄漏) Co-Authored-By: Claude Opus 4.7 --- ...-21-miniprogram-production-ready-design.md | 64 ++++++++----------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md b/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md index 4d30cca..858793f 100644 --- a/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md +++ b/docs/superpowers/specs/2026-05-21-miniprogram-production-ready-design.md @@ -26,7 +26,7 @@ | C5 | 缺少紧急求助/SOS 入口 | `index.tsx` HomeDashboard | 患者突发状况无法快速求助 | | C6 | Token 存储弱加密(XOR + 硬编码 fallback) | `secure-storage.ts:3` | 越狱设备可直接恢复 JWT token | | C7 | 开发快速登录可能泄露到生产 | `login/index.tsx:8-9` | 攻击者获取 dev 凭证 | -| C8 | AI 聊天无后端持久化 | `messages/index.tsx` | Tab 切换可能丢失对话 | +| C8 | AI 聊天持久化需验证 | `messages/index.tsx` | wiki 标记已修复(后端消息加载 API 已添加),需验证实际效果 | ### HIGH(11 项) @@ -35,7 +35,7 @@ | H1 | 积分任务按钮无 onClick | `mall/index.tsx:187` | | H2 | 告警无微信推送能力 | `alerts/index.tsx` | | H3 | 体征录入校验不一致 | `health/index.tsx:112` vs `input/index.tsx:33` | -| H4 | 医生端工作台加载卡死 | `pkg-doctor-core/index.tsx` | +| H4 | 医生端工作台 API 失败无重试 | `pkg-doctor-core/index.tsx` | | H5 | 家属代理操作无权限模型 | `family/index.tsx:47`, `auth.ts:226` | | H6 | 首页医护人员 reLaunch 状态丢失 | `index.tsx:319-329` | | H7 | PII 明文绕过 secure storage | `family/index.tsx:63` | @@ -66,17 +66,13 @@ export function secureGet(key: string): string { try { const decoded = fromBase64(raw); if (decoded) { - const decrypted = xorEncrypt(decoded, ENCRYPTION_KEY); - // 验证解密结果是否为有效数据(JSON 或 JWT) - if (decrypted.startsWith('{') || decrypted.startsWith('eyJ') || decrypted.length > 0) { - return decrypted; - } + return xorEncrypt(decoded, ENCRYPTION_KEY); } } catch { // fallthrough — 可能是未加密的旧数据 } - // fallback: 兼容未加密的旧数据 + // fallback: 兼容未加密的旧数据(明文 JSON/JWT 或其他值) return raw; } ``` @@ -86,10 +82,12 @@ export function secureGet(key: string): string { ### P0-2. 修复 Storage Key 前缀不一致 **文件**: -- `src/services/request.ts` 第 149 行: `Taro.getStorageSync('current_patient_id')` → `secureGet('current_patient_id')` +- `src/services/request.ts` 第 149 行: `Taro.getStorageSync('current_patient_id')` → `secureGet('current_patient_id')`(先修此处,确保内存缓存正确填充) - `src/services/request.ts` 第 218 行: `Taro.removeStorageSync('current_patient_id')` → `secureRemove('current_patient_id')` -- `src/services/health.ts` 第 20 行: `Taro.getStorageSync('current_patient_id')` → 改用 `getCachedPatientId()` 或 `secureGet('current_patient_id')` -- `src/stores/auth.ts` 第 266 行: `secureRemove('analytics_queue')` → `Taro.removeStorageSync('analytics_queue')`(analytics 用明文存储) +- `src/services/health.ts` 第 20 行: 改用 `getCachedPatientId()`(依赖 request.ts 的内存缓存) +- `src/stores/auth.ts` 第 264-265 行: `secureRemove('current_patient')` / `secureRemove('current_patient_id')` 需同步确认写入路径一致 +- `src/stores/auth.ts` 第 266 行: `secureRemove('analytics_queue')` → `Taro.removeStorageSync('analytics_queue')`(analytics 使用明文存储,清理也必须用明文) +- `src/services/analytics.ts`: 确认 analytics_queue 包含 userId/patientId,logout 时必须清除(PII 泄漏风险) ### P0-3. 验证 @@ -103,36 +101,24 @@ export function secureGet(key: string): string { ### P1-1. 咨询创建添加症状描述 -**文件**: `src/pages/consultation/create/index.tsx` +**文件**: +- `src/pages/consultation/create/index.tsx` — 添加 UI 字段 +- `src/services/consultation.ts` — `createSession` 函数类型签名添加 `description` 参数 **改动**: - 添加多行 `` 字段(placeholder: "请描述您的症状或问题") - 标记为建议填写(非必填),但未填写时 Toast 提醒"建议描述症状以便医生更快响应" +- `services/consultation.ts` 的 `createSession` 参数类型扩展,添加 `description?: string` - 提交时将描述传递给后端 API(后端 DTO 已支持 `description` 字段) ### P1-2. 体征录入统一范围校验 -**新建文件**: `src/utils/vital-ranges.ts` +**文件**: `src/pages/health/index.tsx`, `src/pages/pkg-health/input/index.tsx` -```typescript -export const VITAL_RANGES = { - systolic_bp: { min: 60, max: 250, label: '收缩压' }, - diastolic_bp: { min: 40, max: 150, label: '舒张压' }, - heart_rate: { min: 20, max: 300, label: '心率' }, - blood_glucose: { min: 1, max: 50, label: '血糖' }, - weight: { min: 1, max: 500, label: '体重' }, -} as const; - -export function validateVital(type: keyof typeof VITAL_RANGES, value: number): string | null { - const range = VITAL_RANGES[type]; - if (value < range.min || value > range.max) { - return `${range.label}应在 ${range.min}-${range.max} 之间`; - } - return null; -} -``` - -**修改文件**: `src/pages/health/index.tsx` 第 112-115 行,调用 `validateVital()` 替代简单的非空检查。`src/pages/pkg-health/input/index.tsx` 复用同一校验函数(移除内联校验逻辑)。 +**改动**: +- 复用 `input/index.tsx` 中已有的 `num()` 校验函数(非创建新文件),提取到 `src/utils/vital-validation.ts` +- `health/index.tsx` 第 112-115 行,调用提取后的 `num()` 替代简单的非空检查 +- 保留 `input/index.tsx` 中的动态阈值警告逻辑(`getWarnForIndicator`),不降级为静态范围 ### P1-3. 积分任务入口修复 @@ -155,15 +141,15 @@ export function validateVital(type: keyof typeof VITAL_RANGES, value: number): s **文件**: `src/pages/pkg-doctor-core/index.tsx` **改动**: -- `loadDashboard` catch 中设置 `setDashboard({})` 而非仅打印 warn -- loading 为 false 且 dashboard 为 null 时,显示降级 UI(统计卡片显示 `-`)+ 错误提示 + 重试按钮 -- 不再永久卡在 `` +- `loadDashboard` catch 中 `setDashboard({})` 确保组件不卡在永久 Loading +- loading 为 false 且 dashboard 为空对象时,统计卡片显示 `-`(当前已实现) +- 添加错误状态提示和重试按钮(当前缺少) ### P1-5. 首页医护人员跳转优化 **文件**: `src/pages/index/index.tsx` -**改动**: 将 `shouldRedirect` 计算提前到组件顶层(useMemo),在 `useDidShow` 之前就确定跳转。跳转使用 `redirectTo` 替代 `reLaunch`,保留分包预加载状态。 +**改动**: 将 `shouldRedirect` 计算提前到组件顶层(useMemo),在 `useDidShow` 之前就确定跳转。**注意**:首页是 TabBar 页面,`redirectTo` 替换 TabBar 页面可能导致 TabBar 消失,需改为在 `useDidShow` 中使用 `reLaunch`(保留现有方式),但提前判断避免先渲染 Loading 再跳转。 --- @@ -238,8 +224,10 @@ export function validateVital(type: keyof typeof VITAL_RANGES, value: number): s ### P3-2. 安全加固 **开发快速登录保护**: -- `login/index.tsx` 第 8-9 行: 添加编译时保护 -- `config/prod.ts` 中确保 `TARO_APP_DEV_USER` 和 `TARO_APP_DEV_PASS` 注入为空字符串 +- `login/index.tsx` 第 8-9 行: `IS_DEV` 和 `IS_SIMULATOR` 已有保护 +- 实际风险:体验版(`envVersion === 'trial'`)也会显示开发登录按钮(`IS_SIMULATOR` 为 true) +- 修复:第 9 行改为 `typeof __wxConfig !== 'undefined' && (__wxConfig as any).envVersion === 'develop'`(仅开发版显示) +- `config/index.ts` 中 `TARO_APP_DEV_USER/PASS` 已有空字符串 fallback,无需额外修改 `prod.ts` **Tenant ID 安全**: - `request.ts` 第 161 行: `X-Tenant-Id` 改为从 JWT payload 的 `tid` 字段提取