diff --git a/.agents/skills/sync-backend-api/SKILL.md b/.agents/skills/sync-backend-api/SKILL.md index 35762863..7a2d2bad 100644 --- a/.agents/skills/sync-backend-api/SKILL.md +++ b/.agents/skills/sync-backend-api/SKILL.md @@ -1,110 +1,378 @@ --- name: sync-backend-api -description: Sync frontend code with backend OpenAPI specification +description: Sync this Next.js frontend with backend API documentation --- # sync-backend-api -根据用户指定的模块或接口,根据后端文档更新前端代码(服务层、仓库层、UI层)。 +根据后端文档同步当前 Next.js / TypeScript 项目的前端代码。 ## 使用场景 -当后端 API 发生变更时(如新增接口、修改参数),使用此 skill 同步更新前端代码。 +当后端 API 新增、删除、字段结构调整、响应语义变化时,使用本 skill 更新: -## 可用模块 +- 网络层 +- schema / DTO +- repository +- storage +- XState store +- UI 调用逻辑 +- mock 数据 +- 单元测试 -| 模块 | 说明 | API 路径 | -| -------- | ---------- | -------------- | -| `auth` | 认证模块 | /api/auth/\* | -| `chat` | 聊天模块 | /api/chat/\* | -| `user` | 用户模块 | /api/user/\* | -| `verify` | 验证码模块 | /api/verify/\* | +## 当前项目结构 -## 使用方式 +| 层级 | 路径 | 说明 | +| --- | --- | --- | +| API Path | `src/data/services/api/api_path.ts` | 统一维护接口路径 | +| API Service | `src/data/services/api/*_api.ts` | 调用 `httpClient` 并解析响应 | +| Schema | `src/data/schemas//*.ts` | Zod schema,负责防御性解析 | +| DTO | `src/data/dto//*.ts` | DTO class,封装 `fromJson/toJson` | +| Repository Interface | `src/data/repositories/interfaces/i*_repository.ts` | 仓库接口 | +| Repository | `src/data/repositories/*_repository.ts` | 业务仓库实现 | +| Storage | `src/data/storage/*` | 本地持久化 | +| Store | `src/stores//*` | XState 状态机、actors、helpers、sync | +| UI | `src/app/**` | Next.js app router UI | +| Mock | `src/data/mock//**` | 模拟请求 / 响应数据 | +| Tests | `**/__tests__/*.test.ts` | DTO、helper、machine transition 测试 | -### 按模块更新(可多选,用逗号分隔) +## 支持模块 -```bash -根据后端文档更新 auth,chat 模块 -``` +| 模块 | API 路径 | 主要文件 | +| --- | --- | --- | +| `auth` | `/api/auth/*`, `/api/verify/*` | `auth_api.ts`, `auth_repository.ts`, `src/stores/auth/*` | +| `chat` | `/api/chat/*` | `chat_api.ts`, `chat_repository.ts`, `src/stores/chat/*` | +| `user` | `/api/user/*` | `user_api.ts`, `user_repository.ts`, `src/stores/user/*` | +| `payment` | `/api/payment/*` | `payment_api.ts`, `payment_repository.ts`, `src/stores/payment/*` | +| `metrics` | `/api/metrics/*` | `metrics_api.ts`, `metrics_repository.ts` | -### 按接口更新(可多选,用逗号分隔) +## 后端文档来源 -```bash -根据后端文档更新接口 /api/auth/sendCode -根据后端文档更新接口 /api/chat/send -根据后端文档更新接口 /api/auth/register,/api/user/profile -``` +优先读取用户指定的文档或文件片段。 -### 混合同步 +常见文档: -```bash -根据后端文档更新 auth 模块和接口 /api/chat/send -``` +- `FRONTEND_VIP_CREDIT_API.md` +- `PAYWALL_API.md` +- `docs-change.md` +- `docs/backend/openapi.json` -## OpenAPI 文档路径 - -- 测试环境: `docs/backend/openapi.json` +如果用户没有指定文档,先搜索相关 API 路径或模块名,再选择最相关文档。 ## 更新流程 -### 1. 读取 OpenAPI 文档 +### 1. 检查工作区 -读取 `docs/backend/openapi.json`,根据用户指定的模块筛选接口。 +开始前必须执行: -### 2. 分析接口变更 - -- 新增接口:创建对应的模型、API 方法 -- 修改接口:更新现有模型字段 -- 删除接口:移除不再使用的代码 - -### 3. 更新数据模型 (lib/data/models/) - -- 根据 schema 创建/更新 freezed 模型 - -### 4. 更新 API 客户端 (lib/data/services/) - -- 更新 `*_api_client.dart` 中的接口定义 -- 更新 `api_path.dart` 中的路径常量(如有新增) -- 运行 build_runner 生成 `*_api_client.g.dart` - -### 5. 更新仓储层 (lib/data/repositories/) - -- 更新 `*_repository.dart` 接口定义 -- 更新 `*_repository_impl.dart` 实现 - -### 6. 更新 UI 层 - -- 根据功能模块更新对应 BLoC/ViewModel -- 更新 UI 调用逻辑 - -### 7. 生成代码 - -运行 `dart run build_runner build --delete-conflicting-outputs` - -## 示例 - -**按模块更新**: - -``` -根据后端文档更新 auth 模块 -根据后端文档更新 auth,user,verify 模块 +```bash +git status --short ``` -**按接口更新**: +如果存在无关未提交改动: -``` -根据后端文档更新接口 /api/auth/sendCode -根据后端文档更新接口 /api/chat/send -根据后端文档更新接口 /api/auth/register,/api/user/profile +- 不要回退。 +- 不要覆盖。 +- 修改前先确认本次任务需要触碰的文件。 +- 提交时只暂存本次任务相关文件。 + +### 2. 阅读后端文档 + +确认: + +- API method +- API path +- 请求参数 +- 响应结构 +- 字段是否可空 +- 旧字段是否废弃 +- 是否需要认证 token +- 是否影响本地持久化 +- 是否影响状态机或 UI + +如果用户明确要求“不兼容旧字段,彻底更新”,不要保留旧字段 fallback。 + +否则 schema 应尽量防御性解析,例如: + +```ts +z.string().nullable().transform((v) => v ?? "").default("") ``` -**混合同步**: +项目已有通用 helper: -``` -根据后端文档更新 auth 模块和接口 /api/chat/send +```ts +src/data/schemas/nullable-defaults.ts ``` -## 注意事项 +### 3. 更新 API Path + +新增或修改: + +```ts +src/data/services/api/api_path.ts +``` + +示例: + +```ts +static readonly userEntitlements = `${ApiPath._user}/entitlements`; +``` + +### 4. 更新 Schema + +在对应模块下新增或修改: + +```text +src/data/schemas//*.ts +``` + +要求: + +- 使用 Zod。 +- 输出类型用 `z.output`。 +- 输入类型用 `z.input`。 +- 对后端可能返回 `null` 的字段做防御性处理。 +- 不要在 schema 中写 UI 逻辑。 + +示例: + +```ts +export const XxxResponseSchema = z.object({ + id: z.string(), + count: z.number().default(0), +}); + +export type XxxResponseInput = z.input; +export type XxxResponseData = z.output; +``` + +### 5. 更新 DTO + +在对应模块下新增或修改: + +```text +src/data/dto//*.ts +``` + +DTO 统一模式: + +```ts +export class XxxResponse { + declare readonly id: string; + + private constructor(input: XxxResponseInput) { + const data = XxxResponseSchema.parse(input); + Object.assign(this, data); + Object.freeze(this); + } + + static from(input: XxxResponseInput): XxxResponse { + return new XxxResponse(input); + } + + static fromJson(json: unknown): XxxResponse { + return XxxResponse.from(json as XxxResponseInput); + } + + toJson(): XxxResponseData { + return XxxResponseSchema.parse(this); + } +} +``` + +### 6. 更新 API Service + +修改: + +```text +src/data/services/api/_api.ts +``` + +统一调用方式: + +```ts +const env = await httpClient>(ApiPath.xxx, { + method: "POST", + body: request.toJson(), +}); +return XxxResponse.fromJson(unwrap(env) as Record); +``` + +注意: + +- 不要直接返回 `unknown`。 +- 不要在 API 层处理 UI 行为。 +- multipart 仍使用 `FormData`。 + +### 7. 更新 Repository + +修改: + +```text +src/data/repositories/interfaces/i_repository.ts +src/data/repositories/_repository.ts +``` + +Repository 返回项目通用 `Result`: + +```ts +async getXxx(): Promise> { + return Result.wrap(() => this.api.getXxx()); +} +``` + +Repository 可以做轻量编排,例如: + +- request DTO 构造 +- 多接口组合 +- 本地 storage 同步 +- local model 与 DTO 转换 + +不要把 React / UI 逻辑放进 repository。 + +### 8. 更新 Storage + +如果接口影响本地持久化,修改: + +```text +src/data/storage/storage_keys.ts +src/data/storage//* +``` + +原则: + +- 只持久化业务需要的最小字段。 +- 不要为了接口响应完整而盲目存完整 payload。 +- storage 使用 `SpAsyncUtil` 和 Zod schema 校验。 + +示例: + +```ts +getEntitlementSnapshot(): Promise> { + return SpAsyncUtil.getJson( + StorageKeys.userEntitlementSnapshot, + UserEntitlementSnapshotSchema, + ); +} +``` + +### 9. 更新 Store / 状态机 + +如果接口影响状态流,修改: + +```text +src/stores//-state.ts +src/stores//-events.ts +src/stores//-machine.ts +src/stores//-machine.actors.ts +src/stores//-machine.helpers.ts +src/stores//-context.tsx +src/stores//*-sync.tsx +``` + +项目约定: + +- API 调用放在 `actors.ts`。 +- 纯函数转换放在 `helpers.ts`。 +- 跨 store 监听放在 `*-sync.tsx`。 +- 页面组件不承担全局监听职责。 +- 状态机事件应表达业务事实,而不是 UI 操作细节。 + +### 10. 更新 UI + +只有在接口变更影响展示或用户明确要求时,才修改: + +```text +src/app/** +``` + +原则: + +- UI 只负责渲染和派发事件。 +- 不在页面组件里直接写复杂业务编排。 +- 全局监听逻辑优先放到 `stores/*/*-sync.tsx`。 + +### 11. 更新桶文件 + +如果新增 schema / DTO / component,更新对应 `index.ts`: + +```text +src/data/schemas//index.ts +src/data/dto//index.ts +src/app/**/components/index.ts +``` + +如果项目使用 barrelsby 生成,不要手动破坏现有导出格式。 + +### 12. 更新 Mock 数据 + +如果新增或调整数据结构,按一个结构一个文件的原则放到: + +```text +src/data/mock//requests +src/data/mock//responses +src/data/mock//websocket-events +``` + +不要把多个结构塞进一个大 JSON。 + +### 13. 更新测试 + +优先补充: + +- DTO / schema parse 测试 +- helper 纯函数测试 +- XState transition 测试 +- repository 编排测试(如已有测试基础) + +常见位置: + +```text +src/data/dto//__tests__/*.test.ts +src/stores//__tests__/*.test.ts +``` + +### 14. 验证 + +至少运行: + +```bash +pnpm exec tsc --noEmit +pnpm exec eslint +pnpm exec vitest run +``` + +如果没有相关测试,需要说明未覆盖的风险。 + +## 常见任务模板 + +### 新增接口 + +1. 文档确认 path / method / payload。 +2. `api_path.ts` 新增路径。 +3. 新增 schema。 +4. 新增 DTO。 +5. API service 新增方法。 +6. repository interface + implementation 新增方法。 +7. 如需要,接入 store actor / event。 +8. 补 mock 和测试。 +9. 运行验证。 + +### 修改响应字段 + +1. 更新 schema。 +2. 更新 DTO declare 字段和 `toJson`。 +3. 更新 mapper/helper。 +4. 更新 UI 或状态机依赖字段。 +5. 更新 mock 和测试。 +6. 运行验证。 + +### 删除旧接口 + +1. 搜索所有引用。 +2. 删除 API path / API method / repository method。 +3. 删除 DTO/schema/mock/test。 +4. 更新状态机和 UI 调用。 +5. 运行 `rg` 确认无残留引用。 +6. 运行验证。 -1. **测试覆盖**: 更新后运行相关测试确保正常