Files
goodbuddy-shareserver/docs/features/share-server/technical-design.md
T

735 lines
49 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.
# ShareServer 技术设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.6 |
| 日期 | 2026-10-01 |
| 关联 PRD | [ShareServer PRD](./prd.md) |
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
| 共享协议 | [共享网络总体设计](../../../../goodbuddy/docs/architecture/share-network-architecture.md) |
基于 GoodBuddy 当前源码的适配依据、正常网关接入和基础服务实施顺序见
[后端基础设计](./backend-foundation-design.md)。B1 已实现、验收部分完成,B2 至 B6 待实现;第 4.1、4.2 节记录
当前已实现 API,组织与 Office 章节仍是目标设计,不代表现有菜单已有对应服务。
## 1. 仓库与运行边界
ShareServer 在独立的 `goodbuddy-shareserver` 仓库根目录维护。当前可运行结构为:
```text
goodbuddy-shareserver/
README.md
package.json
server/
src/ # Hono API、测试与同源静态资源服务
tsconfig.json
shared/ # 共享目录、网关 DTO 与保留的旧原型类型
web/
src/ # React Web 控制台与测试
index.html
vite.config.ts
tsconfig.json
vitest.config.ts # Web 与 API 测试统一入口
tsconfig.json # 引用 Web 与服务端配置
dist/ # 生成产物,不提交
web/
server/
docs/features/share-server/
docker/ # Dockerfile、Compose 与构建忽略规则
```
以下是后续生产实现的建议模块结构,尚未创建,不代表已实现:
```text
goodbuddy-shareserver/
README.md
package.json
server/src/
api/
auth/
organizations/
devices/
capabilities/
policies/
approvals/
tasks/
relay/
packages/
office/
federation/
audit/
web/
migrations/
deploy/
tests/
```
依赖与命令统一由根 `package.json` 管理,不拆分 workspace。Vite 输出到 `dist/web/`;
服务端 TypeScript 以仓库根为 `rootDir`,输出到 `dist/server/`,因此启动入口为
`dist/server/server/src/index.js`,共享模块位于 `dist/server/shared/`。根目录运行
`npm start` 或容器启动后,Hono 同源托管 `dist/web/`。Docker 配置仍在 `docker/`,
Compose 的 `../data` 仍映射仓库根 `data/` 到容器 `/app/data`。
仓库根目录包含控制台交互原型、本地共享目录和模型网关。Hono 提供健康、就绪、
界面快照、原型操作确认、真实元数据 API 和网关转发,并同源托管 React/Vite Web 控制台;它不依赖 Electron 的
`src/shared`。本地目录与网关使用独立 JSON 文件持久化,契约见第 4.1、4.2 节。当前尚未建立生产数据库、组织认证、设备控制连接,因此也不提前创建空的
`packages/share-protocol`。进入真实 Desktop 协议实现时,服务端和 Desktop 共同依赖
独立发布的 `share-protocol` 协议包,具体归属在真实协议实现时确定;
ShareServer 仍不得导入 Electron 的 `src/shared`,也不得依赖相邻桌面仓库构建。
## 2. 技术选型约束
本节及下文组织身份、数据库事务、Web session 安全与生产部署要求属于完整组织服务目标。
本地目录和网关按第 4.1、4.2 节实现;Office 私有部署第一阶段按第 10.1 节单独推进,不以前述能力为前置条件。
- 使用与仓库一致的 TypeScript/Node.js 工具链,沿用当前 Hono 框架。
- PostgreSQL 是组织、策略、任务元数据和审计的权威数据库;工作空间消息、处理状态及可靠投递记录
也以数据库为准,具体业务表与受控正文存储在协作模块实现时增加。
- 完整组织服务计划使用 S3 兼容对象存储保存 Package、可选中继暂存和 Office 工作副本。
Office 私有部署第一阶段默认本地文件系统,S3 是后续可选扩展,不是启用 Office 的必需依赖。
- Web 控制台与 API 同源部署,采用严格 CSP,不在浏览器保存设备凭据。
- 单实例部署可完整工作,不为未验证规模预设复杂分布式协议。工作空间实时投递优先评估独立
实时组件,与是否水平扩展分开决策;第 18 节记录候选方案,尚未确定采用。
## 3. 服务模块
| 模块 | 职责 |
| --- | --- |
| `auth` | 本地身份、OIDC、Web session、服务和设备凭据 |
| `organizations` | 组织、成员、组、角色和范围解析 |
| `devices` | 注册挑战、设备记录、出站连接和在线状态 |
| `capabilities` | manifest、数字人 profile、provider、Publication 和目录投影 |
| `policies` | Grant、强制策略和确定性授权计算 |
| `approvals` | 内容绑定申请、决定、过期和通知 |
| `tasks` | task/attempt、provider 路由、状态和取消 |
| `workspaces`(待实现) | 共享关联、成员访问、资料同步、空间消息、线程与处理请求 |
| `delivery`(待实现) | 事务投递记录、实时组件适配、去重、离线补收与历史查询 |
| `relay` | 可选有界数据中继,不解释能力正文 |
| `packages` | 不可变对象、签名目录、通道和撤销 |
| `office` | 编辑会话、工作副本、ONLYOFFICE 配置、callback、checkpoint、保留和健康 |
| `federation` | 网关身份、offer、双边策略和跨组织映射 |
| `audit` | 事务内审计写入、查询、保留和导出 |
模块通过服务接口调用,API handler 不直接拼接跨组织 SQL 或自行计算权限。
## 4. API 边界
建议分为六类 audience:
```text
/api/v1/web/* 管理控制台与成员自助 API
/api/v1/device/* 设备注册、目录和任务控制 API
/api/v1/office/* Desktop Office 会话、文件和事件 API
/api/v1/federation/* ShareServer 间 API
/api/v1/public/* 服务描述、健康与登录入口
/internal/office/* ONLYOFFICE 文件读取和 callback API
```
设备使用 WebSocket `/api/v1/device/connect` 建立出站控制通道。包下载使用短期、绑定主体和对象
摘要的 URL。联邦 API 使用网关身份,不接受普通用户 session。每类 token 使用不同 issuer/
audience/scope,不能互换。Office 启动 code、文件 token 和 callback token 也使用互不相同的
audience,并绑定单个 `DocumentEditSession`。
所有请求经 schema、认证、organization scope、授权、速率和大小边界。列表使用游标分页和固定
最大页;错误返回 request ID、稳定 code 和有界参数。
### 4.1 本地共享目录契约(已实现)
此接口供同机 GoodBuddy 与 Web Demo 联调,默认监听 `127.0.0.1:8787`。没有登录、组织隔离
或设备认证;B1 起只允许直连回环管理请求,符合此限制的客户端均可修改目录。设备 ID 仅标识记录,客户端只显示本机撤销按钮
不构成服务端所有权校验。`productionReady` 保持 `false`。上文 audience、组织授权及 WebSocket
规则是后续组织服务目标,不由这些本地接口实现。
| 方法与路径 | 输入 | 成功响应 |
| --- | --- | --- |
| `POST /api/v1/sharing/devices/register` | `id,name,platform,appVersion` | Device |
| `GET /api/v1/sharing/devices` | 无 | `{devices: Device[]}` |
| `GET /api/v1/sharing/publications` | 可选查询参数 `deviceId` | `{publications: Publication[]}` |
| `POST /api/v1/sharing/publications` | PublicationInput | Publication |
| `POST /api/v1/sharing/publications/:id/revoke` | 路径 ID | Publication |
成功状态码为 200。Device 增加 `registeredAt,updatedAt`,均为服务端 ISO UTC 时间。
PublicationInput 包含 `id,deviceId,name,kind,description,sourceMode,permissions`;
`kind` 为 `capability|knowledge`,`sourceMode` 为 `device|server`,`permissions` 包含
独立布尔字段 `search,read,download`。Publication 增加 `status: published|revoked` 及
`createdAt,updatedAt`。来源和权限目前只保存声明,不传输正文、不执行授权或能力调用。
源码类型见 `shared/sharing.ts`,状态规则见[本地目录逻辑](./logic-design.md#本地目录首期状态规则)。
创建与注册要求 `application/json`,不接收额外字段。ID 长度 1–128,首位为 ASCII 字母或数字,
其余允许字母、数字、点、下划线、连字符。名称最多 200 字符,平台与版本最多 64 字符,描述
最多 4000 字符且可为空。请求体上限 32768 字节。
错误响应为 `{code,message,requestId}`:输入或 JSON 无效返回 400,设备或条目不存在返回 404,
发布 ID 内容冲突返回 409,超限返回 413,媒体类型错误返回 415,存储故障返回 503。
未知 API 路径返回 JSON 404,不进入 SPA 回退。列表暂不分页,适用于本地小规模验证。
`SHARESERVER_DATA_DIR` 指定数据目录,默认 `./data`;其中 `sharing.json` 保存设备和发布记录。
每个目录只供一个进程中的一个 Store 使用。读写排队,写入临时文件并同步后 rename,写失败不更新
内存。坏文件返回存储错误,不自动清空。服务重启重新加载文件;就绪接口不验证存储是否可写。
生产组织服务仍计划使用 PostgreSQL,当前没有文件到 PostgreSQL 的迁移实现。
GoodBuddy 通过 `页面 → preload → 可信 IPC → Main HTTP 服务` 调用,Main 保存稳定设备 ID、
服务地址与设备名称,并设置请求超时。Web 同源读取目录,不保存桌面设备凭据。
两仓独立构建;桌面实现与验证见[设备共享文档](../../../../goodbuddy/docs/features/device-sharing/README.md)。
### 4.2 本地模型网关契约(已实现)
实现位于 `server/src/gateway-routes.ts`、`gateway-store.ts` 和 `shared/gateway.ts`,挂载到
`/api/v1/gateway`。B1 已实现本机管理、托管凭据和按模型授权的 Bearer 访问 Key,没有组织隔离、
用户组策略、配额或速率/并发控制。客户端 Key 不转发给上游。就绪接口报告网关边界,保持
`productionReady=false`、`storageChecked=false`。网关独立构建运行,不导入 GoodBuddy 源码。
下表路径均相对于 `/api/v1/gateway`,成功返回 200:
| 方法与路径 | 输入与行为 | 响应 |
| --- | --- | --- |
| `GET /config` | 读取配置、调用元数据和部署指定地址 | `{upstreams,models,audits,gatewayBaseUrl}` |
| `POST /upstreams`、`PUT /upstreams/:id` | `name,baseUrl,protocol,authentication,enabled,credentialAction`;仅 `replace` 带 `apiKey` | 上游公开字段及 `credentialConfigured`,无秘密或密文 |
| `POST /models`、`PUT /models/:id` | `alias,upstreamId,remoteModel,enabled`,可选 `displayName` | 模型映射 |
| `GET /access-keys` | 列出 Key 元数据 | `{accessKeys}`,不含摘要或原文 |
| `POST /access-keys` | `{name,modelIds}`,至少一个不重复的模型记录 ID | `{accessKey,key}`,完整 Key 仅此次返回 |
| `POST /access-keys/:id/revoke` | 撤销 Key | Key 元数据,重复撤销保留原 `revokedAt` |
| `POST /upstreams/:id/discover` | 无请求体;向已启用上游的 `/models` 发 GET | 去重模型 ID 列表 `{models: string[]}`,不自动保存映射 |
| `POST /test` | 本机管理测试,使用 Chat 字段,仅非流式 | 真实 JSON 响应,复用转发和审计,`accessKeyId=null` |
| `GET /v1/models` | Bearer Key;仅列授权、启用且上游启用、凭据可用的模型 | OpenAI 模型列表,模型 ID 为别名 |
| `POST /v1/chat/completions` | Bearer Key 与模型授权;按别名转发 | JSON 或 SSE,响应 `model` 改回别名 |
除两个 `/v1` 客户端端点外,网关管理和旧共享目录 API 均通过 `admin-boundary.ts` 校验实际
`incoming.socket.remoteAddress`,只接受 `::1`、`127.*` IPv4 及其 IPv4-mapped 地址;没有 socket
信息也拒绝。含 `Forwarded`、`X-Forwarded-*` 或 `X-Real-IP` 的管理请求返回 403,不采信其来源声明。
Origin 允许无值、同源、`http://127.0.0.1:5174` 和 `http://localhost:5174`;来源不符同样拒绝。
Vite 在代理前对所有 `/api` 请求检查原始 socket,仅限回环。容器和任意反向代理管理不受支持,
须等待 B2 登录;无转发头的本机代理不能被当作管理员身份。健康与就绪 GET 不受管理限制。
Key 使用 `ss_` 加 32 个随机字节的 base64url 编码,存储 SHA-256 摘要。`modelIds` 绑定映射
记录的 UUID,修改别名不改变授权。Key 名称不代表用户身份;缺失、格式错误、未知或已撤销的 Key
返回 401,未授权或不存在的请求模型返回 403。撤销阻止后续请求,不主动终止已经开始的流。
客户端 Base URL 为 `http://127.0.0.1:8787/api/v1/gateway/v1`。仅支持上述模型列表与 Chat
Completions,不实现 Responses、Anthropic 或其他推理端点,不执行工具。Chat 顶层字段采用
`CHAT_FIELDS` 白名单,包含消息、流式选项、采样参数、`tools/tool_choice/parallel_tool_calls`、
兼容的 `functions/function_call`、`response_format`、`reasoning_effort` 等;未知字段及地址/凭据
覆盖报错。消息角色允许 `system/developer/user/assistant/tool/function`;具体参数语义由上游处理,
不保证每个上游支持全部字段。工具调用数据和 usage 随响应透传。
配置输入严格匹配字段。名称、显示名和远端模型名最多 200 字符,新建或改名的别名最多 128 字符;
已有最长 200 字符的别名可继续使用和原名编辑,接入 GoodBuddy 前需显式改为其支持的长度。
别名以 ASCII 字母或数字开头,其余可含字母、数字、点、下划线、冒号、斜杠和连字符。
上游、模型记录和 Key ID 为服务端生成的 UUID v4。Base URL 最多 2048 字符,允许 HTTP/HTTPS
及合法路径,去掉尾斜杠,禁止用户信息、查询和片段;请求不跟随重定向。支持可信内网 HTTP
上游,但 HTTP 不加密传输凭据,对外部署使用 HTTPS;该校验不构成生产出站访问策略。
`protocol` 仅支持 `openai-chat-completions`,`authentication=api-key|none`。
`credentialAction=keep|replace|clear` 显式保留、替换或清除凭据;空输入不代表清除,选择 `none`
不自动删除已有凭据。替换时 `apiKey` 最多 16384 字符,使用 AES-256-GCM、随机 nonce、认证标签,
并以稳定上游 ID 作为 AAD;密文随上游一起原子保存。`credentialConfigured` 只反映密文或有效
环境引用是否存在,不保证解密或连接成功。
`SHARESERVER_MASTER_KEY_FILE` 指向数据目录外的秘密文件,也可用 `SHARESERVER_MASTER_KEY`
提供 64 位十六进制主密钥,两者不能同时设置。文件路径及真实路径均须在数据目录外。
初始化命令为 `npm run gateway:init-key -- <文件路径>`,父目录须已存在,拒绝覆盖已有文件;
Windows 可运行示例见根 README。主密钥与普通数据分开备份;缺失、错误或无法解密时返回
`CREDENTIAL_UNAVAILABLE`,不重新生成或覆盖。替换任一凭据前先检查既有密文能否解密。
旧版本 `apiKeyEnv` 记录仍可读取并在调用时解析 API 环境,显式替换/清除后移除引用;
新表单及 API 不接受变量名,`/import-env` 已移除。环境文件仅供旧引用及授权开发测试加载:
```text
npm run dev -- --env-file=../goodbuddy/.env.deepseek.local
```
`scripts/dev.mjs` 用 `parseEnv` 加载指定文件,仅将文件中的变量传给 API 子进程,Vite 不接收
这份加载结果,仅接收派生的 `SHARESERVER_API_PORT` 以同步代理目标;已有进程环境优先。
环境变更需重启 API。该路径仅是可选的授权测试输入,
不构成 GoodBuddy 依赖,不自动读取或迁移个人配置;使用既有个人配置须有明确的环境测试授权。
`SHARESERVER_GATEWAY_BASE_URL` 设置完整客户端 Base URL,`/config` 将其返回为 `gatewayBaseUrl`;
未配置时返回 `null`,界面提示配置,不从请求 Host 或 Vite 地址推断。自定义端口或异机访问须
设置客户端可达的地址并保留 `/api/v1/gateway/v1`。
请求体要求 JSON,读取上限为 1 MiB;上游 JSON 响应、发现结果及 SSE 总字节上限为 16 MiB。
SSE 单事件及未分帧缓冲的字符串长度上限为 1,048,576 个 JavaScript UTF-16 码元,不能视为
1 MiB 字节上限。默认超时 120 秒,聊天从读取入站请求到完成流共用该期限;取消会中止上游。
SSE 必须有 `[DONE]`,中途 EOF 记 `INCOMPLETE_STREAM`;聊天 JSON 结果/SSE JSON 数据事件须有
`choices` 数组且无 `error`。流已发出后,失败通过错误事件或流中断报告,HTTP 200 不代表调用成功。
错误包为 `{error:{code,message},requestId}`,不回传上游错误正文。输入无效 400,Key 无效 401,停用、未授权模型或 Origin
拒绝 403,不存在 404,别名冲突 409,请求超限 413,非 JSON 415,上游故障或响应无效 502,
缺凭据/存储故障 503,超时 504。聊天审计的取消状态记 499;这不是承诺客户端能收到的 HTTP 状态。
管理请求按上述 socket 和 Origin 规则校验;聊天端点也检查 Origin。未识别路径在通过管理边界后返回 JSON 404。
存储默认 `data/gateway.json`,跟随 `SHARESERVER_DATA_DIR`。文件缺失时从空上游、映射和审计
开始,首次写入创建文件,无预置服务或调用;每个数据目录只允许单进程中的一个 GatewayStore。
写入排队,经临时文件、文件同步和 rename 后更新内存;坏文件返回存储错误,不自动重置。
配置无删除 API,可编辑或停用;当前不提供配置修订冲突控制或 PostgreSQL 迁移。
存储版本 2 包含上游密文、映射、Key 摘要及元数据与最新写入的 200 条聊天终态审计;版本 1
可读取并在下次写入时保存为版本 2,保留原 ID 和旧环境引用。审计新记录在前,仅含 ID、时间、
已识别模型别名、上游 ID、访问 Key ID、是否流式、成功/失败/取消、HTTP 状态、耗时、输入/输出 Token 和
稳定错误码;未识别别名不保存用户原始值。用量只取上游有效非负整数,缺失为 `null`,不估算为零。
审计不保存提示词、工具参数、响应正文或凭据;配置变更与模型发现不生成聊天审计,不具备完整管理审计。
写入失败不能据此确认审计已保存,流中已发送的数据也不能撤回。
本节按当前源码描述契约。`gateway.test.ts` 中的测试用例不等同于本次运行证据,实测结果与日期
由 `progress.md` 维护;历史模拟网关的通过记录不追溯作为真实转发验收。
## 5. 数据模型
下列数据库模型属于组织服务目标设计。本地目录和网关使用第 4.1、4.2 节的文件存储,不将本地设备 ID 当作组织身份。
核心表建议为:
```text
instances
identity_providers
users
organizations
memberships
groups
group_members
role_bindings
registered_devices
device_credentials
device_connections
capabilities
capability_versions
agent_profiles
providers
publications
grants
policies
approval_requests
approval_decisions
tasks
task_attempts
task_events
package_objects
package_releases
release_channels
update_policies
document_edit_sessions
document_checkpoints
federation_trusts
federation_capability_exports
audit_events
revocations
```
要求:
- 所有组织数据表包含不可为空的 `organization_id`,并由 repository 层强制作用域。
- 唯一约束同时包含 organization,避免跨组织名称冲突泄漏。
- 密钥保存为单向摘要或经外部主密钥加密的密文;数据库不保存明文 token。
- 在线连接和短期 nonce 主要在内存;表中只保存恢复所需元数据,不把 socket 当持久对象。
- task event 只存控制状态和有界统计;正文流不进入普通关系表。
- `agent_profiles` 只保存组织内唯一 handle、显示元数据和其能力版本引用。数字人复用
Publication、Grant、Approval 和 Task,不创建第二套授权或路由模型;本地 Agent 记忆条目、
分区目录和来源正文不进入 ShareServer 数据库。
- 数字人 Publication revision 保存有界 `memory_source_policy`,只允许 `agent`、`global`、
`project` 和 `automation` 种类及 Provider 验证后的本地关联 ID 摘要。Provider 执行时按该
revision 在本地重新解析分区并检索;服务端不接收记忆文本、来源正文、Embedding 或检索
结果。新增来源是权限扩大,移除来源是单向收紧。
- 上述记忆限制针对 Provider 私有记忆授权。FR-18 明确发布的知识或回顾副本走独立共享内容
授权与存储,不借 Publication 内存策略上传私有记忆或开放记忆浏览。
- `document_edit_sessions` 只保存组织、用户、设备、公开来源 ID、显示名、格式、对象键、摘要、
访问模式、策略 revision、状态和保留期限,不保存 Desktop 绝对路径。
- `document_checkpoints` 以 session 内单调 revision 保存对象键、大小、SHA-256、callback 幂等键
和时间;`source_commit_confirmed` 只表示 Desktop 回报结果,不由 callback 自动填写。
## 6. 事务与一致性
- 业务变更与对应审计事件在同一 PostgreSQL 事务提交。
- Publication/Grant/Policy 使用 revision 做乐观并发,Web 草稿不能覆盖他人更新。
- 授权决定创建 task 时再次读取当前 revision,并将命中策略摘要冻结到 task。
- 设备在线状态允许短暂最终一致;身份、撤销、授权和任务接受必须走权威存储。
- 重复注册挑战、联邦 offer 和 task create 使用唯一幂等键。
- 无法确认 provider 副作用时只更新 `outcome_unknown`,不靠消息队列自动重放。
- 工作空间消息/状态与 outbox 待投递事件在同一数据库事务提交,提交成功才确认已发送。
投递重试与客户端补收只传播已有事件,不重建 Task 或重放 Provider 副作用。
- Office callback 的临时对象、checkpoint 行和审计事件在同一提交边界发布。数据库提交失败时
临时对象保持不可下载并由清理任务删除;重复 callback 依赖会话内幂等键返回已有 revision。
## 7. 设备连接与任务
连接建立后完成设备 credential、organization、客户端协议和当前 registration 校验。服务端为
每个连接设置心跳、空闲、帧、发送队列和并发上限。
任务流程:
```text
authorize -> create task -> resolve exact provider connection
-> send declaration -> provider accepts/rejects
-> establish direct or relay route -> stream progress
-> provider terminal result -> caller delivery -> audit terminal state
```
同一设备重复连接时使用 connection generation;新连接接管未来任务,旧连接仍只允许完成或
查询已归属 task,随后有界关闭。不能把新任务随机发到身份相同但 generation 过期的 socket。
## 8. 中继
中继是可选模块:
- 控制面与中继可部署在不同进程,但共享 task 授权验证。
- 每个 relay ticket 绑定 task、调用方、provider、方向、期限和最大字节。
- 实现背压和全局/组织/设备并发及带宽上限。
- 不解析能力正文,不写普通日志;需要临时落盘时使用加密 GoodBuddy-owned 暂存并按 task 清理。
- 中继关闭、过载或策略禁止时返回准确路由错误,不静默转到其他节点。
## 9. Package 存储
上传先进入不可公开暂存:流式计算大小和 SHA-256,验证签名、manifest、版本、平台和权限后,
以 digest 发布为不可变对象。相同 digest 可去重,但组织授权和 Release 分别保存。
Release catalog 使用独立签名域。服务器不能重打包已签名 payload;镜像目标必须保持相同字节、
大小和 SHA-256。撤销更新目录和授权,不覆盖或原地删除仍受审计保留的对象。
## 10. Office 编辑服务
### 10.1 私有部署第一阶段(待实现)
本节是首期容器、配置、接口和持久化的权威设计,范围以 PRD 第 3 节为准。当前 `/documents`
显示待接入说明,仓库只有 ShareServer 的 `docker/compose.yaml`,没有 Office overlay 或真实编辑链路。
第一阶段采用单个 ShareServer 进程、独立 Document Server 和本地文件系统,不新增身份源、数据库、
S3/MinIO、独立 callback worker 或启动 code/安全 Cookie 交换。仅适用于可信私有网络;当前无认证,
可访问服务者可操作试验文档,随机文档 ID 和 JWT 均不能据此宣称用户授权或组织隔离。
#### 容器与地址
后续实现拟增加可选 `docker/compose.office.yaml`,与现有 Compose 叠加使用;该文件尚未创建,
现有默认启动不启用 Office。Document Server 与 Hono 分进程、分容器,通过 Docker 网络 HTTP
取源文件和回传结果。镜像 edition、固定版本 tag/digest 待选型验证,不使用浮动 latest 作为验收基线。
Developer Edition 仍是完整产品候选;私有 Web 编辑的许可证与后续 Desktop/Agent(含 Automation
API 或插件)的授权分别核验,不由容器能启动推定已获授权或功能兼容。
ShareServer 继续绑定宿主 `data/` 到 `/app/data`,Office 文件放在其中的 `office/`。Document Server
所需数据、日志等挂载使用独立宿主目录(拟为 `data/onlyoffice/` 下各子目录),按选定镜像确认挂载点
和 UID/GID,不使用 named volumes,也不把 ShareServer 文档目录挂给它写入。主文档只由 ShareServer
提交;Document Server 缓存不作为主文档备份。备份需包含 Office 文件及其版本元数据,可停写后复制。
以下是拟定配置项的语义,尚未加入环境变量或配置文件:
| 配置 | 谁使用及用途 |
| --- | --- |
| Office 启用开关 | ShareServer;默认关闭,故障不影响其他服务 |
| ShareServer 浏览器 URL | 浏览器访问 Web 与同源 API |
| Document Server 浏览器 URL | 浏览器加载 `web-apps/apps/api/documents/api.js`、编辑器及其连接;必须在客户端可达 |
| ShareServer 内部 origin | Document Server 下载源文件、发送 callback;例如 Docker 服务地址 `http://shareserver:8787` |
| Document Server 内部 origin | ShareServer 请求保存输出或命令;例如拟定服务名 `http://onlyoffice` |
| JWT 共享秘密 | 两服务配置一致,仅保存在部署环境或秘密文件,不返回原始秘密给浏览器 |
| 上传/保存大小、请求超时和存储额度 | 设置有限值,首期只允许 DOCX;实现前确定默认值并在 UI 展示上传上限 |
浏览器地址与容器内部地址分开配置,不能把 Docker 服务名当作用户浏览器可解析的域名,也不能把
容器内 localhost 当另一服务。反向代理需支持编辑器连接;HTTPS 页面所用编辑器地址也需 HTTPS,
按实际部署配置 CSP。ONLYOFFICE 对私有地址请求的过滤开关、默认值及例外配置须查验选定版本,
只允许所需的 ShareServer 内部访问,不假设容器网络互通就能下载文件。
#### 最小接口与协议
拟在 `/api/v1/office/` 提供 DOCX 上传、文档列表/详情及按文档 ID 打开编辑会话的接口,供同源
Web 使用;在 `/internal/office/` 提供按会话关联的源文件读取和 callback。具体路径在实现时定版,
不复用第 10.2 节的 Desktop 凭据流程。服务端生成文档 ID、存储文件名和会话 ID,记录基准 revision、
编辑轮次与 `document.key`;客户端只提交 ID,不接收绝对路径、路径片段或任意源文件 URL。
上传按流限制字节数并验证 DOCX 实际格式,不仅检查扩展名/MIME;返回的保存文件同样限制大小和格式。
1. 上传持久化后返回文档 ID。打开时解析服务器记录,复用当前活动编辑轮次,或从最新已保存版本
建立新轮次;将源文件 URL、`document.key` 和 `editorConfig.callbackUrl` 组成编辑器配置。
2. 配置由 ShareServer 用共享秘密签发 JWT,浏览器只取得配置及签名 token。按所选版本的
[JWT 协议](https://api.onlyoffice.com/docs/docs-api/additional-api/signature/)配置算法、签名范围、
入站/出站 token 开关及传递位置;callback 验签并核对受签名保护的 payload、key 与会话关联,
不仅检查存在一个 token。源文件请求按该版本支持的出站 JWT 校验方式验证并解析关联版本。
3. 按 [callback 协议](https://api.onlyoffice.com/docs/docs-api/usage-api/callback-handler/)处理状态。
status `2`/`6` 才下载保存输出;其他状态按逻辑设计第 8.2 节更新,不误取不存在的 URL。
`document.url` 和 callback 地址由固定配置生成,浏览器不能指定回调目的地。
4. callback 中的输出 `url` 即使签名有效,也只允许明确配置的 Document Server origin。
若输出使用浏览器 origin,按配置的固定映射转换到内部 origin,保留合法路径和查询;禁止任意
主机和用户信息,不跟随重定向。下载有超时和字节上限,不开放通用 URL 抓取接口。
#### 持久化与保存确认
复用单进程文件存储方式,但 Office 使用独立元数据,不混入 `sharing.json`。每个文档的保存按队列
串行提交:先将输出写入同一文件系统的临时文件,验证并计算 SHA-256,再同步、rename 为不可变
版本文件;随后以临时文件、同步、原子 rename 提交该文档的元数据,包含当前 revision、文件引用、
摘要、轮次状态和已处理保存记录。元数据是发布点,读取只跟随已提交引用;崩溃留下的未引用文件
不成为当前版本,重启从元数据恢复,坏元数据报错而非清空。文件与目录同步要求随宿主文件系统验证。
保存记录结合轮次 key、status、协议中可用的保存标识及内容摘要去重,不假设 callback 自带唯一事件
ID,也不以临时 URL 作为唯一幂等依据。首期由服务端触发的 forcesave 串行发起,用该版本支持的
`userdata` 关联单调保存序号;首期不启用额外的定时/编辑器按钮 forcesave,后续启用前须核验其
身份与排序,不能识别的并发保存不盲目覆盖。最终保存与迟到快照的优先级见逻辑设计第 8.2 节,协议字段映射及重复、
乱序用例是实现前的验证项。活动 key 不因 forcesave 或持久化 revision 增长而改变。
首期在 Hono 回调处理中完成持久化后才返回协议成功响应 `{"error":0}`;失败返回协议错误并保留
上一个成功版本。不能先确认再在内存中异步写盘。未来若改为异步处理,必须先形成可恢复的持久任务
才确认接收,且另行报告实际保存结果;本阶段不为此引入新 worker。成功记录与版本引用一同提交,
确认丢失后重复 callback 不重复增版。服务重启不自动轮换仍未收束的 key;先按持久轮次状态核对
Document Server,无法确认时阻止覆盖并提示待确认。
#### 第一阶段验证
基础 liveness 只证明 HTTP 进程可访问,不在周期健康探针里编辑文档。Office 启用验收另做真实往返:
浏览器加载编辑器,Document Server 下载 DOCX,编辑并收到 callback,ShareServer 持久化后关闭/
重新打开可见修改;重启 ShareServer 后再次验证。记录固定镜像版本、edition、地址组合及验证时间。
同时验证 forcesave 不更换 key、同轮多开复用 key、最终保存与重复/迟到回调、磁盘失败、下载超时、
非法 JWT/文档 ID/格式/超限上传、非配置输出 origin 和重定向均不造成错误覆盖或虚假成功。
端口健康或 UI 模拟结果不能替代上述验收,完成后仍不将 `productionReady` 改为 true。
### 10.2 后续 Desktop API 与部署边界
文档以 ShareServer 为主存储,GoodBuddy 承担日常操作。个人创建者可删除或转移给组织,转移
后删除按组织权限判断;管理元数据可见性不授予个人正文访问权。服务端主文档与编辑会话分开
管理,主文档纳入备份及自身删除/保留规则,不随会话过期清理。
本节及第 10.3 节的上传、检查点下载和 `source-commit` 契约描述显式的本地来源导入/回写
模式,继续保留 Desktop/SSH 源文件基准校验和冲突处理。服务端主存模式不要求本地回写确认;
其具体存取接口未在本次演示中实现,不由会话 API 推定已经具备文档持久化。
目标设计集成 ONLYOFFICE Docs Developer Edition,但不把 Document Server 代码并入 Hono
进程。后续组织部署可扩展为以下结构,S3 和独立 worker 不属于第一阶段依赖:
```text
GoodBuddy Desktop -> TLS reverse proxy -> ShareServer Office API
|-> S3 compatible object storage
|-> ONLYOFFICE Document Server
`-> 可选独立 Office callback worker
```
Desktop 经 ShareServer 获取编辑配置,编辑器仍需可达的 Document Server 浏览器 URL。
内部地址由部署配置确定,源文件和 callback URL 会进入编辑器配置,不承诺对浏览器隐藏;
JWT 原始密钥和对象存储凭据仅由服务端保管。正式采用前必须完成许可证和桌面分发审核。
```text
POST /api/v1/office/sessions
PUT /api/v1/office/sessions/{id}/source
POST /api/v1/office/sessions/{id}/launch
GET /api/v1/office/sessions/{id}
GET /api/v1/office/sessions/{id}/events
GET /api/v1/office/sessions/{id}/checkpoints/{revision}
POST /api/v1/office/sessions/{id}/source-commit
DELETE /api/v1/office/sessions/{id}
GET /internal/office/files/{objectToken}
POST /internal/office/callback/{callbackToken}
```
Desktop 使用设备凭据创建会话,只提交公开来源 ID、显示名、MIME、声明大小和 SHA-256。上传按
流读取并校验真实格式,不接受 Desktop 指定对象键。`launch` 返回一次性 code;编辑页面交换为
HttpOnly、Secure、SameSite Cookie 后 code 立即失效。
ONLYOFFICE 文件 URL 和 callback URL 由 ShareServer 生成固定 origin,token 绑定 session、
revision、动作、大小和期限。接口不接受调用方提供的任意 callback URL。
### 10.3 保存检查点
callback 输出先写入随机临时对象,完成大小、摘要、格式和普通对象检查后发布不可变 checkpoint。
ShareServer 随后通过设备事件通道通知 Desktop。只有 Desktop 下载复核并回报
`source_commit_confirmed` 后,服务端审计才记录源文件已回写;冲突、超时和未知结果使用独立
状态,不能把“编辑器保存成功”翻译为“本地文件已保存”。
### 10.4 编辑器插件和 AI
ShareServer 托管固定版本的 GoodBuddy ONLYOFFICE 插件,并在会话配置中精确启用。插件只交换
有界选区描述、文档 revision、白名单编辑操作和非正文状态事件。它不能接收 JavaScript、宏、
任意 ONLYOFFICE 方法名或 ShareServer 管理 token。
AI 请求仍由 GoodBuddy Desktop 的 Conversation 和 Runtime 执行。Ask 只能生成建议;Execute
成功取得绑定 session、revision 和选区的短期 Grant 后,插件才可在单个撤销上下文中应用修改。
ShareServer 负责中继有界事件,不持久化选区正文、Prompt 或模型输出。
### 10.5 生命周期和清理
- 会话、工作副本、checkpoint、启动 Cookie 和对象 token 分别设置明确期限。
- 服务端主文档独立保留,清理临时会话对象不得连带删除主文档。
- 关闭、撤销或到期立即拒绝新读取、保存和 AI 操作。
- 清理使用对象标签和数据库状态,重复执行保持幂等,不根据用户文件名构造删除前缀。
- 法定保留只能延长明确匹配的工作副本、checkpoint 或审计元数据,不能恢复访问授权。
- 备份恢复不恢复编辑 Cookie、启动 code、文件 token、callback token 或活动编辑连接。
## 11. 联邦协议
- 每个实例生成网关身份和可轮换证书,私钥不进入数据库备份明文。
- offer 包含双方可人工核对的组织名、端点、指纹、协议和 nonce。
- 双方确认后交换有界 capability projection 和本地映射 ID。
- 跨组织 task 带双方关联 ID 和短期签名声明;每端独立授权并各写审计。
- 不接受调用方提供的任意回调 URL、第三方 hop 或内部主体 ID。
- 证书轮换需要旧身份签名或双方重新确认;身份突变直接暂停关系。
## 12. Web 安全
- 使用 HttpOnly、Secure、SameSite cookie 和 CSRF 防护管理 Web session。
- OIDC 使用 state、nonce、PKCE 和固定回调地址。
- 富文本字段按纯文本显示;manifest/schema 使用安全代码查看器,不执行 HTML。
- 权限隐藏仅改善界面,所有 API 在服务端重新校验。
- 登录、设备注册、目录、审批、导出和联邦接口分别限速。
- 密钥字段只能替换、轮换和测试,永不回显。
- 编辑页面使用独立 CSP 和 Cookie scope;ONLYOFFICE origin 不能访问管理控制台 session。
- Document Server 默认禁止任意出站网络、宏和未批准插件。编辑文件视为不可信内容。
## 13. 部署与配置
最小生产部署:
```text
TLS reverse proxy/load balancer
ShareServer API + Web
PostgreSQL
可选:S3 compatible object storage
可选:Relay worker
可选套件:ONLYOFFICE Document Server(存储与回调处理按阶段选取,见第 10 节)
```
部署配置通过环境变量或挂载文件提供数据库、主密钥、公开 URL、OIDC、邮件、对象存储和限制。
普通 Web 管理员不能修改基础设施秘密。提供数据库迁移命令、不可变镜像、非 root 容器、健康
与就绪端点、优雅停止和部署示例。
Office 第一阶段配置与独立往返验收见第 10.1 节;后续组织部署增加所选对象存储 bucket、
组织/用户并发、会话期限和 checkpoint 保留。管理界面不能回显密钥。基础 liveness/readiness
不代表 Office 编辑可用,完整 Office 验收须验证编辑器加载、callback 和持久化输出。
## 14. 可观测性与备份
- 结构化日志只包含 request/task/organization 关联 ID、稳定事件和错误码。
- 指标覆盖请求、WebSocket、授权结果、任务状态、中继、Package、Office 会话/callback 和联邦,
不包含能力正文、文档正文或选区。
- tracing 跨内部服务传播关联 ID,跨联邦只传播双方同意的关联标识。
- 备份 PostgreSQL、服务端主文档、Package 对象、策略要求保留的 Office checkpoint 和网关身份;不备份缓存、
Web session、nonce、Office 短期 token 或中继暂存。
- 恢复后撤销列表、组织 ID、签名目录和审计序列保持有效,所有设备重新连接。
## 15. 测试策略
### 15.1 自动化
- schema、认证 audience、CSRF、OIDC 和设备挑战。
- 每个 repository/API 的跨组织隔离与枚举防护。
- 拒绝优先授权、审批内容绑定、预算和并发。
- device generation、离线、撤销、重连和 provider 冻结。
- task 幂等、取消、结果未知和不自动重放。
- Package 流式校验、不可变性、权限 diff 和撤销。
- 联邦双方确认、策略交集、暂停、证书变化和禁止第三 hop。
- 审计事务、过滤、分页、导出和正文缺失。
- Office token audience、上传格式/摘要、callback 幂等、checkpoint 不可变、保留和清理。
- Ask 拒绝编辑写入,Execute Grant 绑定 session、revision 和选区且不能跨文档复用。
### 15.2 集成与部署
- Desktop 与真实 ShareServer 完成注册、目录、调用、文件和取消。
- 两个隔离组织验证搜索、计数、错误、对象和审计不泄漏。
- 两个 ShareServer 完成联邦建立、跨组织调用和单方撤销。
- 中继关闭与开启、容量耗尽、连接中断和大文件背压。
- 数据库备份恢复、版本升级、滚动停止和设备重连。
- 生产 TLS、OIDC 和对象存储组合的端到端验证。
- Windows、macOS、Linux Desktop 与同一 ShareServer 打开、编辑和保存 DOCX、XLSX、PPTX。
- 多文档会话隔离、同一源文件多开只读、源文件冲突、断线恢复、callback 失败和过期清理。
- ONLYOFFICE 真实版本、许可证允许的并发矩阵、浏览器兼容、CPU/内存和大文件边界。
设计完成不代表服务已实现;只有独立部署和真实 Desktop 端到端路径通过后才能更新实施状态。
## 16. 当前控制台实现
Hono 与 `@hono/node-server` 提供 API 和构建后的同源 Web 托管。Web 沿用 React、Vite、
TypeScript、Tailwind CSS 和 Radix。部署已有非 root 多阶段 Docker 镜像。
`web/src/App.tsx` 不再请求原型快照,不显示虚构身份或角色切换。概览显示服务入口,
`/capabilities` 通过 `sharing-center.tsx` 与 `local-sharing.tsx` 读取真实设备和发布记录;
来源与知识权限只是声明,不代表正文、检索或调用已可用。
`/model-gateway` 的 `gateway-page.tsx` 使用第 4.2 节真实 API 保存配置、发现模型、发起非流式
测试和读取审计;测试正文仅在组件内存,卸载取消在途请求。`/distribution` 只显示待接入说明
与网关链接。文档和其他未实现模块显示待接入状态,没有示例列表或模拟操作。
服务端仍保留旧 `/api/v1/web/prototype/*` 接口及 fixture,但它们已退出控制台调用路径,不能
作为组织业务 API。`/api/v1/public/ready` 仍报告 `mode=interactive-prototype`、
`productionReady=false` 和存储未检查。历史模拟测试不作为真实业务验收。
B1 正常网关接入代码已实现,真实验收部分完成,范围见实施进度。组织服务需补身份、PostgreSQL 与授权;Office 私有
试验可按第 10.1 节独立推进。保存网关模型不会自动下发给 GoodBuddy,配置交付仍未实现。
## 17. 工作空间协作的待实现契约
本节记录 FR-15 至 FR-18 对服务端的最低契约要求,尚未确定具体 API 路径、数据库迁移和协议
包版本;跨产品网络架构继续以关联总体设计为准,进入开发前须完成 Desktop/Server 联合评审。
| 对象或契约 | 服务端要求 |
| --- | --- |
| 共享关联 | 本地 workspace ID 与服务端共享 ID 分开;绑定 organization、owner、成员和 revision;本地未共享空间不注册到服务端 |
| 资料同步请求 | 携带共享关联、客户端操作 ID、基准 revision 和增量游标;逐项返回接受、拒绝或冲突,不使用客户端时间决定权限 |
| 空间消息 | 保存发送者、message ID、thread ID、提及身份、上下文版本与受众;普通消息不创建处理任务 |
| 处理请求 | 显式意图关联一条消息,保存唯一处理人、本人确认标记和接受时的偏好版本;提及与处理人分开 |
| 处理偏好 | 按空间和成员保存模式、分身绑定、来源授权引用及 revision;只应用于处理请求,绑定 Publication 沿用能力权限检查 |
| 消息事件与历史 | 稳定事件身份、服务端顺序及对象 revision;支持增量补收和历史分页,资料暂停不停止消息事件 |
| 执行与回复 | 对消息处理版本、任务关联和回复 ID 建立幂等约束;接手与发布以同一权威版本比较,防止迟到结果发布 |
| 回顾与知识 | 引用来源身份、版本、受众和动作授权;明确共享的内容副本存入受控内容存储,原始私有分区不入库 |
ShareServer 必须持久化获准共享的消息和资料,才能提供消息离线补收及资料同步。此功能使用受控
业务内容存储,不能复用普通审计或只转发不持久化的 task relay;仅部署控制面的实例应报告
空间协作不可用,不假装接受消息。具体正文存储介质、额度、加密与保留配置在协议实现前确定。
管理员仅凭管理身份不能通过内容接口读取正文,备份策略须覆盖启用后的共享业务内容。
授权撤销、成员状态与同步接收使用服务端当前事实。在同一事务中接受一次操作并记录幂等结果,
重试只能获得同一结果;冲突不能静默覆盖成员名单、内容范围或回复。分身运行环境仍在
Provider,工作空间消息不使 ShareServer 自动成为模型运行节点。草稿确认须原子消费待确认的
处理版本;同操作重试返回原结果,不同正文的重复确认或接手后的旧确认返回版本冲突。
验收须覆盖 US 中的离线发送、确认丢失、部分成功、跨空间隔离、移除成员、关闭共享后旧队列、
同时接手与发布、不同正文的重复确认、待开启时关闭共享、新成员读取历史的限制,以及回顾
两种权限的单独撤销。静态 demo 的 localStorage 和
角色切换不能作为这些服务端契约的实现。
## 18. 实时通信组件选型提案
状态:待验证,未定版、未安装组件、未新增运行依赖。优先评估 **ShareServer + PostgreSQL +
Centrifugo**。此处是组件职责比较,不代表已核验当前版本、许可证、部署成本或生产可靠性;
选型须以固定版本的文档核对与端到端试验为依据。
| 候选 | 采用范围 | 需要 GoodBuddy 完成的部分 | 当前判断 |
| --- | --- | --- | --- |
| [Centrifugo](https://centrifugal.dev/) | 实时频道、连接及恢复相关机制 | 权威消息存储、历史、权限、成员通知和协作处理 | 优先验证,可保留 ShareServer 业务模型 |
| [Matrix / Synapse](https://matrix.org/) | 房间、成员、消息及同步体系 | 身份、空间与房间映射、界面及分身协作扩展 | 采用完整通信体系的备选 |
| [OpenIM](https://openim.io/) | IM 服务与客户端 SDK | 组织和空间集成、协作处理及来源授权 | IM 范围扩大时比较 |
| [Socket.IO](https://socket.io/) | 连接、事件及房间广播 | 持久化、可靠交付、历史、补收和业务权限 | 可作传输层,不按完整 IM 服务评估 |
### 18.1 职责与投递流程
ShareServer 接收客户端消息并执行组织、空间、受众和附件授权检查。Centrifugo 候选方案只负责
实时传输,不能由客户端绕过 ShareServer 直接广播一条被视为权威的消息。
1. 客户端携带稳定操作 ID 提交普通消息、线程回复或显式处理请求;附件版本先准备完成。
2. ShareServer 在一个事务内写入消息/状态、必要的处理请求及 outbox 事件,提交后返回接受结果。
3. 后台投递器读取 outbox,将已提交事件发布到实时组件;成功后记录投递结果。发布后未能记录
成功可能导致重复投递,因此采用至少一次投递,客户端按事件身份与对象版本合并。
4. 在线客户端收到更新通知后,按授权读取新增消息和状态。断线后按服务端游标补收;组件的
短期历史只作加速,不能替代数据库中的权威消息历史。
5. 仅显式处理请求的执行调度可创建分身 Task,创建受幂等约束;历史重放、事件重投或成员多设备
在线不会重复启动执行。
实时组件故障时,只要数据库和业务接收条件正常,ShareServer 可确认消息已保存,并说明实时
投递延迟;outbox 恢复后继续投递。数据库事务失败则不确认已发送。过期历史或游标失效时返回
明确的重新同步路径,在现有保留范围内重建进度,不伪造完整历史。
### 18.2 访问与事件范围
首期优先使用不含正文、草稿或敏感摘要的更新通知,具体订阅标识由服务端授权生成;客户端
经当前授权的内容 API 拉取正文。移除成员或关闭共享后,内容、历史及补收接口立即拒绝新
读取,并撤销其订阅/连接资格,不能仅依赖长效订阅凭据自然过期。
频道按组织、空间及受众隔离。AI 草稿使用独立私有范围,公开频道、历史、outbox 公开事件和
普通成员补收中均不含草稿正文。本人确认后的最终回复作为新的公开消息提交。已在撤销前取得
的正文无法通过断开连接擦除;旧通知也不能帮助客户端绕过当前内容授权。
### 18.3 验证与定版条件
- 两个真实客户端验证普通消息、提及、线程回复、请求处理和状态更新;暂停资料自动同步时消息仍及时到达。
- 数据库提交后、实时发布前终止投递器,恢复后消息不丢失;发布后确认丢失允许重复事件但不重复显示或执行。
- 多设备断线重连验证顺序、去重、历史分页和游标恢复;组件历史不可用时可从数据库补收。
- 移除成员后旧连接不能获取新正文,旧 token 不能读取历史或补收;验证跨组织、跨空间与草稿隔离。
- 本人接手和分身完成竞争、两个设备确认不同草稿时,只发布规则允许的结果。
- 核验候选固定版本的许可证、维护状态、SDK 与 Desktop 兼容性,以及部署、备份恢复和资源需求。
上述验证通过后再确定组件、版本、接口与部署拓扑。消息目标延迟、规模及保留期限仍待产品和
部署评审;当前不作吞吐或实时性数值承诺。运维需记录 outbox 积压、投递重试及补收失败,备份
消息与事件进度;连接、短期凭据不恢复。新增组件的配置、凭据和健康检查进入部署设计后才能上线。