# 组织清晰、可替换的模块与接口

## 分开业务、服务和界面

界面收集输入并显示状态；业务定义提示词、计分、奖励和结果用途；服务模块处理外部请求、认证、查询和下载。初始化时显式提供实现，不在每个按钮中拼供应商 URL，也不靠运行时猜测全局方法。

测试实现与真实实现使用同一个业务入口和结果结构。替换服务时保持参数、结果与失败含义，业务和界面不读取供应商原始 JSON。文件名沿用工程习惯，已有合理边界直接复用。

## 用具体操作划分接口

| 需求 | 模块应提供的职责 |
| --- | --- |
| 一次生成/购买/提交成绩 | 创建操作、识别同一次动作、返回稳定标识与明确结果 |
| 长任务 | 查询单任务、查询当前用户的运行中任务、取得最终结果 |
| 订单 | 读取订单状态、权益和库存；与任务处理状态分开 |
| 列表界面 | 分页、筛选、更新版本和错误；空列表与读取失败分开 |
| 图片结果 | 稳定素材标识、访问地址和有效期、尺寸/格式信息 |
| 联机 | 房间状态、成员准备、传输状态、游戏模拟各自独立 |

这些是项目自身接口的职责，不限定函数名或 HTTP 路由。先读现有服务支持的操作，再补实际缺口。接口说明至少写输入、输出、身份范围、副作用、错误和恢复方式；不要用一个含义不明的 success 覆盖所有业务状态。

## AI 任务接口示例

项目自己的服务模块可按以下职责组织，界面只读规范化结果，供应商差异留在实现中：

```ts
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 和同一组测试完成正常、失败、延迟和恢复场景。业务代码不新增供应商判断，原语义不丢失，资源和身份范围仍受控，即达到模块隔离目标。
