【西游劫:第三篇】 API 路由设计详解
API 路由设计详解一、设计总览在构建一个沉浸式的文字冒险游戏《西游记》题材时后端 API 需要同时满足多种需求支持存档管理让玩家可以拥有多个游戏进度提供游戏初始化与状态快照保证每次进入游戏都能准确恢复现场处理核心交互既要能快速返回完整结果JSON也要能流式输出SSE以提升 LLM 生成的体验记录消息历史便于复盘并提供LLM 连通性测试帮助调试外部模型配置。基于以上考量设计了一套 RESTful 风格的路由围绕/api/game资源展开。下面将按章节逐一拆解每个接口的作用、内部逻辑以及设计背后的思考。路由总览Mermaid 思维导图游戏API路由/savesGET 列出所有存档DELETE 删除指定存档/initPOST 创建新游戏/actionPOST 主交互Accept: application/json - 非流式全量返回Accept: text/event-stream - SSE流式响应/stateGET 获取当前完整状态/messageGET 分页拉取消息历史/test-endpointPOST 测试外部LLM连通性二、存档管理/api/game/saves作用列出所有游戏存档的基本信息以及删除不再需要的存档。GET 请求 —— 获取存档列表返回内容每个存档的摘要包括id、updatedAt等基础字段从playerState和worldStateJSON 中解析出的关键数值如等级、修为、当前位置、当前章节聚合统计信息该存档下的消息数量、物品数量、任务数量通过 Prisma 的_count实现设计意图玩家可能在多个存档间切换列表视图需要快速呈现“一眼看懂”的进度信息而不需要加载完整的游戏状态。通过预解析 JSON 字段和关联计数可以避免额外的数据库查询。DELETE 请求 —— 删除存档参数查询参数?id存档ID实现利用数据库的级联删除Prisma 的cascade配置一次删除GameSave及其关联的PlayerState、WorldState、Inventory、NPC 关系、消息、任务进度等所有子表数据保证无残留。三、新游戏启动POST /api/game/init当玩家点击“新游戏”时后端需要快速生成一份完整的初始世界。请求参数{playerName:string// 非空最长20字符statOverrides?:{// 可选允许覆盖部分初始属性strength?:number// 1-20agility?:numberwisdom?:numberluck?:number}}核心流程Mermaid 流程图接收 playerName 与可选的 statOverrides调用 initGame 创建核心记录GameSave, PlayerState, WorldState, Inventory, NPC关系并发查询 QuestLog 和 WorldEvent 表组装初始任务列表与世界事件返回完整初始化数据结束典型初始值以“五行山脚下”剧本为例玩家状态生命 100 / 法力 20 / 位置 五行山脚下 / 铜钱 10世界状态贞观十三年春辰时黎明附近有明显 NPC孙悟空初始道具粗布衣 ×1已装备、馒头 ×3默认属性力量 8 / 敏捷 10 / 慧根 12 / 运气 6 —— 可通过statOverrides自定义调整返回示例{gameSaveId:uuid,playerState:{...},worldState:{...},narrative:你从一块青石上醒来...,quests:[...],worldEvents:[...]}为什么单独返回narrative初始化本身就是一次“叙事开头”直接给出一段文字描述可以让前端立即显示避免发起一次额外的/action请求。四、主交互POST /api/game/action这是游戏最核心的接口 —— 玩家输入一个动作如“与孙悟空对话”、“跳到悬崖下”后端调用大语言模型LLM生成剧情响应。请求参数{gameSaveId:stringaction:string// 非空最长500字符llmConfig?:{// 可选用于临时覆盖全局LLM配置model?:stringtemperature?:number// 0-2默认0.8maxTokens?:number// 256-4096默认2048topP?:number// 0.1-1.0默认0.9customSystemPrompt?:stringcustomEndpoint?:stringcustomApiKey?:stringcustomModelId?:string}}流式SSE与 非流式JSON的协商策略前端通过设置Accept请求头来决定使用哪种响应模式Accept: text/event-stream→SSE 流式其他或不传 →JSON 非流式包含 text/event-stream不包含或为其他客户端发起 POST /action检查 Accept 头调用 processActionStream返回 SSE 响应流客户端逐块接收增量文本调用 processAction等待完整 GameResponse加载最新玩家/世界状态返回完整 JSON 快照非流式处理步骤调用processAction(gameSaveId, action, llmConfig)—— 这是封装了 LLM 调用、状态更新、剧情生成的纯函数。等待返回完整的GameResponse包含narrative、stateChanges等。额外调用loadGameState(gameSaveId)重新加载数据库中的最新状态确保返回给前端的数据与数据库完全一致。返回一个包含以下字段的 JSON 对象narrative生成的剧情文本stateChanges本次动作导致的状态变更摘要playerState、worldState、inventory、npcRelations等完整快照流式处理步骤调用processActionStream(...)获得一个AsyncIterable异步可迭代对象它会随着 LLM 生成逐块产出文本片段。将 AsyncIterable 包装为标准的Server-Sent Events (SSE)响应设置Content-Type: text/event-stream保持连接打开不断发送事件定义的事件类型heartbeat表示 LLM 已开始处理用于避免前端超时误判。chunk携带一部分增量文本例如每次输出几个 token。done完整响应结束携带最终的结构化结果与 JSON 模式的返回体结构相同。error出现异常时发送错误信息并关闭流。为什么需要两种模式JSON 模式简单可靠适合网络状况较好或希望一次拿到所有数据的场景如批量测试。SSE 模式极大提升了用户体验 —— 玩家能在 LLM 生成过程中“实时阅读”逐渐浮出的文字感觉更像真人在叙述。同时 SSE 天然支持心跳防止代理服务器超时断开。五、状态快照GET /api/game/state玩家切换面板背包、属性、任务或需要刷新界面时客户端可请求此接口获取当前所有状态的“合影”。查询参数gameSaveId必填作为查询字符串参数例如?gameSaveIdxxx返回内容gameSave存档摘要id、更新时间等playerState生命、法力、位置、属性等JSON 解析后的对象worldState世界时间、场景描述、当前主线章节等inventory背包物品列表含装备标记npcRelations与每个 NPC 的好感度、已知信息activeQuests进行中的任务列表activeWorldEvents当前触发的时间限定事件recentMessages最近 5 条消息记录截断长文本并补充时间戳为什么只返回最近 5 条消息状态快照主要用于 UI 面板的刷新不需要完整历史。如果需要查看全部对话应该使用专门的/message分页接口。六、消息历史GET /api/game/message专用于翻阅玩家与游戏之间的所有对话记录。分页参数参数默认值最大值说明limit50200每页条目数offset0无跳过的条目数非负整数实现细节参数校验limit必须为正整数offset为非负整数否则返回 HTTP 400。排序数据库查询按createdAt DESC获取最新记录然后在前端 / 中间层调用reverse()转换成时间正序从旧到新便于按页阅读。元数据处理每条消息的metadata字段存储为 JSON 字符串会被解析成对象返回。返回结构{messages:[{id:...,role:user,content:我要跳到悬崖下,createdAt:2025-...,metadata:{actionType:dangerous}},...],pagination:{total:487,limit:50,offset:0,hasMore:true}}七、LLM 连通性测试POST /api/game/test-endpoint当管理员或玩家在配置自定义 LLM 端点时需要一个安全的探测接口来验证端点是否可用、认证是否正确。请求参数{endpoint:string// 必填会进行 URL 格式校验modelId:string// 必填模型名称如 gpt-3.5-turboapiKey?:string// 选填如果提供则作为 Bearer Token}测试逻辑构造最小化请求体{model:modelId,messages:[{role:user,content:你好}],max_tokens:10,temperature:0.1}设置请求头Authorization: Bearer apiKey如果提供了 apiKey超时限制15 秒防止阻塞。成功条件收到符合 OpenAI 风格的响应格式至少包含choices数组。返回信息请求耗时毫秒、响应状态、是否通过了格式校验。设计考量该接口不会保存任何配置仅做探测。真正的 LLM 配置会由用户在自己的客户端或服务器全局配置中管理不会通过此接口写入数据库。八、统一的错误处理模式为了给前端一致的错误反馈所有 API 端点遵循相同的错误处理约定示例错误响应{success:false,error:gameSaveId 不能为空}为什么统一返回success: false前端可以通过该字段快速判断调用是否成功而不用依赖 HTTP 状态码的解析尤其某些代理环境可能篡改状态码。同时保留状态码用于网关层监控。九、总结本章详细介绍了游戏 API 的每一个端点从存档管理、新游戏初始化到核心的动作交互支持流式与 JSON 两种模式再到状态快照、消息历史以及 LLM 连通性测试。整套设计遵循以下原则职责单一每个接口只做一件事便于维护。性能友好通过分页、滑动窗口、并发查询等手段减少数据库负担。体验优先SSE 流式响应让 LLM 输出更自然。灵活可配玩家或管理员可以临时覆盖 LLM 参数便于调试和个性化。通过这样的 API 结构前端可以轻松构建出沉浸、流畅、具有丰富剧情分支的文字冒险游戏。