- Mark the user signature upload API as implemented in the backendization plan. - Refresh the progress next-step list to focus on remaining E2E seed migration, fallback cleanup, audit visibility, export API, rate limits, data migration, and production operations.
19 KiB
后端化与用户化改造方案
本文基于当前迁移期版本整理需要改动的范围。当前系统已完成主业务后端化,目标是继续把开发回退、审计、安全、部署和运维能力收紧为“多用户、可审计、可部署、可维护”的前后端系统。
改造目标
当前系统已经有较完整的页面、业务流程和后端 API。仍需关注的迁移期边界包括:
- 用户、密码、权限、工作台统计、报告、报告媒体关系、模板、字段库、模板图片、用户和部门权限已优先接入后端 API;
localStorage.users仅作为开发回退兼容缓存存在。 - 设置和部分兼容缓存仍存在
localStorage;生产构建默认关闭本地回退。 - 签名、模板图片资源、视频和关键帧文件已优先进入后端文件资源;报告媒体引用已通过
ReportMedia关系表保存。 - AI Key 和讯飞语音密钥已进入后端代理链路。
- 权限判断主要在前端页面里完成。
后端化后应达到:
- 登录认证由后端完成。
- 所有数据以用户、部门、医院/租户为边界存储。
- 权限在后端强校验,前端只做展示控制。
- 文件和图片从 Base64 本地存储迁移到后端文件服务或对象存储。
- AI 与语音等密钥由后端托管,前端不接触真实密钥。
- 报告查看、编辑、删除等关键行为有审计记录;报告导出按权限控制,不要求专门导出审计。
技术栈决策
后端化第一阶段建议采用 TypeScript 全栈方案,优先保证权限边界、模块结构、数据迁移和测试可维护。
| 层级 | 选择 | 说明 |
|---|---|---|
| 前端 | React 19 + TypeScript + Vite + Tailwind CSS | 保留现有页面主体,逐步把 localStorage 读写替换为 API 调用。 |
| 后端框架 | NestJS + TypeScript | 适合按认证、报告、模板、用户、设置、文件、AI 等模块拆分;Guard/Interceptor/Service 结构适合集中权限控制和审计。 |
| 数据库 | PostgreSQL | 适合报告、历史版本、权限关系、审计日志等关系型数据;支持事务、索引和后续统计。 |
| ORM/迁移 | Prisma | 类型生成清晰,迁移和 seed 成本低,便于前后端共享 DTO 思路。 |
| 认证 | HttpOnly Cookie Session,Access Token 仅作为可选扩展 | 前端不直接持久化敏感 token;后端维护会话、退出和失效。第一阶段可用数据库会话表,后续再引入 Redis。 |
| 密码 | Argon2 哈希 | 禁止明文密码进入前端或日志。 |
| 文件存储 | 本地文件目录 + files 表,预留 MinIO/S3 兼容接口 |
开发和院内部署先降低成本;生产可切换对象存储。 |
| 输入校验 | Zod 或 NestJS DTO 校验 | 所有 API 入参都必须校验,尤其是 HTML、文件、权限相关参数。 |
| 测试 | Vitest/Testing Library + Playwright + 后端单元/集成测试 | 保留现有前端测试;新增后端 policy/service/API 测试;E2E 后续从 localStorage seed 改为 API/test DB seed。 |
| 部署 | Docker Compose | 前端静态资源、NestJS API、PostgreSQL、可选 Redis/MinIO 分服务部署。 |
暂不建议第一阶段使用微服务、复杂租户系统或后端抽帧服务。抽帧可以先继续留在前端 canvas,生成关键帧后上传后端文件接口。
当前后端骨架
已新增 server/ 目录作为后端化第一阶段基础:
server/src/main.ts:NestJS 启动入口,设置/api前缀、CORS、Cookie Session、数据库 Session Store 和统一错误响应。server/src/dashboard:GET /api/dashboard/stats,按角色范围统计报告、模板、用户和趋势。server/src/audit:第一版审计服务,记录登录、报告、模板、用户、设置和文件修改。server/src/health:GET /api/health健康检查。server/src/auth:POST /api/auth/login、GET /api/auth/me、POST /api/auth/logout认证接口。server/src/reports:GET/POST/PATCH/DELETE /api/reports报告接口,含角色范围过滤、历史版本、软删除和报告媒体关系同步。server/src/templates:GET/POST/PATCH/DELETE /api/templates模板接口,含access=use/manage、部门模板、部门授权和个人模板。server/src/users:GET/POST/PATCH/DELETE /api/users与/api/departments接口,含用户范围过滤、管理员唯一性、医生创建约束和部门模板授权。server/src/settings:GET/PATCH /api/settings/system和重置接口,保存全局设置与个人默认模板。server/src/files:通用文件上传、列表、删除、受控读取接口;签名文件会额外关联User.signatureFileId。server/src/library:GET/PATCH /api/library/fields字段库接口,保存字段、时间格式和选项库。server/src/ai:GET /api/ai/models和POST /api/ai/chat,使用全局共用 Provider Key 代理 OpenAI 兼容接口。server/src/speech:GET /api/speech/iatWebSocket 代理,使用 Session 校验用户,后端生成讯飞鉴权 URL 并转发音频帧和识别结果。server/src/prisma:PrismaService,使用 Prisma 7 的 PostgreSQL driver adapter。server/src/permissions:报告、模板、用户和管理员创建权限策略,已配套单元测试。server/prisma/schema.prisma:PostgreSQL 数据模型,覆盖租户、部门、用户、业务 Session、AppSession、报告、报告媒体、历史、模板、文件、设置和审计日志。server/prisma/seed.ts:写入默认医院、部门和admin、manager、0001三个账号。
当前前端登录、Dashboard、报告读写、报告媒体关系、模板读写、字段库、模板图片资源、视频/关键帧文件、用户管理、部门模板授权、系统设置、签名文件、AI 对话代理和语音听写代理已接入后端认证/API。真实 PostgreSQL 服务集成测试已覆盖 Auth、Dashboard、Reports、ReportMedia、Templates 和 Files 核心服务,下一步应逐步替换剩余开发回退数据访问。
总体架构建议
前端
保留当前 React + Vite 页面主体,逐步把 storage.get/set 替换成 API 调用。
建议新增:
src/api/:统一 API client。src/auth/:登录态、Token、权限上下文。src/hooks/:数据获取和变更 hook。src/services/:报告、模板、用户、系统设置、文件、AI 服务封装。
后端
建议先采用 NestJS 单体后端,避免一开始拆太复杂:
- Node.js + NestJS + TypeScript。
- PostgreSQL + Prisma。
- Redis 可选,用于会话、限流、任务状态。
- 对象存储可选,院内部署可先用本地文件目录,生产建议 MinIO/S3 兼容存储。
数据边界
后端需要引入明确的组织边界:
tenant:医院或部署实例。department:科室。user:用户账号。role:超级管理员、管理员、医生。permission:模板权限、报告权限、系统配置权限。
如果项目只给单医院内网使用,可以先不做复杂租户模型,但数据库字段建议预留 tenant_id。
数据模型改造
用户与认证
新增表:
usersdepartmentsroles或直接使用枚举字段user_sessions或使用 JWT + refresh tokenuser_template_permissions
当前 User 需要变化:
password改为password_hash,禁止前端返回。signature从 Base64 改为signature_file_id或signature_url。visibleTemplates/manageableTemplates拆成权限关联表。status保留。- 增加
last_login_at、created_by、updated_by。
报告
新增表:
reportsreport_historiesreport_mediareport_exports可选
当前 Report 需要变化:
content仍可保存 HTML,但必须后端清洗。videos.url不再长期依赖浏览器对象 URL,后端保存ReportMedia.fileId/url并返回受控文件 URL。capturedFrames.dataUrl改成ReportMedia.fileId/url派生出的图片 URL。author/authorName改成author_id,展示时 join 用户表。createdAt/updatedAt统一为 ISO 时间戳。status保留draft/completed。history拆到report_histories。
模板与字段
新增表:
templatestemplate_versions可选form_fieldstemplate_fields可选image_assets
当前 Template 需要变化:
content保存 HTML,但导入和保存时后端清洗。fields不建议长期嵌在模板对象里,可拆出字段配置版本。author改成author_id。- 增加
department_id、tenant_id、is_global、status。
系统设置
新增表:
system_settingsai_provider_configsspeech_provider_configs
当前 SystemSettings 需要变化:
- AI Key、讯飞密钥只存后端,前端只看到是否已配置和脱敏信息;普通用户请求设置时不返回真实密钥。
- 抽帧设置可以按全局、科室或用户默认值分层。
- 默认模板可以改成用户级配置
user_preferences.default_template_id。
文件资源
新增表:
files
字段建议:
idtenant_idowner_idkind:signature、template_asset、video、frame、report_exportfilenamemime_typesizestorage_keychecksumcreated_at
API 改造清单
认证
POST /api/auth/loginPOST /api/auth/logoutPOST /api/auth/refreshGET /api/auth/mePATCH /api/auth/password
用户
GET /api/usersPOST /api/usersGET /api/users/:idPATCH /api/users/:idDELETE /api/users/:idPOST /api/users/:id/signatureGET /api/departmentsPOST /api/departmentsPATCH /api/departments/:idDELETE /api/departments/:idPATCH /api/departments/:id/template-permissions
模板
GET /api/templatesPOST /api/templatesGET /api/templates/:idPATCH /api/templates/:idDELETE /api/templates/:idPOST /api/templates/importGET /api/templates/:id/exportPOST /api/templates/batch-exportGET /api/form-fieldsPUT /api/form-fields
报告
GET /api/reportsPOST /api/reportsGET /api/reports/:idPATCH /api/reports/:idDELETE /api/reports/:idPOST /api/reports/:id/completeGET /api/reports/:id/historyPOST /api/reports/:id/restoreGET /api/reports/:id/export.jsonPOST /api/reports/export
文件与视频
POST /api/filesGET /api/filesGET /api/files/:idDELETE /api/files/:id
当前第一版已经保留前端 canvas 抽帧,并把视频和生成的关键帧通过通用文件 API 上传为 VIDEO / FRAME 文件资源。后续如果要做后端视频服务,再新增专用 videos 模块,用于转码、异步抽帧、断点续传和媒体引用关系维护。
AI 与语音
POST /api/ai/chatGET /api/ai/providersPATCH /api/ai/providers/:idPOST /api/ai/providers/:id/testGET /api/speech/configPATCH /api/speech/configPOST /api/speech/token或GET /api/speech/ws-auth
前端不再直接调用第三方 AI 接口,不再保存真实 API Key。
系统设置
GET /api/settingsPATCH /api/settingsPOST /api/settings/reset
清空浏览器本地数据只作为开发/显式回退模式能力;如果未来需要“重置全部后端数据”,应作为独立维护接口谨慎保留,只允许超级管理员在维护模式下执行,并写审计日志。
前端改造清单
第一批必须改
-
新增 API Client
- 统一 base URL、认证头、错误处理、401 刷新或跳登录。
- 当前已新增
src/api/client.ts,支持{ data }envelope 解包、Cookie credentials 和错误 envelope。
-
替换登录逻辑
- 登录调用
/api/auth/login。 - 当前用户来自
/api/auth/me。 - 当前已完成登录 API 接入;默认模板、字段、图片和本地用户初始化仍暂时保留,供未迁移页面使用。
- 登录调用
-
替换
currentUser- 从
localStorage.currentUser改成 Auth Context 或全局 store。 - 本地只保存 token 或 session 标识。
- 当前已新增 Auth Context,但仍会同步
currentUser兼容旧页面;后续页面完成 API 迁移后再移除该缓存依赖。
- 从
-
替换报告数据
ReportManage用GET /api/reports。ReportView用GET /api/reports/:id。ReportEditor保存用POST/PATCH /api/reports。- 历史恢复用后端历史 API。
- 当前前三项已接入;历史恢复仍通过返回的兼容
history和sessionStorage.restore_${reportId}进入编辑器。
-
替换模板数据
TemplateManage全部从 API 获取和保存。- 模板权限由后端返回可见/可管理范围。
- 当前已接入模板列表、创建、编辑、保存内容、删除、医生个人模板、字段库和模板图片资源。
-
替换用户管理
UserManage调用用户 API。- 密码不再回显,也不进入前端用户对象。
- 当前已接入用户列表、新增、编辑、删除、部门模板授权和签名文件上传。
-
替换系统设置
- 当前已接入 Settings API,保存全局设置和个人默认模板。
- 后续应只展示脱敏密钥;保存时发送新密钥,后端加密存储并由代理使用。
第二批建议改
-
文件上传
- 签名、模板图片、视频和关键帧已完成第一版文件 API;报告媒体引用已通过
ReportMedia正规化,报告正文内手动插入图片仍需改为上传文件。 - 报告 HTML 中引用文件 URL 或受控下载 URL。
- 签名、模板图片、视频和关键帧已完成第一版文件 API;报告媒体引用已通过
-
报告内容清洗
- 前端仍可编辑 HTML。
- 保存前后端做白名单清洗。
- 渲染时避免直接信任历史 HTML。
-
草稿同步
- 当前草稿在本地,可改成服务端 draft autosave。
- 防止换浏览器或清缓存丢草稿。
-
AI 代理
ReportEditor的fetch(chat/completions)改成/api/ai/chat。- 后端记录请求摘要和错误,避免前端暴露密钥。
- 当前已接入
/api/ai/models和/api/ai/chat,所有用户共用全局 Provider Key;仍待第三方调用摘要和错误追踪落库。
-
审计日志
- 登录、编辑、完成、删除、重置、权限变更等关键操作写日志。
- 报告导出不要求水印、导出原因、审批或专门导出审计。
权限模型建议
角色能力
| 能力 | 超级管理员 | 管理员 | 医生 |
|---|---|---|---|
| 管理全局设置 | 是 | 否 | 否 |
| 管理 AI/语音密钥 | 是 | 否 | 否 |
| 管理全部用户 | 是 | 否 | 否 |
| 管理同部门医生 | 是 | 是 | 否 |
| 管理模板 | 全部 | 授权模板 | 否 |
| 使用模板 | 全部 | 可见模板 | 可见模板 |
| 查看报告 | 全部 | 本部门 | 本人 |
| 编辑报告 | 全部 | 本部门 | 本人,包括已完成报告 |
| 删除报告 | 全部 | 本部门,包括已完成报告 | 本人,包括已完成报告 |
| 导出报告 | 全部 | 本部门 | 本人 |
已确定权限规则以 权限设计 为准:
- 超级管理员全权限,可修改任何数据。
- 超级管理员默认属于
admin部门。 - 管理员只能看、改、删本部门报告。
- 医生完成报告后可以继续修改,也可以删除本人已完成报告。
- 完成报告后的每次修改必须增加修订版本号。
- 模板按部门授权;管理员可修改本部门模板;医生可复制/新建个人模板。
- 一个部门只能有一个管理员,且只有超级管理员能创建或授权管理员。
- AI 只能接收当前报告内容作为上下文。
后端强校验
每个 API 都必须根据当前用户进行后端权限判断。前端菜单隐藏只是体验优化,不能作为安全依据。
安全改造清单
- 密码使用 bcrypt/argon2 哈希。
- Token 使用 HttpOnly Cookie 或短期 access token + refresh token。
- API 增加 CSRF 或 SameSite 策略。
- HTML 内容用 DOMPurify 或服务端白名单清洗。
- 文件上传限制 MIME、大小、扩展名,并做病毒扫描或隔离策略。
- AI/语音密钥后端加密存储。
- 敏感日志脱敏,禁止记录完整病历到普通日志。
- 报告导出当前不要求水印、原因、审批或专门审计;后续重点是权限控制、错误追踪和后端导出能力。
- 对报告、模板、用户操作增加审计日志。
- 增加数据库备份和恢复策略。
测试改造清单
后端测试
- Auth:登录、登出、刷新、密码错误、禁用用户。
- RBAC:不同角色访问用户、模板、报告、设置 API。
- Reports:新增、编辑、完成、历史、删除、导出。
- Templates:新增、导入、导出、权限过滤、字段配置。
- Files:上传、读取、权限、大小限制。
- Settings:密钥脱敏、配置保存、抽帧设置。
- AI Proxy:请求构造、错误处理、密钥不下发。
前端测试
保留现有 Vitest 测试,并新增:
- API client 401/错误处理。
- Auth Context 登录态恢复。
- 报告列表 API 数据渲染。
- 模板权限数据渲染。
- 用户管理表单 API 提交。
E2E 测试
建议新增 Playwright:
- 管理员登录、创建医生、分配模板。
- 医生登录、新建报告、保存草稿、完成报告。
- 报告管理搜索、查看、导出。
- 模板新增、字段插入、报告套用模板。
- 视频上传和抽帧可用性。
迁移步骤建议
阶段 0:冻结当前功能
- 保留现有
localStorage版本作为 baseline。 - 当前 Vitest 测试继续作为前端契约测试。
- 增加功能验收清单,避免后端化时漏功能。
阶段 1:后端骨架
- 建后端项目、数据库迁移、基础表结构。
- 实现登录、当前用户、用户列表、模板列表、报告列表。
- 前端新增 API client,但先不大规模替换页面。
阶段 2:认证和用户后端化
- 替换
Login、Sidebar、UserManage。 - 移除前端默认账号初始化。
- 前端不再保存密码和完整用户列表。
阶段 3:模板和报告后端化
- 替换
TemplateManage、ReportManage、ReportView、ReportEditor保存逻辑。 - 报告历史改为后端历史表。
- 字段配置由后端提供。
阶段 4:文件和关键帧后端化
- 签名、图片资源、关键帧、视频统一上传。
- 报告 HTML 中从 Data URL 改为文件 URL 或受控资源地址。
阶段 5:AI/语音代理与审计
- 前端调用后端 AI API。
- 后端托管 AI 密钥;语音密钥仍待代理化。
- 增加查看日志、第三方代理调用摘要和错误追踪;报告导出不要求专门审计,后端导出能力另行实现。
阶段 6:生产化
- 部署数据库、对象存储、后端服务。
- 配置备份、监控、日志、权限审计。
- 做安全扫描、数据迁移演练和院内验收。
需要优先决策的问题
- 后端技术栈:Express、NestJS、Fastify,还是已有院内后端技术?
- 数据库:PostgreSQL、MySQL,还是院内指定数据库?
- 部署方式:单机 Docker Compose,还是院内 Kubernetes/服务器?
- 文件存储:本地磁盘、MinIO、S3 兼容,还是院内文件系统?
- 租户模型:只支持单医院,还是要支持多医院/多院区?
- 报告权限:管理员是否只能看同部门,还是可以按模板/患者/手术组授权?
- 完成报告后是否允许医生修改?是否需要审批或留痕版本?
- AI 和语音是否必须在院内网代理,是否允许公网访问第三方模型?