Files
Mdeical_Sur_Report/工程分析/实现方案-2026-04-20-10-14-09.md

178 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 实现方案
> 时间戳2026-04-20-10-14-09
> 对应需求分析:`需求分析-2026-04-20-10-14-09.md`
---
## 1. 方案概述
在不改变 `storage.get/set` 同步调用签名的前提下,将 `systemSettings` 的底层加密从 XOR 迁移至 Web Crypto API AES-GCM密钥与 API Key、授权哈希全部移入构建阶段环境变量。应用启动时异步初始化存储模块预解密 `systemSettings` 到内存缓存,后续同步读写均走缓存。旧 XOR 密文自动识别并迁移。UserManage.tsx 中的明文授权常量替换为 SHA-256 环境变量哈希比对,同时以最小代价替换原生 alert/confirm 为内联状态提示。
---
## 2. 详细改动清单
### 2.1 文件src/utils/storage.ts
| 行号范围 | 改动类型 | 说明 |
|----------|----------|------|
| 1-6 | 删除 | 移除 `API_KEY_CODES` 数组与 `getDefaultApiKey()` 的旧实现 |
| 8 | 修改 | `CRYPTO_KEY` 改为从 `process.env.STORAGE_KEY` 读取,并更名为 `LEGACY_CRYPTO_KEY` |
| 10-25 | 保留 | 旧 `xorEncrypt` / `xorDecrypt` 保留用于向后兼容读取 |
| 新增 | 新增 | 引入 Web Crypto API`importCryptoKey``aesEncrypt``aesDecrypt` |
| 新增 | 新增 | `AES_GCM_PREFIX = 'ag:'` 用于识别新格式密文 |
| 新增 | 新增 | `systemSettingsCache``initPromise` 等模块级状态 |
| 新增 | 新增 | 异步 `init()` 函数:导入密钥、预加载 systemSettings、自动迁移旧数据 |
| 27-43 | 修改 | `get()``systemSettings` 改为从内存缓存返回 |
| 45-55 | 修改 | `set()``systemSettings` 改为更新缓存并触发异步 `persistSystemSettings()` |
| 新增 | 新增 | `sha256(text)` 工具函数Web Crypto API供 UserManage.tsx 使用 |
**关键代码逻辑**
```typescript
// AES-GCM 密文格式: ag:<base64(iv)>:<base64(ciphertext)>
const AES_GCM_PREFIX = 'ag:';
async function init() {
const keyData = process.env.STORAGE_KEY || '';
if (!keyData) throw new Error('STORAGE_KEY not configured in build env');
cryptoKey = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(keyData.padEnd(32, '0').slice(0, 32)),
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
const raw = localStorage.getItem('systemSettings');
if (raw) {
if (raw.startsWith(AES_GCM_PREFIX)) {
systemSettingsCache = await aesDecrypt(raw);
} else {
try { systemSettingsCache = JSON.parse(raw); }
catch { systemSettingsCache = JSON.parse(xorDecrypt(raw, LEGACY_KEY)); }
await persistSystemSettings(); // 静默迁移
}
}
}
```
### 2.2 文件src/main.tsx
| 行号范围 | 改动类型 | 说明 |
|----------|----------|------|
| 4-10 | 修改 | 包装为异步 `bootstrap()`,先 `await storage.init()` 再 render App |
```typescript
import { storage } from './utils/storage';
async function bootstrap() {
await storage.init();
createRoot(document.getElementById('root')!).render(
<StrictMode><App /></StrictMode>
);
}
bootstrap();
```
### 2.3 文件vite.config.ts
| 行号范围 | 改动类型 | 说明 |
|----------|----------|------|
| 10-12 | 修改 | 在 `define` 中新增 4 个环境变量注入 |
```typescript
define: {
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
'process.env.STORAGE_KEY': JSON.stringify(env.STORAGE_KEY),
'process.env.DEFAULT_API_KEY': JSON.stringify(env.DEFAULT_API_KEY),
'process.env.ADMIN_AUTH_HASH': JSON.stringify(env.ADMIN_AUTH_HASH),
'process.env.ADMIN_AUTH_SALT': JSON.stringify(env.ADMIN_AUTH_SALT),
},
```
### 2.4 文件:.env.example
| 行号范围 | 改动类型 | 说明 |
|----------|----------|------|
| 新增 | 新增 | 追加 4 个必需环境变量模板 |
```bash
# 本地存储 AES-GCM 加密密钥(建议 32 位随机字符串)
STORAGE_KEY="YourRandom32CharKeyHere!!!"
# 默认 AI API Key替代原 API_KEY_CODES 硬编码)
DEFAULT_API_KEY="your-api-key-here"
# 禁用管理员操作的授权码 SHA-256 哈希值hex 小写)
ADMIN_AUTH_HASH="your-sha256-hash-here"
# 授权码哈希盐值
ADMIN_AUTH_SALT="your-random-salt-here"
```
### 2.5 文件src/pages/UserManage.tsx
| 行号范围 | 改动类型 | 说明 |
|----------|----------|------|
| 8 | 删除 | 移除 `ADMIN_DISABLE_AUTH_KEY = 'DISABLE_ADMIN_2024'` |
| 新增 | 新增 | 从环境变量读取 `ADMIN_AUTH_HASH``ADMIN_AUTH_SALT` |
| 29 | 修改 | `authKey` 保持,但比对逻辑改为 SHA-256 |
| 新增 | 新增 | 增加 `errorMessage``confirmDialog` 状态替换 alert/confirm |
| 103-116 | 修改 | `handleDelete` 改为触发确认对话框状态,移除 `window.confirm` |
| 118-154 | 修改 | `handleEdit` 中 alert 改为设置 `errorMessage` |
| 185-324 | 修改 | `handleSubmit` 改为 async所有 alert 改为 `errorMessage`,授权比对改为 `await sha256(authKey + salt)` |
| 新增 JSX | 新增 | 表单顶部增加错误提示条;新增轻量确认对话框覆盖层 |
---
## 3. 关键设计决策
| 决策点 | 选择方案 | 备选方案 | 不选备选的原因 |
|--------|----------|----------|----------------|
| systemSettings 异步加密的同步接口兼容 | 模块初始化时预解密到内存缓存get/set 保持同步 | 将 storage.get/set 全局改为 async | 调用点超过 50 处,改动面过大,风险不可控 |
| AES-GCM 密钥长度处理 | 将环境变量字符串 padEnd 到 32 字节后截断 | 要求用户必须提供 32 字节 | 提升用户体验,减少配置门槛 |
| 旧数据迁移时机 | init() 中自动检测并静默迁移 | 提供手动迁移脚本 | 纯前端无脚本执行环境,自动迁移对用户透明 |
| SHA-256 实现选择 | Web Crypto API已在 storage.ts 提供工具函数) | crypto-js 同步计算 | 统一使用 Web Crypto API减少额外依赖引用 |
| alert/confirm 替换策略 | 内联状态提示条 + 轻量确认状态 | 抽取全局 Modal 组件 | 项目中无现成 Modal本次以最小代价消除明文暴露和原生弹窗 |
---
## 4. 与现有规范的兼容性检查
- [ ] contentEditable 编辑器规范5.1 — 本次不涉及编辑器
- [x] 自动保存与草稿机制5.2 — storage 接口签名不变ReportEditor 自动保存逻辑无影响
- [ ] 图片与视频处理5.3 — 本次不涉及
- [ ] 智能字段与动态表单5.4 — 本次不涉及
- [ ] AI 功能开发规范5.5`getDefaultApiKey()` 返回值不变,调用方无感知
- [x] 数据迁移与类型安全5.6 — 旧 XOR 密文自动迁移,使用 `(obj as any).oldField` 安全访问思路已体现在旧格式兼容分支
- [x] UI/UX 通用规范5.7 — UserManage.tsx 中原生 alert/confirm 全部替换为自定义状态提示
---
## 5. 回滚策略
若构建失败或运行异常:
```bash
git reset --hard HEAD~1
```
或手动还原:
1. 恢复 `src/utils/storage.ts` 中的 XOR 逻辑与 `API_KEY_CODES`
2. 恢复 `src/pages/UserManage.tsx` 中的 `ADMIN_DISABLE_AUTH_KEY`
3. 恢复 `src/main.tsx` 为同步 render
4. 还原 `vite.config.ts` 的 define 配置
---
## 6. 预估工作量
| 阶段 | 预估时间 |
|------|----------|
| 代码修改 | 25 min |
| 测试验证 | 15 min |
| 文档更新 | 10 min |
---
**状态**:✅ 用户已豁免人工审核确认