fmode-api.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import {
  2. Agent,
  3. ArtifactJob,
  4. AuthSession,
  5. Deliverable,
  6. DeliverableGroup,
  7. MatrixProvision,
  8. Me,
  9. Task,
  10. UploadTicket,
  11. } from './models';
  12. /**
  13. * 数据访问抽象接口 —— 应用层与数据源之间的**唯一闸门**
  14. *
  15. * 依据《数据访问层设计.md》§一/§四。硬约束:
  16. * ① 页面/components **禁止** inject(FmodeApi) 或其实现(FR-14 AC-14.2)
  17. * ② 业务服务**禁止**直接 fetch / 直接用 fmode-ng(黄金规则 5)
  18. * ③ 每个数据域**只有一个** owner 服务
  19. *
  20. * 实现:`ParseApi`(真实,走 fmode-ng)/ `MockApi`(演示数据)。
  21. * 切换由 `environment.useMockData` 单开关控制(api.provider.ts)。
  22. *
  23. * ⚠️ `Project` / `EventItem` **不在此接口**:它们是派生投影(由 Agent + Deliverable 组合),
  24. * 由对应服务计算,见《数据访问层设计.md》§四.1。
  25. */
  26. export abstract class FmodeApi {
  27. // ---------- 鉴权 ----------
  28. /** 发送手机验证码(REST R4:`code === 1` 才算成功) */
  29. abstract sendMobileCode(mobile: string): Promise<void>;
  30. /** 验证码登录(REST R5:`code === 200 && data.token` 才算成功) */
  31. abstract loginWithMobile(mobile: string, code: string): Promise<AuthSession>;
  32. /** Token 直登(REST R1:`POST /parse/users/me`) */
  33. abstract loginWithToken(token: string): Promise<AuthSession>;
  34. /** 用已有 token 取回身份 */
  35. abstract fetchMe(token: string): Promise<Me>;
  36. // ---------- 数字生命(实况) ----------
  37. /** 列表查询(云函数 xlosi3m46q,`op:'query'`) */
  38. abstract fetchAgents(): Promise<Agent[]>;
  39. /** 单条查询;`code` 404/403 时返回 null */
  40. abstract fetchAgentStatus(agentId: string): Promise<Agent | null>;
  41. // ---------- 会话(Matrix) ----------
  42. /** 开通 Matrix 身份(云函数 RP5Ht55NeX);绑定表命中时 `cached: true` */
  43. abstract provisionMatrix(agentId: string): Promise<MatrixProvision>;
  44. /** 校验/刷新凭据(`validate: 1`) */
  45. abstract validateMatrix(agentId: string): Promise<MatrixProvision>;
  46. // ---------- 交付成果 ----------
  47. /** 列表(云函数 lfYlgU7SkK,`action:'list'`) */
  48. abstract fetchDeliverables(opts?: { agentId?: string; limit?: number }): Promise<Deliverable[]>;
  49. /** 按项目分组(`action:'byProject'`) */
  50. abstract fetchDeliverablesByProject(limit?: number): Promise<DeliverableGroup[]>;
  51. // ---------- 任务看板(FR-07b) ----------
  52. /**
  53. * 任务列表(按 agentId 过滤)。
  54. * 🔴 云函数接收端**尚未实现**:就绪前实现方须返回**空数组**,**禁止伪造数据**
  55. * (SPEC FR-07b「依赖声明」)。
  56. */
  57. abstract fetchTasks(agentId: string): Promise<Task[]>;
  58. /** 任务详情(含 runs / comments / attachments) */
  59. abstract fetchTaskDetail(taskId: string): Promise<Task | null>;
  60. // ---------- 上传 ----------
  61. /** 取预签名(云函数 AlP56LCKFm);缺 uploadUrl 即抛错,不得静默降级 */
  62. abstract requestUploadUrl(filename: string, mimeType: string, size: number): Promise<UploadTicket>;
  63. /** 预签名直传(前端不经手任何 OBS 密钥) */
  64. abstract uploadFile(
  65. file: File,
  66. onProgress?: (pct: number) => void,
  67. ): Promise<{ key: string; publicUrl: string }>;
  68. // ---------- 成果文件入库(云盘同步) ----------
  69. /**
  70. * 告诉平台「这个成果的附件在哪、该传到哪」。
  71. *
  72. * 平台侧(云函数 lfYlgU7SkK `action:'requestSync'`):
  73. * ① 按文件名取 OBS 预签名(与前端上传**同一条**上传通道)
  74. * ② 把预签名地址写进该成果的待同步队列,**由容器自己下载原文件后 PUT**
  75. * ③ 收到文件后回写 `artifacts[i].url` 为云盘公网地址
  76. *
  77. * 为什么不在这里直接传:文件在**容器里**(如 /opt/data/...),
  78. * 浏览器拿不到,只能由容器完成搬运。前端负责的是「登记 + 触发」。
  79. */
  80. abstract requestArtifactSync(input: {
  81. deliverableId: string;
  82. /** 容器内的原始路径 */
  83. source: string;
  84. filename: string;
  85. }): Promise<{ queued: boolean; message?: string }>;
  86. // ---------- 成果文件搬运队列(容器侧轮询) ----------
  87. /**
  88. * 登记一次搬运:把容器里的 `source` 文件搬到云盘。
  89. *
  90. * 🔴 **只报「传哪个」,不报「传到哪」** —— 上传地址与必需的头由云函数
  91. * 用服务端密钥签好后写进队列,客户端不接触任何密钥
  92. * (2026-10-21 定稿;此前的「App 自签」方案已废弃)。
  93. *
  94. * @param index 附件在成果 `artifacts[]` 中的下标 —— 保证同名附件不互相覆盖
  95. */
  96. abstract enqueueArtifactJob(job: {
  97. deliverableId: string;
  98. agentId: string;
  99. /** 容器内的原始路径 */
  100. source: string;
  101. filename: string;
  102. index: number;
  103. }): Promise<{ id: string }>;
  104. /** 容器领取待办(按 agentId,只取未完成且未过期的) */
  105. abstract fetchArtifactJobs(agentId: string): Promise<ArtifactJob[]>;
  106. /** 容器回执:上传成功(带公网地址)/ 失败(带原因) */
  107. abstract ackArtifactJob(input: {
  108. id: string;
  109. ok: boolean;
  110. publicUrl?: string;
  111. error?: string;
  112. }): Promise<void>;
  113. }