组织清晰、可替换的模块与接口
分开业务、服务和界面
界面收集输入并显示状态;业务定义提示词、计分、奖励和结果用途;服务模块处理外部请求、认证、查询和下载。初始化时显式提供实现,不在每个按钮中拼供应商 URL,也不靠运行时猜测全局方法。
测试实现与真实实现使用同一个业务入口和结果结构。替换服务时保持参数、结果与失败含义,业务和界面不读取供应商原始 JSON。文件名沿用工程习惯,已有合理边界直接复用。
用具体操作划分接口
| 需求 | 模块应提供的职责 |
|---|---|
| 一次生成/购买/提交成绩 | 创建操作、识别同一次动作、返回稳定标识与明确结果 |
| 长任务 | 查询单任务、查询当前用户的运行中任务、取得最终结果 |
| 订单 | 读取订单状态、权益和库存;与任务处理状态分开 |
| 列表界面 | 分页、筛选、更新版本和错误;空列表与读取失败分开 |
| 图片结果 | 稳定素材标识、访问地址和有效期、尺寸/格式信息 |
| 联机 | 房间状态、成员准备、传输状态、游戏模拟各自独立 |
这些是项目自身接口的职责,不限定函数名或 HTTP 路由。先读现有服务支持的操作,再补实际缺口。接口说明至少写输入、输出、身份范围、副作用、错误和恢复方式;不要用一个含义不明的 success 覆盖所有业务状态。
AI 任务接口示例
项目自己的服务模块可按以下职责组织,界面只读规范化结果,供应商差异留在实现中:
type TaskView = {
id: string;
actionId: string;
revision: number;
state: 'waiting' | 'processing' | 'reviewing' | 'succeeded' | 'rejected' | 'failed';
stageLabel: string;
createdAt: string;
resultId?: string;
orderId?: string;
error?: { code: string; message: string; recovery: 'read' | 'confirm' | 'none' };
};
interface GenerationService {
submit(actionId: string, input: GenerationInput): Promise<TaskView>;
recover(actionId: string): Promise<TaskView | null>;
getTask(taskId: string): Promise<TaskView>;
listTasks(query: { activeOnly: boolean; cursor?: string }): Promise<{
items: TaskView[];
nextCursor?: string;
}>;
getResult(resultId: string): Promise<GeneratedResult>;
}
GenerationInput 和 GeneratedResult 按文本/图片业务定义,不让 UI 依赖供应商原始响应。可信服务验证当前身份和范围,revision 用于同一任务的更新排序;submit 的同动作幂等与 recover 必须由服务真正实现。项目使用自己的命名和协议,只有读取、恢复和错误语义一致时才替换实现。
状态存储按 id 合并详情与列表,拒绝更旧的 revision;出现同版本但内容冲突时记录错误并重新读取,不能任意选一个覆盖。订单模块另提供原订单查询,不把支付状态塞进 task.state。没有启用订单的作品不增加订单流程。
一次动作和稳定标识
用户确认新动作时创建操作标识,网络重试、重新渲染和查询不重新创建。服务结果未知时保留原输入与标识,先核对原操作;只有真实服务保证幂等才自动重试有副作用的提交。读取失败不等于业务失败。
区分业务动作 ID、订单 ID、任务 ID、素材 ID 和房间/对局代次,分别关联,不混作同一个键。账号、作品和应用范围由自己的可信后端验证,不能仅相信浏览器传入的名称或 ID。
集中状态,填充真实界面
一个状态存储统一接收详情、列表、推送/轮询结果,组件订阅同一份数据。按稳定标识合并,按服务提供的更新版本拒绝旧结果;没有可比较版本时明确刷新策略,不用不同机器的时钟猜更新顺序。
列表只显示已实际加载范围,分页未取完不声称“全部任务”。切换账号或作品使旧请求失效,取消本地请求后仍要拒绝迟到回包。刷新从服务重新查询,浏览器缓存只作恢复线索。
控制资源与失败
每种请求有截止、并发上限和退避;后台页面降低查询频率,订阅由一个管理器共享。退出时取消监听和定时器,释放图片 Blob 与无引用资源。长任务等待可以持续,单次网络连接不能无限挂起。
错误区分取消、认证失效、校验失败、限流、网络失败和结果未知。向用户提供对应恢复动作,不把所有错误变成“再试一次生成”。密钥留在可信后端,日志只保留必要标识和计数,不保存签名 URL、token 或原始私密输入。
检查接口是否可维护
替换一个服务实现后,用原有 UI 和同一组测试完成正常、失败、延迟和恢复场景。业务代码不新增供应商判断,原语义不丢失,资源和身份范围仍受控,即达到模块隔离目标。