整理后端模块清单
This commit is contained in:
11
.codex/environments/environment.toml
Normal file
11
.codex/environments/environment.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
|
||||
version = 1
|
||||
name = "Genarrative"
|
||||
|
||||
[setup]
|
||||
script = '''
|
||||
npm install
|
||||
cd ./node-server
|
||||
npm install
|
||||
cd ..
|
||||
'''
|
||||
397
docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md
Normal file
397
docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md
Normal file
@@ -0,0 +1,397 @@
|
||||
# Node 后端模块与接口索引
|
||||
|
||||
> 该文档由 `server-node/src/manifest/backendCapabilityManifest.ts` 自动生成。
|
||||
> 生成命令:`npm run server-node:manifest:backend`
|
||||
> 生成时间:`2026-04-20T14:26:38.663Z`
|
||||
|
||||
## 总览
|
||||
|
||||
- 对外挂载面:6 个
|
||||
- 已登记路由:96 条
|
||||
- 内部模块目录:12 个
|
||||
- 公开接口:10 条
|
||||
- JWT 接口:69 条
|
||||
- 受环境开关控制的接口:17 条
|
||||
- 流式接口:6 条
|
||||
|
||||
## 产物
|
||||
|
||||
- JSON 清单:`server-node/manifests/backend-capability-index.json`
|
||||
- Markdown 索引:`docs/technical/NODE_BACKEND_MODULE_AND_API_INDEX.md`
|
||||
- Manifest 源:`server-node/src/manifest/backendCapabilityManifest.ts`
|
||||
|
||||
## 对外挂载面
|
||||
|
||||
### 资产生成工具面
|
||||
|
||||
- 标识:`assets`
|
||||
- 路由数:14
|
||||
- 入口:`server-node/src/app.ts -> /api/assets -> createCharacterAssetRoutes`;`server-node/src/app.ts -> /api/assets/qwen-sprite -> createQwenSpriteRoutes`
|
||||
- 关联模块:`assets`
|
||||
- 责任:
|
||||
- 生成角色主形象、动作、动作模板与工作流缓存。
|
||||
- 承接 Qwen 精灵表主图、整表、修帧与保存链路。
|
||||
- 把产物发布到 `public/generated-*` 目录并落地局部 manifest。
|
||||
- 主要服务边界:
|
||||
- 负责对接 DashScope、Ark 等外部媒体供应商,但不维护 runtime 快照与业务状态。
|
||||
- 统一受 `ASSETS_API_ENABLED` 开关控制,产物以文件与 JSON manifest 形式落在仓库工作区。
|
||||
|
||||
### 鉴权与会话面
|
||||
|
||||
- 标识:`auth`
|
||||
- 路由数:17
|
||||
- 入口:`server-node/src/app.ts -> /api/auth -> createAuthRoutes`
|
||||
- 关联模块:无
|
||||
- 责任:
|
||||
- 承接本地账号、短信验证码与微信登录流程。
|
||||
- 管理 refresh session、用户信息、会话吊销、审计日志与风险拦截。
|
||||
- 主要服务边界:
|
||||
- HTTP 层只做 schema 校验、请求上下文拼装与 Cookie 管理,核心鉴权逻辑统一收口到 `server-node/src/auth/*`。
|
||||
- 用户、身份、会话、风控与短信事件等持久化职责全部下沉到 repository 层,避免路由直接碰数据库细节。
|
||||
|
||||
### 编辑器工具面
|
||||
|
||||
- 标识:`editor`
|
||||
- 路由数:3
|
||||
- 入口:`server-node/src/app.ts -> /api/editor -> createEditorRoutes`
|
||||
- 关联模块:`editor`
|
||||
- 责任:
|
||||
- 读取编辑器资源 JSON。
|
||||
- 回写编辑器覆盖文件。
|
||||
- 枚举 `public/Icons` 下的物品图标资源。
|
||||
- 主要服务边界:
|
||||
- 只对工作区文件系统与 `public` 目录负责,不参与运行时数据库存储。
|
||||
- 统一受 `EDITOR_API_ENABLED` 开关控制,生产环境可按需关闭。
|
||||
|
||||
### 基础健康检查
|
||||
|
||||
- 标识:`health`
|
||||
- 路由数:1
|
||||
- 入口:`server-node/src/app.ts -> /healthz -> createApp`
|
||||
- 关联模块:无
|
||||
- 责任:
|
||||
- 提供 Node 后端进程级健康探针。
|
||||
- 给反向代理、部署平台和本地联调提供最小可用状态确认。
|
||||
- 主要服务边界:
|
||||
- 只返回服务静态信息,不触达数据库、鉴权或外部模型供应商。
|
||||
|
||||
### 运行时主能力面
|
||||
|
||||
- 标识:`runtime-main`
|
||||
- 路由数:59
|
||||
- 入口:`server-node/src/app.ts -> /api -> createRuntimeRoutes`;`server-node/src/routes/runtimeRoutes.ts -> /runtime/custom-world/agent -> createCustomWorldAgentRoutes`
|
||||
- 关联模块:`ai`、`custom-world`、`quest`、`runtime`、`runtime-item`、`story`
|
||||
- 责任:
|
||||
- 承接运行时资料库、公开画廊、存档、设置与个人档案接口。
|
||||
- 承接剧情生成、聊天流、任务生成、运行时物品意图与自定义世界链路。
|
||||
- 承接 Custom World Agent 会话、消息流和操作回放。
|
||||
- 主要服务边界:
|
||||
- HTTP contract 收口在 `runtimeRoutes.ts`,真正的世界生成、剧情、聊天、任务和资源逻辑继续下沉到 `services/*` 与 `src/modules/*`。
|
||||
- 除公开画廊外,运行时接口统一走 JWT 鉴权,并依赖 `runtimeRepository`、session store 与 LLM client 执行。
|
||||
|
||||
### 运行时 Story Action 面
|
||||
|
||||
- 标识:`runtime-story-action`
|
||||
- 路由数:2
|
||||
- 入口:`server-node/src/app.ts -> /api/runtime/story -> createStoryActionRoutes`
|
||||
- 关联模块:`story`、`quest`、`inventory`、`runtime-item`、`npc`、`progression`、`combat`、`runtime`
|
||||
- 责任:
|
||||
- 把前端 story choice 动作解析为新的运行时状态。
|
||||
- 查询指定 story session 的可恢复状态。
|
||||
- 主要服务边界:
|
||||
- 路由层只做鉴权与 schema 校验,真正的动作分发与跨模块协作集中在 `storyActionService.ts`。
|
||||
- Story Action 会联动 quest、inventory、runtime-item、npc 等内部模块,但对前端只暴露 story 这一条稳定入口。
|
||||
|
||||
## 接口索引
|
||||
|
||||
| 方法 | 路径 | 访问 | 响应 | 挂载面 | 内部模块 | 说明 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| POST | `/api/assets/character-animation/generate` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 生成角色动作草稿。 |
|
||||
| POST | `/api/assets/character-animation/import-video` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 导入动作参考视频并转为可消费素材。 |
|
||||
| GET | `/api/assets/character-animation/jobs/:taskId` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 查询角色动作生成任务状态。 |
|
||||
| POST | `/api/assets/character-animation/publish` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 发布角色动作帧集到 public 目录。 |
|
||||
| GET | `/api/assets/character-animation/templates` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 列出内置角色动作模板。 |
|
||||
| POST | `/api/assets/character-visual/generate` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 生成角色主形象候选图。 |
|
||||
| GET | `/api/assets/character-visual/jobs/:taskId` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 查询角色主形象生成任务状态。 |
|
||||
| POST | `/api/assets/character-visual/publish` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 发布选中的角色主形象到 public 目录。 |
|
||||
| POST | `/api/assets/character-workflow-cache` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 保存角色资产工作流缓存。 |
|
||||
| GET | `/api/assets/character-workflow-cache/:characterId` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 按角色读取角色资产工作流缓存。 |
|
||||
| POST | `/api/assets/qwen-sprite/frame-repair` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 对单帧做 Qwen 修复。 |
|
||||
| POST | `/api/assets/qwen-sprite/master` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 生成 Qwen 精灵主图。 |
|
||||
| POST | `/api/assets/qwen-sprite/save` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 保存 Qwen 精灵资产到 public 目录。 |
|
||||
| POST | `/api/assets/qwen-sprite/sheet` | 开关: ASSETS_API_ENABLED | json | `assets` | `assets` | 生成 Qwen 精灵表。 |
|
||||
| GET | `/api/auth/audit-logs` | JWT | json | `auth` | 无 | 查询当前账号的鉴权审计日志。 |
|
||||
| POST | `/api/auth/entry` | 公开 | json | `auth` | 无 | 用户名密码登录;不存在则创建本地账号。 |
|
||||
| GET | `/api/auth/login-options` | 公开 | json | `auth` | 无 | 返回当前启用的登录方式与入口配置。 |
|
||||
| POST | `/api/auth/logout` | JWT | json | `auth` | 无 | 退出当前会话并清理 refresh cookie。 |
|
||||
| POST | `/api/auth/logout-all` | JWT | json | `auth` | 无 | 退出当前账号的全部会话。 |
|
||||
| GET | `/api/auth/me` | JWT | json | `auth` | 无 | 读取当前登录用户的鉴权资料。 |
|
||||
| POST | `/api/auth/phone/change` | JWT | json | `auth` | 无 | 已登录用户更换绑定手机号。 |
|
||||
| POST | `/api/auth/phone/login` | 公开 | json | `auth` | 无 | 手机号验证码登录。 |
|
||||
| POST | `/api/auth/phone/send-code` | 公开 | json | `auth` | 无 | 发送手机号登录或绑定验证码。 |
|
||||
| POST | `/api/auth/refresh` | 公开 | json | `auth` | 无 | 使用 refresh session 刷新 JWT。 |
|
||||
| GET | `/api/auth/risk-blocks` | JWT | json | `auth` | 无 | 查询当前用户命中的风控封禁。 |
|
||||
| POST | `/api/auth/risk-blocks/:scopeType/lift` | JWT | json | `auth` | 无 | 请求解除指定维度的风控拦截。 |
|
||||
| GET | `/api/auth/sessions` | JWT | json | `auth` | 无 | 列出当前账号的活跃会话。 |
|
||||
| POST | `/api/auth/sessions/:sessionId/revoke` | JWT | json | `auth` | 无 | 吊销指定会话。 |
|
||||
| POST | `/api/auth/wechat/bind-phone` | JWT | json | `auth` | 无 | 为已登录微信账号绑定手机号。 |
|
||||
| GET | `/api/auth/wechat/callback` | 公开 | redirect | `auth` | 无 | 处理微信回调并重定向回前端。 |
|
||||
| GET | `/api/auth/wechat/start` | 公开 | json | `auth` | 无 | 发起微信登录并返回授权 URL。 |
|
||||
| POST | `/api/custom-world/cover-image` | JWT | json | `runtime-main` | `custom-world`、`assets` | 生成自定义世界封面图。 |
|
||||
| POST | `/api/custom-world/cover-upload` | JWT | json | `runtime-main` | `custom-world`、`assets` | 上传并落地自定义世界封面图。 |
|
||||
| POST | `/api/custom-world/entity` | JWT | json | `runtime-main` | `custom-world`、`ai` | 按世界 profile 生成单个角色或地标实体。 |
|
||||
| POST | `/api/custom-world/scene-image` | JWT | json | `runtime-main` | `custom-world`、`assets` | 生成自定义世界场景图。 |
|
||||
| POST | `/api/custom-world/scene-npc` | JWT | json | `runtime-main` | `custom-world`、`ai`、`npc` | 按地标生成场景 NPC。 |
|
||||
| GET | `/api/editor/catalog/items` | 开关: EDITOR_API_ENABLED | json | `editor` | `editor` | 列出 `public/Icons` 下的物品图标资源。 |
|
||||
| GET | `/api/editor/json/:resourceId` | 开关: EDITOR_API_ENABLED | json | `editor` | `editor` | 读取指定编辑器资源 JSON。 |
|
||||
| POST | `/api/editor/json/:resourceId` | 开关: EDITOR_API_ENABLED | json | `editor` | `editor` | 回写指定编辑器资源 JSON。 |
|
||||
| POST | `/api/llm/chat/completions` | JWT | proxy | `runtime-main` | `ai` | 把聊天补全请求透传到上游模型。 |
|
||||
| DELETE | `/api/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 清空平台浏览历史。 |
|
||||
| GET | `/api/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 读取平台浏览历史。 |
|
||||
| POST | `/api/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 写入或批量同步平台浏览历史。 |
|
||||
| GET | `/api/profile/dashboard` | JWT | json | `runtime-main` | `runtime` | 读取运行时个人主页汇总。 |
|
||||
| GET | `/api/profile/play-stats` | JWT | json | `runtime-main` | `runtime` | 读取个人游玩统计。 |
|
||||
| GET | `/api/profile/save-archives` | JWT | json | `runtime-main` | `runtime` | 列出个人存档摘要。 |
|
||||
| POST | `/api/profile/save-archives/:worldKey` | JWT | json | `runtime-main` | `runtime` | 恢复指定世界的最近存档。 |
|
||||
| GET | `/api/profile/wallet-ledger` | JWT | json | `runtime-main` | `runtime` | 列出个人资产流水。 |
|
||||
| POST | `/api/runtime/chat/character/reply/stream` | JWT | stream | `runtime-main` | `ai`、`story` | 流式生成角色回复。 |
|
||||
| POST | `/api/runtime/chat/character/suggestions` | JWT | json | `runtime-main` | `ai`、`story` | 生成角色聊天建议语。 |
|
||||
| POST | `/api/runtime/chat/character/summary` | JWT | json | `runtime-main` | `ai`、`story` | 生成角色聊天摘要。 |
|
||||
| POST | `/api/runtime/chat/npc/dialogue/stream` | JWT | stream | `runtime-main` | `ai`、`npc`、`story` | 流式生成 NPC 对话。 |
|
||||
| POST | `/api/runtime/chat/npc/recruit/stream` | JWT | stream | `runtime-main` | `ai`、`npc`、`story` | 流式生成招募 NPC 对话。 |
|
||||
| POST | `/api/runtime/chat/npc/turn/stream` | JWT | stream | `runtime-main` | `ai`、`npc`、`story` | 流式生成 NPC 单回合发言。 |
|
||||
| GET | `/api/runtime/custom-world-gallery` | 公开 | json | `runtime-main` | `custom-world`、`runtime` | 列出公开的自定义世界画廊。 |
|
||||
| GET | `/api/runtime/custom-world-gallery/:ownerUserId/:profileId` | 公开 | json | `runtime-main` | `custom-world`、`runtime` | 读取指定公开世界作品详情。 |
|
||||
| GET | `/api/runtime/custom-world-library` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 列出当前账号的自定义世界资料库。 |
|
||||
| DELETE | `/api/runtime/custom-world-library/:profileId` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 删除指定自定义世界 profile。 |
|
||||
| PUT | `/api/runtime/custom-world-library/:profileId` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 写入或更新指定自定义世界 profile。 |
|
||||
| POST | `/api/runtime/custom-world-library/:profileId/publish` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 发布指定世界到公开画廊。 |
|
||||
| POST | `/api/runtime/custom-world-library/:profileId/unpublish` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 撤回指定世界的公开发布状态。 |
|
||||
| POST | `/api/runtime/custom-world/agent/sessions` | JWT | json | `runtime-main` | `custom-world`、`ai` | 创建 Custom World Agent 会话。 |
|
||||
| GET | `/api/runtime/custom-world/agent/sessions/:sessionId` | JWT | json | `runtime-main` | `custom-world`、`ai` | 读取 Agent 会话快照。 |
|
||||
| POST | `/api/runtime/custom-world/agent/sessions/:sessionId/actions` | JWT | json | `runtime-main` | `custom-world`、`ai`、`assets` | 执行 Agent 卡片生成、资产同步或发布动作。 |
|
||||
| GET | `/api/runtime/custom-world/agent/sessions/:sessionId/cards/:cardId` | JWT | json | `runtime-main` | `custom-world`、`ai` | 读取 Agent 卡片详情。 |
|
||||
| POST | `/api/runtime/custom-world/agent/sessions/:sessionId/messages` | JWT | json | `runtime-main` | `custom-world`、`ai` | 向 Agent 会话提交一条创作消息。 |
|
||||
| POST | `/api/runtime/custom-world/agent/sessions/:sessionId/messages/stream` | JWT | stream | `runtime-main` | `custom-world`、`ai` | 流式提交 Agent 消息并实时接收回执。 |
|
||||
| GET | `/api/runtime/custom-world/agent/sessions/:sessionId/operations/:operationId` | JWT | json | `runtime-main` | `custom-world`、`ai` | 查询 Agent 后台操作状态。 |
|
||||
| POST | `/api/runtime/custom-world/entity` | JWT | json | `runtime-main` | `custom-world`、`ai` | 按世界 profile 生成单个角色或地标实体(兼容路径)。 |
|
||||
| POST | `/api/runtime/custom-world/scene-npc` | JWT | json | `runtime-main` | `custom-world`、`ai`、`npc` | 按地标生成场景 NPC(兼容路径)。 |
|
||||
| POST | `/api/runtime/custom-world/sessions` | JWT | json | `runtime-main` | `custom-world` | 创建传统自定义世界问答会话。 |
|
||||
| GET | `/api/runtime/custom-world/sessions/:sessionId` | JWT | json | `runtime-main` | `custom-world` | 读取传统自定义世界问答会话。 |
|
||||
| POST | `/api/runtime/custom-world/sessions/:sessionId/answers` | JWT | json | `runtime-main` | `custom-world` | 回答传统自定义世界问答题目。 |
|
||||
| GET | `/api/runtime/custom-world/sessions/:sessionId/generate/stream` | JWT | stream | `runtime-main` | `custom-world`、`ai` | 流式编译传统自定义世界 profile。 |
|
||||
| GET | `/api/runtime/custom-world/works` | JWT | json | `runtime-main` | `custom-world`、`runtime` | 列出当前账号的自定义世界作品汇总。 |
|
||||
| POST | `/api/runtime/items/runtime-intent` | JWT | json | `runtime-main` | `runtime-item`、`ai` | 生成运行时物品意图。 |
|
||||
| DELETE | `/api/runtime/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 清空平台浏览历史。(兼容路径) |
|
||||
| GET | `/api/runtime/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 读取平台浏览历史。(兼容路径) |
|
||||
| POST | `/api/runtime/profile/browse-history` | JWT | json | `runtime-main` | `runtime` | 写入或批量同步平台浏览历史。(兼容路径) |
|
||||
| GET | `/api/runtime/profile/dashboard` | JWT | json | `runtime-main` | `runtime` | 读取运行时个人主页汇总。(兼容路径) |
|
||||
| GET | `/api/runtime/profile/play-stats` | JWT | json | `runtime-main` | `runtime` | 读取个人游玩统计。(兼容路径) |
|
||||
| GET | `/api/runtime/profile/save-archives` | JWT | json | `runtime-main` | `runtime` | 列出个人存档摘要。(兼容路径) |
|
||||
| POST | `/api/runtime/profile/save-archives/:worldKey` | JWT | json | `runtime-main` | `runtime` | 恢复指定世界的最近存档(兼容路径)。 |
|
||||
| GET | `/api/runtime/profile/wallet-ledger` | JWT | json | `runtime-main` | `runtime` | 列出个人资产流水。(兼容路径) |
|
||||
| POST | `/api/runtime/quests/generate` | JWT | json | `runtime-main` | `quest`、`ai` | 按当前遭遇生成任务候选。 |
|
||||
| DELETE | `/api/runtime/save/snapshot` | JWT | json | `runtime-main` | `runtime` | 删除当前用户的运行时存档。 |
|
||||
| GET | `/api/runtime/save/snapshot` | JWT | json | `runtime-main` | `runtime`、`progression`、`quest` | 读取当前用户的运行时存档。 |
|
||||
| PUT | `/api/runtime/save/snapshot` | JWT | json | `runtime-main` | `runtime`、`progression`、`quest` | 保存并归一化当前运行时存档。 |
|
||||
| GET | `/api/runtime/settings` | JWT | json | `runtime-main` | `runtime` | 读取运行时设置。 |
|
||||
| PUT | `/api/runtime/settings` | JWT | json | `runtime-main` | `runtime` | 更新运行时设置。 |
|
||||
| POST | `/api/runtime/story/actions/resolve` | JWT | json | `runtime-story-action` | `story`、`quest`、`inventory`、`runtime-item`、`npc`、`progression`、`combat`、`runtime` | 解析前端 story choice 动作为新的运行时结果。 |
|
||||
| POST | `/api/runtime/story/continue` | JWT | json | `runtime-main` | `story`、`ai` | 生成下一段故事内容。 |
|
||||
| POST | `/api/runtime/story/initial` | JWT | json | `runtime-main` | `story`、`ai` | 生成首段故事内容。 |
|
||||
| GET | `/api/runtime/story/state/:sessionId` | JWT | json | `runtime-story-action` | `story`、`runtime` | 读取指定 story session 的运行时状态。 |
|
||||
| GET | `/api/ws/health` | JWT | json | `runtime-main` | `runtime` | 保留给未来实时链路的占位健康检查。 |
|
||||
| GET | `/healthz` | 公开 | json | `health` | 无 | 返回 Node 后端进程健康状态。 |
|
||||
|
||||
## 内部模块边界
|
||||
|
||||
### AI 编排模块
|
||||
|
||||
- 标识:`ai`
|
||||
- 目录:`server-node/src/modules/ai`
|
||||
- 对外可见面:`runtime-main`
|
||||
- 关联路由数:23
|
||||
- 职责:
|
||||
- 统一剧情、多轮聊天与自定义世界编排器的 prompt 构造与输出归一化。
|
||||
- 屏蔽前端对不同 AI 链路的直接拼装细节。
|
||||
- 主要服务边界:
|
||||
- 专注提示词与编排,不负责持久化与 HTTP 传输。
|
||||
- 通过 `services/llmClient.ts` 与外部模型交互,由路由与 service 层决定何时调用。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/ai/chatOrchestrator.ts`
|
||||
- `server-node/src/modules/ai/customWorldOrchestrator.ts`
|
||||
- `server-node/src/modules/ai/storyOrchestrator.ts`
|
||||
|
||||
### 资产工具模块
|
||||
|
||||
- 标识:`assets`
|
||||
- 目录:`server-node/src/modules/assets`
|
||||
- 对外可见面:`assets`
|
||||
- 关联路由数:18
|
||||
- 职责:
|
||||
- 承接角色资产与 Qwen 精灵表的生成、查询、发布和保存。
|
||||
- 维护资产流程需要的缓存、草稿与产物 manifest。
|
||||
- 主要服务边界:
|
||||
- 以文件系统和外部媒体模型为主要边界,不碰 runtimeRepository。
|
||||
- 对外暴露稳定 HTTP 路径,对内通过私有 helper 处理媒体编码、任务轮询与写盘。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/assets/characterAssetRoutes.ts`
|
||||
- `server-node/src/modules/assets/qwenSpriteRoutes.ts`
|
||||
|
||||
### 战斗结算模块
|
||||
|
||||
- 标识:`combat`
|
||||
- 目录:`server-node/src/modules/combat`
|
||||
- 对外可见面:`runtime-story-action`
|
||||
- 关联路由数:1
|
||||
- 职责:
|
||||
- 提供运行时战斗结算与数值变更能力。
|
||||
- 为 story action 里的战斗型交互提供纯计算服务。
|
||||
- 主要服务边界:
|
||||
- 聚焦状态推导与结果计算,不负责 transport 与持久化。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/combat/combatResolutionService.ts`
|
||||
|
||||
### 自定义世界运行时模块
|
||||
|
||||
- 标识:`custom-world`
|
||||
- 目录:`server-node/src/modules/custom-world`
|
||||
- 对外可见面:`runtime-main`
|
||||
- 关联路由数:26
|
||||
- 职责:
|
||||
- 规范 creator intent、世界运行时类型与 profile compile。
|
||||
- 把世界创作输入整理成运行时可消费的数据结构。
|
||||
- 主要服务边界:
|
||||
- 偏纯领域建模与 compile,不直接做 HTTP、数据库查询或模型调用。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/custom-world/creatorIntentRuntime.ts`
|
||||
- `server-node/src/modules/custom-world/runtimeProfile.ts`
|
||||
- `server-node/src/modules/custom-world/runtimeTypes.ts`
|
||||
|
||||
### 编辑器资源模块
|
||||
|
||||
- 标识:`editor`
|
||||
- 目录:`server-node/src/modules/editor`
|
||||
- 对外可见面:`editor`
|
||||
- 关联路由数:3
|
||||
- 职责:
|
||||
- 提供编辑器资源目录枚举与 JSON 读写入口。
|
||||
- 主要服务边界:
|
||||
- 只负责工作区文件输入输出,不参与运行时业务计算。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/editor/editorRoutes.ts`
|
||||
|
||||
### 背包与物品变更模块
|
||||
|
||||
- 标识:`inventory`
|
||||
- 目录:`server-node/src/modules/inventory`
|
||||
- 对外可见面:`runtime-story-action`
|
||||
- 关联路由数:1
|
||||
- 职责:
|
||||
- 维护背包变更、NPC 背包交互与 story action 里的物品副作用。
|
||||
- 主要服务边界:
|
||||
- 对运行时状态做局部变更,不直接暴露 HTTP 路由。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/inventory/inventoryMutationService.ts`
|
||||
- `server-node/src/modules/inventory/inventoryStoryActionService.ts`
|
||||
- `server-node/src/modules/inventory/npcInventoryStoryActionService.ts`
|
||||
|
||||
### NPC 交互模块
|
||||
|
||||
- 标识:`npc`
|
||||
- 目录:`server-node/src/modules/npc`
|
||||
- 对外可见面:`runtime-story-action`、`runtime-main`
|
||||
- 关联路由数:6
|
||||
- 职责:
|
||||
- 维护 NPC 互动规则、任务 primitive 与关系变更逻辑。
|
||||
- 主要服务边界:
|
||||
- 专注 NPC 侧状态推导,供 story action 与聊天/任务链路复用。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/npc/npcInteractionService.ts`
|
||||
- `server-node/src/modules/npc/npcTask6Primitives.ts`
|
||||
|
||||
### 成长与关卡进程模块
|
||||
|
||||
- 标识:`progression`
|
||||
- 目录:`server-node/src/modules/progression`
|
||||
- 对外可见面:`runtime-story-action`、`runtime-main`
|
||||
- 关联路由数:3
|
||||
- 职责:
|
||||
- 提供角色成长、敌对等级、章节推进与 benchmark 逻辑。
|
||||
- 主要服务边界:
|
||||
- 只做成长数值与章节进度计算,由 runtime hydrate 与 story action 复用。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/progression/playerProgressionService.ts`
|
||||
- `server-node/src/modules/progression/hostileProgressionService.ts`
|
||||
- `server-node/src/modules/progression/chapterProgressionPlanner.ts`
|
||||
|
||||
### 任务运行时模块
|
||||
|
||||
- 标识:`quest`
|
||||
- 目录:`server-node/src/modules/quest`
|
||||
- 对外可见面:`runtime-main`、`runtime-story-action`
|
||||
- 关联路由数:4
|
||||
- 职责:
|
||||
- 生成任务意图、维护任务日志与处理任务进度信号。
|
||||
- 为运行时 quest 接口与 story action 提供统一任务语义。
|
||||
- 主要服务边界:
|
||||
- 领域逻辑以 quest module 为中心,AI 生成只是一种输入来源。
|
||||
- 不直接处理 HTTP 响应,统一由 routes/service 层调用。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/quest/runtimeQuestModule.ts`
|
||||
- `server-node/src/modules/quest/questProgressionService.ts`
|
||||
- `server-node/src/modules/quest/questStoryActionService.ts`
|
||||
|
||||
### 运行时状态基座模块
|
||||
|
||||
- 标识:`runtime`
|
||||
- 目录:`server-node/src/modules/runtime`
|
||||
- 对外可见面:`runtime-main`、`runtime-story-action`
|
||||
- 关联路由数:32
|
||||
- 职责:
|
||||
- 定义运行时状态 primitive、经济与装备规则。
|
||||
- 负责存档 hydration、兼容迁移与状态归一化。
|
||||
- 主要服务边界:
|
||||
- 是 runtimeRepository 与 story action 的共同状态基座,不承担 HTTP 入口职责。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/runtime/runtimeSnapshotHydration.ts`
|
||||
- `server-node/src/modules/runtime/runtimeStatePrimitives.ts`
|
||||
- `server-node/src/modules/runtime/runtimeEquipmentModule.ts`
|
||||
|
||||
### 运行时物品模块
|
||||
|
||||
- 标识:`runtime-item`
|
||||
- 目录:`server-node/src/modules/runtime-item`
|
||||
- 对外可见面:`runtime-main`、`runtime-story-action`
|
||||
- 关联路由数:2
|
||||
- 职责:
|
||||
- 生成运行时物品意图、物品奖励与剧情指纹。
|
||||
- 维护宝藏与物品解析逻辑。
|
||||
- 主要服务边界:
|
||||
- 聚焦物品领域编译与奖励拼装,由 route/service 选择具体触发时机。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/runtime-item/runtimeItemModule.ts`
|
||||
- `server-node/src/modules/runtime-item/runtimeItemResolutionService.ts`
|
||||
- `server-node/src/modules/runtime-item/treasureStoryActionService.ts`
|
||||
|
||||
### 故事会话模块
|
||||
|
||||
- 标识:`story`
|
||||
- 目录:`server-node/src/modules/story`
|
||||
- 对外可见面:`runtime-main`、`runtime-story-action`
|
||||
- 关联路由数:10
|
||||
- 职责:
|
||||
- 维护运行时故事会话状态与 action 分发。
|
||||
- 为 story resolve、story state 查询提供统一入口。
|
||||
- 主要服务边界:
|
||||
- story 模块是 runtime 主循环的编排层,必要时再向 quest、inventory、combat 等领域模块分发。
|
||||
- 关键文件:
|
||||
- `server-node/src/modules/story/runtimeSession.ts`
|
||||
- `server-node/src/modules/story/storyActionRoutes.ts`
|
||||
- `server-node/src/modules/story/storyActionService.ts`
|
||||
|
||||
## 维护规则
|
||||
|
||||
- 新增 `server-node/src/modules/*` 目录时,必须先补充 manifest 里的模块说明,再重新生成产物。
|
||||
- 新增或下线路由时,先更新 manifest 里的路由清单,再运行生成命令同步 JSON 与文档。
|
||||
- 如果路由来自兼容路径或中间件派生路径,`sourceHint` 需要指向源代码里的真实表达式,确保生成脚本能做最小校验。
|
||||
@@ -10,6 +10,7 @@
|
||||
- [CUSTOM_WORLD_AUTO_ASSET_VISIBILITY_FIX_2026-04-20.md](./CUSTOM_WORLD_AUTO_ASSET_VISIBILITY_FIX_2026-04-20.md):世界草稿里“资产已生成但结果页看不到”的根因拆解,包含角色主形象展示、分幕背景露出和 fallback 资源格式修复。
|
||||
- [CUSTOM_WORLD_PHASE4_COUNT_SEMANTICS_ALIGNMENT_2026-04-20.md](./CUSTOM_WORLD_PHASE4_COUNT_SEMANTICS_ALIGNMENT_2026-04-20.md):Phase4 新增角色/地点后草稿作品卡数量统计与测试断言的语义对齐说明。
|
||||
- [TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md](./TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md):把外部仓库 TXT 模式完整迁入当前项目的冻结边界、模块映射、分阶段计划与验收清单。
|
||||
- [NODE_BACKEND_MODULE_AND_API_INDEX.md](./NODE_BACKEND_MODULE_AND_API_INDEX.md):由 `server-node/src/manifest/backendCapabilityManifest.ts` 生成的 Node 后端模块职责、挂载面与接口索引,后续新增模块/接口时同步更新这一份。
|
||||
- [NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md](./NODE_SERVER_KNOWLEDGE_GRAPH_2026-04-08.md):当前 Node 运行时后端的技术栈、入口、鉴权、存储与接口知识图谱。
|
||||
- [EXPRESS_BACKEND_INTEGRATION_FREEZE_2026-04-09.md](./EXPRESS_BACKEND_INTEGRATION_FREEZE_2026-04-09.md):Express 后端当前 contract 冻结版本、热点文件编辑规则与集成窗口清单。
|
||||
- [EXPRESS_BACKEND_WORKSTREAM_AUDIT_2026-04-09.md](./EXPRESS_BACKEND_WORKSTREAM_AUDIT_2026-04-09.md):按并行工作流文档逐项核对后的完成度审计与剩余收口点。
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"server-node:dev": "npm --prefix server-node run dev",
|
||||
"server-node:build": "npm --prefix server-node run build",
|
||||
"server-node:db:migrate": "npm --prefix server-node run db:migrate",
|
||||
"server-node:manifest:backend": "npm --prefix server-node run manifest:backend",
|
||||
"server-node:test": "npm --prefix server-node run test",
|
||||
"server-node:test:baseline": "npx tsx --test server-node/src/observability.test.ts",
|
||||
"server-node:smoke": "npx tsx scripts/smoke-server-node.ts",
|
||||
|
||||
2250
server-node/manifests/backend-capability-index.json
Normal file
2250
server-node/manifests/backend-capability-index.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,8 @@
|
||||
"build": "node build.mjs",
|
||||
"start": "node dist/server.cjs",
|
||||
"test": "node test.mjs",
|
||||
"db:migrate": "tsx src/migrate.ts"
|
||||
"db:migrate": "tsx src/migrate.ts",
|
||||
"manifest:backend": "tsx scripts/generateBackendCapabilityArtifacts.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alicloud/dypnsapi20170525": "^2.0.0",
|
||||
|
||||
372
server-node/scripts/generateBackendCapabilityArtifacts.ts
Normal file
372
server-node/scripts/generateBackendCapabilityArtifacts.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import {
|
||||
BACKEND_CAPABILITY_MANIFEST,
|
||||
type BackendCapabilityManifest,
|
||||
type BackendDomainModule,
|
||||
type BackendRouteCapability,
|
||||
type BackendRouteSurface,
|
||||
} from '../src/manifest/backendCapabilityManifest.js';
|
||||
|
||||
type GeneratedSummary = {
|
||||
surfaceCount: number;
|
||||
routeCount: number;
|
||||
moduleCount: number;
|
||||
publicRouteCount: number;
|
||||
jwtRouteCount: number;
|
||||
envSwitchRouteCount: number;
|
||||
streamRouteCount: number;
|
||||
};
|
||||
|
||||
type GeneratedArtifact = {
|
||||
generatedAt: string;
|
||||
manifestVersion: string;
|
||||
generatedCommand: string;
|
||||
outputTargets: BackendCapabilityManifest['outputTargets'];
|
||||
summary: GeneratedSummary;
|
||||
surfaces: Array<
|
||||
BackendRouteSurface & {
|
||||
routeCount: number;
|
||||
routeIds: string[];
|
||||
}
|
||||
>;
|
||||
modules: Array<
|
||||
BackendDomainModule & {
|
||||
routeCount: number;
|
||||
routeIds: string[];
|
||||
}
|
||||
>;
|
||||
routes: BackendRouteCapability[];
|
||||
maintenanceRules: string[];
|
||||
};
|
||||
|
||||
const currentFilePath = fileURLToPath(import.meta.url);
|
||||
const scriptDirectory = path.dirname(currentFilePath);
|
||||
const repoRoot = path.resolve(scriptDirectory, '..', '..');
|
||||
|
||||
/**
|
||||
* 统一把 repo 相对路径转成绝对路径,避免不同工作目录下解析不一致。
|
||||
*/
|
||||
function resolveRepoPath(relativePath: string) {
|
||||
return path.resolve(repoRoot, relativePath);
|
||||
}
|
||||
|
||||
function sortById<T extends { id: string }>(items: T[]) {
|
||||
return [...items].sort((left, right) => left.id.localeCompare(right.id, 'zh-Hans-CN'));
|
||||
}
|
||||
|
||||
function sortRoutes(routes: BackendRouteCapability[]) {
|
||||
return [...routes].sort((left, right) => {
|
||||
if (left.path === right.path) {
|
||||
return left.method.localeCompare(right.method, 'en-US');
|
||||
}
|
||||
return left.path.localeCompare(right.path, 'en-US');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 用最小约束校验 manifest 的唯一性与引用完整性,确保生成结果可维护。
|
||||
*/
|
||||
function assertUniqueIds(items: Array<{ id: string }>, label: string) {
|
||||
const seen = new Set<string>();
|
||||
const duplicates: string[] = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
if (seen.has(item.id)) {
|
||||
duplicates.push(item.id);
|
||||
return;
|
||||
}
|
||||
seen.add(item.id);
|
||||
});
|
||||
|
||||
if (duplicates.length > 0) {
|
||||
throw new Error(`${label} 存在重复 id:${duplicates.join('、')}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function assertSourceFileContains(params: {
|
||||
sourceFile: string;
|
||||
sourceHint: string;
|
||||
routeId: string;
|
||||
}) {
|
||||
const absolutePath = resolveRepoPath(params.sourceFile);
|
||||
const content = await readFile(absolutePath, 'utf8');
|
||||
if (!content.includes(params.sourceHint)) {
|
||||
throw new Error(
|
||||
`路由 ${params.routeId} 的 sourceHint 未命中源码:${params.sourceFile} -> ${params.sourceHint}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function validateModuleCoverage(modules: BackendDomainModule[]) {
|
||||
const modulesRoot = resolveRepoPath('server-node/src/modules');
|
||||
const directoryEntries = await readdir(modulesRoot, { withFileTypes: true });
|
||||
const actualDirectories = directoryEntries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => `server-node/src/modules/${entry.name}`)
|
||||
.sort((left, right) => left.localeCompare(right, 'en-US'));
|
||||
const manifestDirectories = modules
|
||||
.map((moduleItem) => moduleItem.directory)
|
||||
.sort((left, right) => left.localeCompare(right, 'en-US'));
|
||||
|
||||
const missingInManifest = actualDirectories.filter(
|
||||
(directory) => !manifestDirectories.includes(directory),
|
||||
);
|
||||
const staleInManifest = manifestDirectories.filter(
|
||||
(directory) => !actualDirectories.includes(directory),
|
||||
);
|
||||
|
||||
if (missingInManifest.length > 0) {
|
||||
throw new Error(
|
||||
`以下模块目录尚未进入能力 manifest:${missingInManifest.join('、')}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (staleInManifest.length > 0) {
|
||||
throw new Error(
|
||||
`manifest 中存在已失效的模块目录:${staleInManifest.join('、')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function validateManifest(manifest: BackendCapabilityManifest) {
|
||||
assertUniqueIds(manifest.surfaces, '挂载面');
|
||||
assertUniqueIds(manifest.modules, '内部模块');
|
||||
assertUniqueIds(manifest.routes, '路由');
|
||||
|
||||
const surfaceIds = new Set(manifest.surfaces.map((surface) => surface.id));
|
||||
const moduleIds = new Set(manifest.modules.map((moduleItem) => moduleItem.id));
|
||||
|
||||
for (const surface of manifest.surfaces) {
|
||||
for (const relatedModuleId of surface.relatedModuleIds) {
|
||||
if (!moduleIds.has(relatedModuleId)) {
|
||||
throw new Error(
|
||||
`挂载面 ${surface.id} 引用了未定义的模块:${relatedModuleId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const mount of surface.mounts) {
|
||||
const absoluteEntryPath = resolveRepoPath(mount.entryFile);
|
||||
const content = await readFile(absoluteEntryPath, 'utf8');
|
||||
if (!content.includes(mount.routeFactory)) {
|
||||
throw new Error(
|
||||
`挂载面 ${surface.id} 的入口文件缺少工厂引用:${mount.entryFile} -> ${mount.routeFactory}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const moduleItem of manifest.modules) {
|
||||
for (const surfaceId of moduleItem.exposedBySurfaceIds) {
|
||||
if (!surfaceIds.has(surfaceId)) {
|
||||
throw new Error(
|
||||
`模块 ${moduleItem.id} 引用了未定义的挂载面:${surfaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const absoluteDirectory = resolveRepoPath(moduleItem.directory);
|
||||
const statsEntries = await readdir(absoluteDirectory);
|
||||
if (statsEntries.length === 0) {
|
||||
throw new Error(`模块目录为空,无法作为能力边界:${moduleItem.directory}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const route of manifest.routes) {
|
||||
if (!surfaceIds.has(route.surfaceId)) {
|
||||
throw new Error(`路由 ${route.id} 引用了未定义的挂载面:${route.surfaceId}`);
|
||||
}
|
||||
|
||||
for (const moduleId of route.domainModuleIds) {
|
||||
if (!moduleIds.has(moduleId)) {
|
||||
throw new Error(`路由 ${route.id} 引用了未定义的模块:${moduleId}`);
|
||||
}
|
||||
}
|
||||
|
||||
await assertSourceFileContains({
|
||||
sourceFile: route.sourceFile,
|
||||
sourceHint: route.sourceHint,
|
||||
routeId: route.id,
|
||||
});
|
||||
}
|
||||
|
||||
await validateModuleCoverage(manifest.modules);
|
||||
}
|
||||
|
||||
function buildSummary(routes: BackendRouteCapability[]): GeneratedSummary {
|
||||
return {
|
||||
surfaceCount: BACKEND_CAPABILITY_MANIFEST.surfaces.length,
|
||||
routeCount: routes.length,
|
||||
moduleCount: BACKEND_CAPABILITY_MANIFEST.modules.length,
|
||||
publicRouteCount: routes.filter((route) => route.access === '公开').length,
|
||||
jwtRouteCount: routes.filter((route) => route.access === 'JWT').length,
|
||||
envSwitchRouteCount: routes.filter((route) => route.access.startsWith('开关:')).length,
|
||||
streamRouteCount: routes.filter((route) => route.responseMode === 'stream').length,
|
||||
};
|
||||
}
|
||||
|
||||
function buildArtifact(manifest: BackendCapabilityManifest): GeneratedArtifact {
|
||||
const routes = sortRoutes(manifest.routes);
|
||||
const summary = buildSummary(routes);
|
||||
|
||||
const surfaces = sortById(manifest.surfaces).map((surface) => {
|
||||
const surfaceRoutes = routes.filter((route) => route.surfaceId === surface.id);
|
||||
return {
|
||||
...surface,
|
||||
routeCount: surfaceRoutes.length,
|
||||
routeIds: surfaceRoutes.map((route) => route.id),
|
||||
};
|
||||
});
|
||||
|
||||
const modules = sortById(manifest.modules).map((moduleItem) => {
|
||||
const moduleRoutes = routes.filter((route) =>
|
||||
route.domainModuleIds.includes(moduleItem.id),
|
||||
);
|
||||
return {
|
||||
...moduleItem,
|
||||
routeCount: moduleRoutes.length,
|
||||
routeIds: moduleRoutes.map((route) => route.id),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
manifestVersion: manifest.version,
|
||||
generatedCommand: manifest.generatedCommand,
|
||||
outputTargets: manifest.outputTargets,
|
||||
summary,
|
||||
surfaces,
|
||||
modules,
|
||||
routes,
|
||||
maintenanceRules: manifest.maintenanceRules,
|
||||
};
|
||||
}
|
||||
|
||||
function renderMarkdown(artifact: GeneratedArtifact) {
|
||||
const lines: string[] = [];
|
||||
lines.push('# Node 后端模块与接口索引');
|
||||
lines.push('');
|
||||
lines.push('> 该文档由 `server-node/src/manifest/backendCapabilityManifest.ts` 自动生成。');
|
||||
lines.push(`> 生成命令:\`${artifact.generatedCommand}\``);
|
||||
lines.push(`> 生成时间:\`${artifact.generatedAt}\``);
|
||||
lines.push('');
|
||||
lines.push('## 总览');
|
||||
lines.push('');
|
||||
lines.push(`- 对外挂载面:${artifact.summary.surfaceCount} 个`);
|
||||
lines.push(`- 已登记路由:${artifact.summary.routeCount} 条`);
|
||||
lines.push(`- 内部模块目录:${artifact.summary.moduleCount} 个`);
|
||||
lines.push(`- 公开接口:${artifact.summary.publicRouteCount} 条`);
|
||||
lines.push(`- JWT 接口:${artifact.summary.jwtRouteCount} 条`);
|
||||
lines.push(`- 受环境开关控制的接口:${artifact.summary.envSwitchRouteCount} 条`);
|
||||
lines.push(`- 流式接口:${artifact.summary.streamRouteCount} 条`);
|
||||
lines.push('');
|
||||
lines.push('## 产物');
|
||||
lines.push('');
|
||||
lines.push(`- JSON 清单:\`${artifact.outputTargets.json}\``);
|
||||
lines.push(`- Markdown 索引:\`${artifact.outputTargets.markdown}\``);
|
||||
lines.push(`- Manifest 源:\`server-node/src/manifest/backendCapabilityManifest.ts\``);
|
||||
lines.push('');
|
||||
lines.push('## 对外挂载面');
|
||||
lines.push('');
|
||||
|
||||
artifact.surfaces.forEach((surface) => {
|
||||
lines.push(`### ${surface.title}`);
|
||||
lines.push('');
|
||||
lines.push(`- 标识:\`${surface.id}\``);
|
||||
lines.push(`- 路由数:${surface.routeCount}`);
|
||||
lines.push(`- 入口:${surface.mounts.map((mount) => `\`${mount.entryFile} -> ${mount.mountPath} -> ${mount.routeFactory}\``).join(';')}`);
|
||||
lines.push(`- 关联模块:${surface.relatedModuleIds.length > 0 ? surface.relatedModuleIds.map((moduleId) => `\`${moduleId}\``).join('、') : '无'}`);
|
||||
lines.push('- 责任:');
|
||||
surface.responsibilities.forEach((item) => {
|
||||
lines.push(` - ${item}`);
|
||||
});
|
||||
lines.push('- 主要服务边界:');
|
||||
surface.primaryServiceBoundaries.forEach((item) => {
|
||||
lines.push(` - ${item}`);
|
||||
});
|
||||
lines.push('');
|
||||
});
|
||||
|
||||
lines.push('## 接口索引');
|
||||
lines.push('');
|
||||
lines.push('| 方法 | 路径 | 访问 | 响应 | 挂载面 | 内部模块 | 说明 |');
|
||||
lines.push('| --- | --- | --- | --- | --- | --- | --- |');
|
||||
artifact.routes.forEach((route) => {
|
||||
const moduleLabel =
|
||||
route.domainModuleIds.length > 0
|
||||
? route.domainModuleIds.map((moduleId) => `\`${moduleId}\``).join('、')
|
||||
: '无';
|
||||
lines.push(
|
||||
`| ${route.method} | \`${route.path}\` | ${route.access} | ${route.responseMode} | \`${route.surfaceId}\` | ${moduleLabel} | ${route.summary} |`,
|
||||
);
|
||||
});
|
||||
lines.push('');
|
||||
|
||||
lines.push('## 内部模块边界');
|
||||
lines.push('');
|
||||
artifact.modules.forEach((moduleItem) => {
|
||||
lines.push(`### ${moduleItem.title}`);
|
||||
lines.push('');
|
||||
lines.push(`- 标识:\`${moduleItem.id}\``);
|
||||
lines.push(`- 目录:\`${moduleItem.directory}\``);
|
||||
lines.push(`- 对外可见面:${moduleItem.exposedBySurfaceIds.map((surfaceId) => `\`${surfaceId}\``).join('、')}`);
|
||||
lines.push(`- 关联路由数:${moduleItem.routeCount}`);
|
||||
lines.push('- 职责:');
|
||||
moduleItem.responsibilities.forEach((item) => {
|
||||
lines.push(` - ${item}`);
|
||||
});
|
||||
lines.push('- 主要服务边界:');
|
||||
moduleItem.primaryServiceBoundaries.forEach((item) => {
|
||||
lines.push(` - ${item}`);
|
||||
});
|
||||
lines.push('- 关键文件:');
|
||||
moduleItem.keyFiles.forEach((filePath) => {
|
||||
lines.push(` - \`${filePath}\``);
|
||||
});
|
||||
lines.push('');
|
||||
});
|
||||
|
||||
lines.push('## 维护规则');
|
||||
lines.push('');
|
||||
artifact.maintenanceRules.forEach((rule) => {
|
||||
lines.push(`- ${rule}`);
|
||||
});
|
||||
lines.push('');
|
||||
|
||||
return `${lines.join('\n')}`;
|
||||
}
|
||||
|
||||
async function writeArtifactFile(relativePath: string, content: string) {
|
||||
const absolutePath = resolveRepoPath(relativePath);
|
||||
await mkdir(path.dirname(absolutePath), { recursive: true });
|
||||
await writeFile(absolutePath, content, 'utf8');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await validateManifest(BACKEND_CAPABILITY_MANIFEST);
|
||||
|
||||
const artifact = buildArtifact(BACKEND_CAPABILITY_MANIFEST);
|
||||
const jsonContent = `${JSON.stringify(artifact, null, 2)}\n`;
|
||||
const markdownContent = renderMarkdown(artifact);
|
||||
|
||||
await writeArtifactFile(artifact.outputTargets.json, jsonContent);
|
||||
await writeArtifactFile(artifact.outputTargets.markdown, markdownContent);
|
||||
|
||||
console.log(
|
||||
[
|
||||
`backend capability artifacts generated`,
|
||||
`json=${artifact.outputTargets.json}`,
|
||||
`markdown=${artifact.outputTargets.markdown}`,
|
||||
`routes=${artifact.summary.routeCount}`,
|
||||
`modules=${artifact.summary.moduleCount}`,
|
||||
].join(' | '),
|
||||
);
|
||||
}
|
||||
|
||||
void main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
1696
server-node/src/manifest/backendCapabilityManifest.ts
Normal file
1696
server-node/src/manifest/backendCapabilityManifest.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@ import { PNG } from 'pngjs';
|
||||
import { removeBackgroundFromRgba } from '../../../../packages/shared/src/assets/chromaKey.js';
|
||||
import { parseApiErrorMessage } from '../../../../packages/shared/src/http.js';
|
||||
import type { AppConfig } from '../../config.js';
|
||||
import { routeMeta } from '../../middleware/routeMeta.js';
|
||||
import {
|
||||
buildArkCharacterAnimationPrompt,
|
||||
buildFallbackModerationSafeAnimationPrompt,
|
||||
@@ -30,14 +31,18 @@ import {
|
||||
import type { UpstreamLlmClient } from '../../services/llmClient.js';
|
||||
|
||||
const CHARACTER_WORKFLOW_CACHE_PATH = '/api/assets/character-workflow-cache';
|
||||
const CHARACTER_WORKFLOW_CACHE_DETAIL_PATH =
|
||||
'/api/assets/character-workflow-cache/:characterId';
|
||||
const CHARACTER_VISUAL_GENERATE_PATH = '/api/assets/character-visual/generate';
|
||||
const CHARACTER_VISUAL_PUBLISH_PATH = '/api/assets/character-visual/publish';
|
||||
const CHARACTER_VISUAL_JOBS_PATH = '/api/assets/character-visual/jobs/';
|
||||
const CHARACTER_VISUAL_JOB_DETAIL_PATH =
|
||||
'/api/assets/character-visual/jobs/:taskId';
|
||||
const CHARACTER_ANIMATION_GENERATE_PATH =
|
||||
'/api/assets/character-animation/generate';
|
||||
const CHARACTER_ANIMATION_PUBLISH_PATH =
|
||||
'/api/assets/character-animation/publish';
|
||||
const CHARACTER_ANIMATION_JOBS_PATH = '/api/assets/character-animation/jobs/';
|
||||
const CHARACTER_ANIMATION_JOB_DETAIL_PATH =
|
||||
'/api/assets/character-animation/jobs/:taskId';
|
||||
const CHARACTER_ANIMATION_IMPORT_VIDEO_PATH =
|
||||
'/api/assets/character-animation/import-video';
|
||||
const CHARACTER_ANIMATION_TEMPLATES_PATH =
|
||||
@@ -2990,29 +2995,37 @@ export function createCharacterAssetRoutes(
|
||||
next();
|
||||
});
|
||||
|
||||
router.use(
|
||||
router.post(
|
||||
CHARACTER_WORKFLOW_CACHE_PATH,
|
||||
routeMeta({ operation: 'assets.character.workflowCache.save' }),
|
||||
toExpressHandler((request, response) => {
|
||||
if (request.method === 'GET') {
|
||||
return handleGetCharacterWorkflowCache(config, request, response);
|
||||
}
|
||||
return handleSaveCharacterWorkflowCache(config, request, response);
|
||||
}),
|
||||
);
|
||||
router.use(
|
||||
router.get(
|
||||
CHARACTER_WORKFLOW_CACHE_DETAIL_PATH,
|
||||
routeMeta({ operation: 'assets.character.workflowCache.get' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleGetCharacterWorkflowCache(config, request, response),
|
||||
),
|
||||
);
|
||||
router.post(
|
||||
CHARACTER_VISUAL_GENERATE_PATH,
|
||||
routeMeta({ operation: 'assets.character.visual.generate' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleGenerateCharacterVisuals(config, request, response),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
router.post(
|
||||
CHARACTER_VISUAL_PUBLISH_PATH,
|
||||
routeMeta({ operation: 'assets.character.visual.publish' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handlePublishCharacterVisual(config, request, response),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
CHARACTER_VISUAL_JOBS_PATH,
|
||||
router.get(
|
||||
CHARACTER_VISUAL_JOB_DETAIL_PATH,
|
||||
routeMeta({ operation: 'assets.character.visual.job.get' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleReadCharacterJobStatus(
|
||||
config.projectRoot,
|
||||
@@ -3022,20 +3035,23 @@ export function createCharacterAssetRoutes(
|
||||
),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
router.post(
|
||||
CHARACTER_ANIMATION_GENERATE_PATH,
|
||||
routeMeta({ operation: 'assets.character.animation.generate' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleGenerateCharacterAnimation(config, request, response),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
router.post(
|
||||
CHARACTER_ANIMATION_PUBLISH_PATH,
|
||||
routeMeta({ operation: 'assets.character.animation.publish' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handlePublishCharacterAnimation(config, request, response),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
CHARACTER_ANIMATION_JOBS_PATH,
|
||||
router.get(
|
||||
CHARACTER_ANIMATION_JOB_DETAIL_PATH,
|
||||
routeMeta({ operation: 'assets.character.animation.job.get' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleReadCharacterJobStatus(
|
||||
config.projectRoot,
|
||||
@@ -3045,8 +3061,9 @@ export function createCharacterAssetRoutes(
|
||||
),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
router.post(
|
||||
CHARACTER_ANIMATION_IMPORT_VIDEO_PATH,
|
||||
routeMeta({ operation: 'assets.character.animation.importVideo' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleImportCharacterAnimationVideo(
|
||||
config.projectRoot,
|
||||
@@ -3055,8 +3072,9 @@ export function createCharacterAssetRoutes(
|
||||
),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
router.get(
|
||||
CHARACTER_ANIMATION_TEMPLATES_PATH,
|
||||
routeMeta({ operation: 'assets.character.animation.templates.list' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleListAnimationTemplates(config, request, response),
|
||||
),
|
||||
|
||||
@@ -9,6 +9,7 @@ import path from 'node:path';
|
||||
|
||||
import { Router, type NextFunction, type Request, type Response } from 'express';
|
||||
import type { AppConfig } from '../../config.js';
|
||||
import { routeMeta } from '../../middleware/routeMeta.js';
|
||||
|
||||
const QWEN_SPRITE_MASTER_GENERATE_PATH = '/api/assets/qwen-sprite/master';
|
||||
const QWEN_SPRITE_SHEET_GENERATE_PATH = '/api/assets/qwen-sprite/sheet';
|
||||
@@ -878,26 +879,30 @@ export function createQwenSpriteRoutes(config: AppConfig) {
|
||||
next();
|
||||
});
|
||||
|
||||
router.use(
|
||||
router.post(
|
||||
QWEN_SPRITE_MASTER_GENERATE_PATH,
|
||||
routeMeta({ operation: 'assets.qwenSprite.master.generate' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleGenerateMaster(config, request, response),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
router.post(
|
||||
QWEN_SPRITE_SHEET_GENERATE_PATH,
|
||||
routeMeta({ operation: 'assets.qwenSprite.sheet.generate' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleGenerateSheet(config, request, response),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
router.post(
|
||||
QWEN_SPRITE_FRAME_REPAIR_PATH,
|
||||
routeMeta({ operation: 'assets.qwenSprite.frameRepair.generate' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleRepairFrame(config, request, response),
|
||||
),
|
||||
);
|
||||
router.use(
|
||||
router.post(
|
||||
QWEN_SPRITE_SAVE_PATH,
|
||||
routeMeta({ operation: 'assets.qwenSprite.asset.save' }),
|
||||
toExpressHandler((request, response) =>
|
||||
handleSaveAsset(config.projectRoot, request, response),
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Router } from 'express';
|
||||
import type { AppConfig } from '../../config.js';
|
||||
import { badRequest, notFound } from '../../errors.js';
|
||||
import { asyncHandler } from '../../http.js';
|
||||
import { routeMeta } from '../../middleware/routeMeta.js';
|
||||
|
||||
const EDITOR_JSON_RESOURCE_FILES = {
|
||||
'item-overrides': 'src/data/itemOverrides.json',
|
||||
@@ -102,6 +103,7 @@ export function createEditorRoutes(config: AppConfig) {
|
||||
|
||||
router.get(
|
||||
'/api/editor/catalog/items',
|
||||
routeMeta({ operation: 'editor.catalog.items.list' }),
|
||||
asyncHandler(async (_request, response) => {
|
||||
response.json({
|
||||
assetPaths: await collectPngAssetPaths(
|
||||
@@ -113,6 +115,7 @@ export function createEditorRoutes(config: AppConfig) {
|
||||
|
||||
router.get(
|
||||
'/api/editor/json/:resourceId',
|
||||
routeMeta({ operation: 'editor.resource.read' }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const filePath = resolveEditorJsonFile(config, request.params.resourceId);
|
||||
response.json(await readEditorJsonFile(filePath));
|
||||
@@ -121,6 +124,7 @@ export function createEditorRoutes(config: AppConfig) {
|
||||
|
||||
router.post(
|
||||
'/api/editor/json/:resourceId',
|
||||
routeMeta({ operation: 'editor.resource.write' }),
|
||||
asyncHandler(async (request, response) => {
|
||||
if (!isEditorJsonPayload(request.body)) {
|
||||
throw badRequest('编辑器保存请求必须是 JSON 对象。');
|
||||
|
||||
Reference in New Issue
Block a user