docs(spec): 修正 spec review 反馈的 6 个问题
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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` 参数
|
||||
|
||||
**改动**:
|
||||
- 添加多行 `<Input type='textarea'>` 字段(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(统计卡片显示 `-`)+ 错误提示 + 重试按钮
|
||||
- 不再永久卡在 `<Loading />`
|
||||
- `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` 字段提取
|
||||
|
||||
Reference in New Issue
Block a user