Initialize backendized SurClaw report system
- Add React/Vite frontend for login, dashboard, reports, templates, users, settings, AI, speech, and media workflows. - Add NestJS/Prisma/PostgreSQL backend with auth, dashboard stats, reports, templates, users, departments, settings, files, AI, speech, audit logs, and HTML sanitization. - Add Prisma schema, migrations, seed data, persistent app sessions, Docker/Nginx deployment files, and upload volume configuration. - Add Vitest, Playwright, backend integration tests, and project documentation for requirements, design, permissions, API contracts, testing, deployment, security, and progress. - Configure production local fallback switch and remove unused Gemini direct dependency/env wiring.
This commit is contained in:
660
docs/api-contract.md
Normal file
660
docs/api-contract.md
Normal file
@@ -0,0 +1,660 @@
|
||||
# API 契约草案
|
||||
|
||||
本文档定义后端化时的接口语义和权限边界。当前项目已实现认证、报告、模板、用户/部门、设置和签名文件接口,其余接口仍用于指导后续后端化。
|
||||
|
||||
当前后端骨架已实现:
|
||||
|
||||
- `GET /api/health`
|
||||
- `POST /api/auth/login`
|
||||
- `GET /api/auth/me`
|
||||
- `POST /api/auth/logout`
|
||||
- `GET /api/reports`
|
||||
- `POST /api/reports`
|
||||
- `GET /api/reports/:id`
|
||||
- `PATCH /api/reports/:id`
|
||||
- `DELETE /api/reports/:id`
|
||||
- `GET /api/templates`
|
||||
- `POST /api/templates`
|
||||
- `GET /api/templates/:id`
|
||||
- `PATCH /api/templates/:id`
|
||||
- `DELETE /api/templates/:id`
|
||||
- `GET /api/users`
|
||||
- `POST /api/users`
|
||||
- `GET /api/users/:id`
|
||||
- `PATCH /api/users/:id`
|
||||
- `DELETE /api/users/:id`
|
||||
- `GET /api/departments`
|
||||
- `POST /api/departments`
|
||||
- `PATCH /api/departments/:id`
|
||||
- `DELETE /api/departments/:id`
|
||||
- `PATCH /api/departments/:id/template-permissions`
|
||||
- `GET /api/settings/system`
|
||||
- `PATCH /api/settings/system`
|
||||
- `POST /api/settings/system/reset`
|
||||
- `POST /api/users/:id/signature`
|
||||
- `DELETE /api/users/:id/signature`
|
||||
- `GET /api/files/:id/content`
|
||||
- `GET /api/ai/models`
|
||||
- `POST /api/ai/chat`
|
||||
- `GET /api/speech/iat` WebSocket
|
||||
- `GET /api/library/fields`
|
||||
- `PATCH /api/library/fields`
|
||||
- `GET /api/files`
|
||||
- `POST /api/files`
|
||||
- `DELETE /api/files/:id`
|
||||
|
||||
报告媒体关系表、数据库 Session 和第一版审计日志已实现;查看日志、第三方调用摘要和后端导出接口仍是后续项。
|
||||
|
||||
## 通用约定
|
||||
|
||||
### Base URL
|
||||
|
||||
```text
|
||||
/api
|
||||
```
|
||||
|
||||
### 认证
|
||||
|
||||
建议使用短期 Access Token + HttpOnly Refresh Cookie,或完整 HttpOnly Session Cookie。前端不应保存密码、完整用户列表或第三方服务密钥。
|
||||
|
||||
### 响应格式
|
||||
|
||||
成功:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {},
|
||||
"requestId": "req_xxx"
|
||||
}
|
||||
```
|
||||
|
||||
失败:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "FORBIDDEN",
|
||||
"message": "无权访问该资源"
|
||||
},
|
||||
"requestId": "req_xxx"
|
||||
}
|
||||
```
|
||||
|
||||
### 常用状态码
|
||||
|
||||
| 状态码 | 含义 |
|
||||
| --- | --- |
|
||||
| `200` | 请求成功。 |
|
||||
| `201` | 创建成功。 |
|
||||
| `204` | 删除或无响应体操作成功。 |
|
||||
| `400` | 请求参数错误。 |
|
||||
| `401` | 未登录或登录态失效。 |
|
||||
| `403` | 已登录但无权限。 |
|
||||
| `404` | 资源不存在,或当前用户无权感知该资源。 |
|
||||
| `409` | 唯一约束或状态冲突。 |
|
||||
| `422` | 业务校验失败。 |
|
||||
|
||||
## 核心类型
|
||||
|
||||
### User
|
||||
|
||||
```ts
|
||||
type UserRole = 'super' | 'admin' | 'doctor';
|
||||
|
||||
interface UserDTO {
|
||||
id: string;
|
||||
username: string;
|
||||
role: UserRole;
|
||||
name: string;
|
||||
departmentId: string;
|
||||
departmentName?: string;
|
||||
department?: string;
|
||||
status: 'active' | 'inactive';
|
||||
phone?: string;
|
||||
email?: string;
|
||||
signatureFileId?: string;
|
||||
signature?: string;
|
||||
visibleTemplates?: string[];
|
||||
manageableTemplates?: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
后端不得返回 `password_hash`。
|
||||
|
||||
### Report
|
||||
|
||||
```ts
|
||||
interface ReportDTO {
|
||||
id: string;
|
||||
title: string;
|
||||
patientName: string;
|
||||
hospitalId: string;
|
||||
departmentId: string;
|
||||
departmentName: string;
|
||||
authorId: string;
|
||||
authorName: string;
|
||||
status: 'draft' | 'completed';
|
||||
revision: number;
|
||||
content: string;
|
||||
videos?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
duration: number;
|
||||
fileId?: string;
|
||||
}>;
|
||||
capturedFrames?: Array<{
|
||||
id: number;
|
||||
videoIndex: number;
|
||||
videoName: string;
|
||||
time: number;
|
||||
timeFormatted: string;
|
||||
dataUrl: string;
|
||||
fileId?: string;
|
||||
}>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
已完成报告再次修改时,后端必须递增 `revision` 并创建历史版本。`videos` 和 `capturedFrames` 是前端兼容 DTO 字段,后端持久化到 `ReportMedia` 并关联 `FileResource`。
|
||||
|
||||
### Template
|
||||
|
||||
```ts
|
||||
interface TemplateDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
scope: 'department' | 'personal';
|
||||
departmentId?: string;
|
||||
ownerUserId?: string;
|
||||
content: string;
|
||||
fields: FormFieldDTO[];
|
||||
canUse: boolean;
|
||||
canManage: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
医生个人模板必须只对本人可见和可用。
|
||||
|
||||
## Auth API
|
||||
|
||||
### `POST /api/auth/login`
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "0001",
|
||||
"password": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"user": {
|
||||
"id": "usr_0001",
|
||||
"username": "0001",
|
||||
"role": "doctor",
|
||||
"name": "张医生",
|
||||
"departmentId": "dept_surgery",
|
||||
"departmentName": "外科",
|
||||
"status": "active"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 禁用用户返回 `403 FORBIDDEN`。
|
||||
- 用户名或密码错误返回 `401 UNAUTHORIZED`。
|
||||
- 前端通过 HttpOnly Session Cookie 维持登录态,不保存 access token。
|
||||
|
||||
### `GET /api/auth/me`
|
||||
|
||||
返回当前登录用户。前端启动后用它恢复登录态,并把后端 `doctor` 角色映射为当前前端兼容类型 `user`。
|
||||
|
||||
### `POST /api/auth/logout`
|
||||
|
||||
清除服务端会话或 refresh cookie。
|
||||
|
||||
## Reports API
|
||||
|
||||
### `GET /api/reports`
|
||||
|
||||
查询参数:
|
||||
|
||||
```text
|
||||
q?: string
|
||||
status?: draft | completed
|
||||
dateRange?: today | week | month
|
||||
page?: number
|
||||
pageSize?: number
|
||||
```
|
||||
|
||||
权限过滤:
|
||||
|
||||
- 超级管理员:返回全部报告。
|
||||
- 管理员:只返回本部门报告。
|
||||
- 医生:只返回本人报告。
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"items": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/reports`
|
||||
|
||||
创建报告。后端写入 `authorId`、`departmentId`、`revision = 1`。
|
||||
当前实现接收前端兼容 Report 对象,核心字段进入 `Report` 表,视频/关键帧引用进入 `ReportMedia` 表,其他扩展字段进入 `metadata`。
|
||||
|
||||
### `GET /api/reports/:id`
|
||||
|
||||
权限:
|
||||
|
||||
- 超级管理员可读所有报告。
|
||||
- 管理员可读本部门报告。
|
||||
- 医生可读本人报告。
|
||||
|
||||
无权限当前返回 `403`。
|
||||
|
||||
### `PATCH /api/reports/:id`
|
||||
|
||||
规则:
|
||||
|
||||
- 超级管理员可编辑任何报告。
|
||||
- 管理员可编辑本部门报告。
|
||||
- 医生可编辑本人报告。
|
||||
- 如果原报告状态为 `completed`,每次保存递增 `revision`。
|
||||
- 每次保存创建历史版本。
|
||||
|
||||
### `POST /api/reports/:id/complete`
|
||||
|
||||
把报告标记为完成。完成前必须校验患者姓名、住院号等必填字段。
|
||||
当前实现暂未单独开放该接口,前端通过 `PATCH /api/reports/:id` 传入 `status = completed` 完成报告。
|
||||
|
||||
### `DELETE /api/reports/:id`
|
||||
|
||||
规则:
|
||||
|
||||
- 超级管理员可删除全部报告。
|
||||
- 管理员可删除本部门报告。
|
||||
- 医生可删除本人报告,包括已完成报告。
|
||||
|
||||
建议先做软删除。
|
||||
当前实现已做软删除,写入 `deletedAt` 和 `deletedBy`。
|
||||
|
||||
### `GET /api/reports/:id/history`
|
||||
|
||||
返回报告历史版本:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "hist_1",
|
||||
"revision": 1,
|
||||
"content": "<p>旧内容</p>",
|
||||
"updatedBy": "张医生",
|
||||
"updatedAt": "2026-05-01T08:00:00.000Z",
|
||||
"action": "complete_report"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/reports/:id/export.json`
|
||||
|
||||
返回结构化报告 JSON。权限与查看报告一致。当前确定不需要水印、导出原因、审批或专门导出审计;现阶段仍主要由前端 Blob 导出。
|
||||
|
||||
## Templates API
|
||||
|
||||
### `GET /api/templates`
|
||||
|
||||
权限过滤:
|
||||
|
||||
- 超级管理员:全部模板。
|
||||
- 管理员:本部门模板和被授权模板。
|
||||
- 医生:本部门授权模板和自己的个人模板。
|
||||
|
||||
当前实现支持查询参数 `access=use | manage`,分别返回当前用户可使用或可管理的模板。
|
||||
|
||||
### `POST /api/templates`
|
||||
|
||||
创建模板。
|
||||
|
||||
规则:
|
||||
|
||||
- 超级管理员可创建部门模板并授权给部门。
|
||||
- 管理员可创建或修改本部门模板。
|
||||
- 医生只能创建个人模板,`scope` 必须为 `personal`,`ownerUserId` 必须是本人。
|
||||
|
||||
当前实现由后端根据 Session 用户写入归属部门或归属用户;前端不能伪造 `ownerUserId`。
|
||||
|
||||
### `PATCH /api/templates/:id`
|
||||
|
||||
规则:
|
||||
|
||||
- 超级管理员可编辑任何模板。
|
||||
- 管理员可编辑本部门模板。
|
||||
- 医生只能编辑自己的个人模板。
|
||||
|
||||
### `DELETE /api/templates/:id`
|
||||
|
||||
规则同编辑模板。删除部门模板时需确认是否已有报告引用;建议软删除。
|
||||
当前实现会先把引用该模板的报告 `templateId` 置空,再删除模板。
|
||||
|
||||
### `POST /api/templates/:id/copy`
|
||||
|
||||
医生复制部门模板为个人模板。
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "我的腹腔镜模板"
|
||||
}
|
||||
```
|
||||
|
||||
响应返回新的 `TemplateDTO`,`scope = personal`。
|
||||
|
||||
## Users API
|
||||
|
||||
### `GET /api/users`
|
||||
|
||||
权限:
|
||||
|
||||
- 超级管理员:全部用户。
|
||||
- 管理员:自己和本部门医生。
|
||||
- 医生:只返回自己。
|
||||
|
||||
当前实现返回前端兼容 `User` 结构,其中后端 `doctor` 会映射为前端历史角色 `user`,并带上由部门模板授权计算出来的 `visibleTemplates/manageableTemplates`。
|
||||
|
||||
### `POST /api/users`
|
||||
|
||||
规则:
|
||||
|
||||
- 只有超级管理员可创建管理员。
|
||||
- 一个部门只能有一个管理员,冲突返回 `409`。
|
||||
- 管理员只能创建本部门医生。
|
||||
- 创建医生前,该部门必须已有启用管理员。
|
||||
- 后端使用 Argon2 保存密码哈希,不返回明文密码。
|
||||
|
||||
### `PATCH /api/users/:id`
|
||||
|
||||
规则:
|
||||
|
||||
- 超级管理员可改任何用户。
|
||||
- 管理员可改本部门医生的基础信息、密码和状态。
|
||||
- 医生只能改自己的基础资料。
|
||||
- 只有超级管理员可修改角色、部门和管理员模板授权。
|
||||
|
||||
### `DELETE /api/users/:id`
|
||||
|
||||
规则:
|
||||
|
||||
- 禁止删除当前登录用户。
|
||||
- 禁止删除默认超级管理员 `admin`。
|
||||
- 有报告、模板、审计等业务关联时返回 `409`,建议改为禁用账号。
|
||||
|
||||
## Departments API
|
||||
|
||||
### `GET /api/departments`
|
||||
|
||||
权限:
|
||||
|
||||
- 超级管理员:全部部门。
|
||||
- 管理员和医生:本人所在部门。
|
||||
|
||||
### `POST /api/departments`
|
||||
|
||||
只有超级管理员可创建部门。
|
||||
|
||||
### `PATCH /api/departments/:id`
|
||||
|
||||
只有超级管理员可修改部门名称或编码。
|
||||
|
||||
### `DELETE /api/departments/:id`
|
||||
|
||||
只有超级管理员可删除无业务关联的部门;不能删除当前登录用户所在部门。
|
||||
|
||||
### `PATCH /api/departments/:id/template-permissions`
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"visibleTemplates": ["tpl_a"],
|
||||
"manageableTemplates": ["tpl_b"]
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 只有超级管理员可维护部门模板授权。
|
||||
- `manageableTemplates` 会自动包含使用权。
|
||||
- 后端写入 `template_department_permissions.canUse/canManage`。
|
||||
|
||||
## Settings API
|
||||
|
||||
### `GET /api/settings/system`
|
||||
|
||||
返回当前用户可用的系统设置。当前实现会合并全局设置和个人默认模板,并返回前端兼容 `SystemSettings` 对象。
|
||||
|
||||
注意:AI 对话和讯飞语音均已改为后端代理。普通用户读取设置时不返回 AI Key、讯飞 APIKey 或讯飞 APISecret;超级管理员仍可在设置页维护全局共用 Provider Key 和语音配置。
|
||||
|
||||
### `PATCH /api/settings/system`
|
||||
|
||||
规则:
|
||||
|
||||
- 超级管理员可修改全局设置、AI Provider、语音配置。
|
||||
- 医生可修改个人默认模板。
|
||||
- 管理员可修改个人默认模板。
|
||||
- 非超级管理员提交的其他字段会被忽略或拒绝,只保留个人默认模板。
|
||||
|
||||
### `POST /api/settings/system/reset`
|
||||
|
||||
只有超级管理员可重置全局系统设置。
|
||||
|
||||
## Signature Files API
|
||||
|
||||
### `POST /api/users/:id/signature`
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"dataUrl": "data:image/jpeg;base64,...",
|
||||
"filename": "signature.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 用户本人、本部门管理员、超级管理员可上传或替换签名。
|
||||
- 当前支持 JPG、PNG、WebP,大小限制 1MB。
|
||||
- 后端写入 `FileResource.kind = SIGNATURE`,并把 `User.signatureFileId` 指向新文件。
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"file": {
|
||||
"id": "file_xxx",
|
||||
"url": "/api/files/file_xxx/content",
|
||||
"mimeType": "image/jpeg",
|
||||
"size": 12345
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `DELETE /api/users/:id/signature`
|
||||
|
||||
清除用户签名并删除后端文件资源。权限同上传。
|
||||
|
||||
### `GET /api/files/:id/content`
|
||||
|
||||
返回文件二进制内容。当前已实现签名文件读取权限:
|
||||
|
||||
- 本人可读自己的签名。
|
||||
- 本部门管理员可读本部门用户签名。
|
||||
- 超级管理员可读全部签名。
|
||||
- 报告相关文件预留继承报告权限。
|
||||
|
||||
## AI API
|
||||
|
||||
### `GET /api/ai/models`
|
||||
|
||||
后端读取全局共用 AI Provider 配置,请求 OpenAI 兼容 `/models` 并返回模型 ID 列表。前端“测试连接”使用该接口,不再直接携带 API Key 请求第三方服务。
|
||||
|
||||
### `POST /api/ai/chat`
|
||||
|
||||
规则:
|
||||
|
||||
- 后端托管第三方模型密钥。
|
||||
- 请求上下文只能包含当前报告内容和当前报告内用户有权访问的图片/关键帧。
|
||||
- 不允许跨部门检索报告作为上下文。
|
||||
- 当前实现接收 OpenAI 兼容 `messages`、温度等参数,后端会用全局 Provider 的 `modelName` 覆盖请求中的 `model`,所有用户共用同一套 key。
|
||||
|
||||
## Speech API
|
||||
|
||||
### `GET /api/speech/iat` WebSocket
|
||||
|
||||
浏览器通过当前登录 Session Cookie 发起 WebSocket upgrade。后端使用同一套 Session 中间件校验登录态,读取 Settings API 中的 `xfSpeechConfig`,连接讯飞 IAT WebSocket 并转发音频帧和识别结果。
|
||||
|
||||
客户端发送的首帧只需要包含音频 `data`:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"status": 0,
|
||||
"format": "audio/L16;rate=16000",
|
||||
"encoding": "raw",
|
||||
"audio": "base64-pcm"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
后端会在首帧补齐:
|
||||
|
||||
```json
|
||||
{
|
||||
"common": { "app_id": "server-side-app-id" },
|
||||
"business": { "language": "zh_cn", "domain": "iat", "accent": "mandarin" }
|
||||
}
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 未登录的 upgrade 返回 `401`。
|
||||
- 未配置 APPID/APIKey/APISecret 时,代理向客户端返回错误消息并关闭连接。
|
||||
- 前端不得保存或拼接讯飞鉴权 URL。
|
||||
- 上游讯飞返回的识别消息原样转发给客户端。
|
||||
|
||||
## Files API
|
||||
|
||||
### `GET /api/files`
|
||||
|
||||
查询参数:
|
||||
|
||||
```text
|
||||
kind?: TEMPLATE_ASSET | VIDEO | FRAME | REPORT_EXPORT
|
||||
```
|
||||
|
||||
返回当前租户内可读文件。`TEMPLATE_ASSET` 当前作为模板图片资源,登录用户可读取。
|
||||
|
||||
### `POST /api/files`
|
||||
|
||||
通用文件上传接口。当前已用于模板图片资源、视频和关键帧,后续继续承载报告图片和导出文件。
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "TEMPLATE_ASSET",
|
||||
"filename": "logo.png",
|
||||
"dataUrl": "data:image/png;base64,...",
|
||||
"reportId": "optional_report_id"
|
||||
}
|
||||
```
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"file": {
|
||||
"id": "file_xxx",
|
||||
"filename": "logo.png",
|
||||
"mimeType": "image/png",
|
||||
"size": 1234,
|
||||
"url": "/api/files/file_xxx/content",
|
||||
"createdAt": "2026-05-02T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `DELETE /api/files/:id`
|
||||
|
||||
删除当前用户可管理的文件。超级管理员可删全部,同 owner 可删自己的文件,管理员可删除模板图片资源。
|
||||
|
||||
## Library API
|
||||
|
||||
### `GET /api/library/fields`
|
||||
|
||||
返回字段库:
|
||||
|
||||
```ts
|
||||
interface FieldLibraryDTO {
|
||||
formFields: FormField[];
|
||||
customTimeFormats: string[];
|
||||
multiSelectOptions: Record<string, string[]>;
|
||||
anesthesiaOptions: string[];
|
||||
}
|
||||
```
|
||||
|
||||
### `PATCH /api/library/fields`
|
||||
|
||||
保存字段库。当前第一版保存为租户全局 `SystemSetting.key = fieldLibrary`,前端仍会同步本地兼容缓存。
|
||||
|
||||
文件权限继承业务对象:
|
||||
|
||||
- 报告文件继承报告权限。
|
||||
- 模板图片资源当前登录用户可读取,删除权限限制为超级管理员、管理员或 owner。
|
||||
- 签名文件已先行通过 `POST /api/users/:id/signature` 实现。
|
||||
|
||||
## 后续测试落点
|
||||
|
||||
后端骨架建立后,应把本文档转为真实测试:
|
||||
|
||||
- Auth/HTTP 集成测试。
|
||||
- RBAC policy 单元测试。
|
||||
- Reports API 按角色过滤测试。
|
||||
- Report revision 递增测试。
|
||||
- Templates API 部门模板和个人模板测试。
|
||||
- Settings API 保存和默认模板测试。
|
||||
- Signature Files API 上传和读取权限测试。
|
||||
- Library API 字段库保存测试。
|
||||
- Generic Files API 上传、列表和删除测试。
|
||||
- AI 代理上下文隔离测试。
|
||||
- Speech WebSocket 未登录拒绝、首帧注入和上游错误转发测试。
|
||||
Reference in New Issue
Block a user