bash
# Skill: 业务模块全流程交付
触发方式:当用户说"开发 XX 模块"、"实现 XX 功能"、"交付 XX" 时,按此流程推进。
---
## 总体原则
- 每步有明确产出物,编译或验证通过再进下一步
- 关键设计决策必须等用户确认,不自行拍板
- 先咨询后实现,先后端后前端
---
## Step 1 --- 咨询与设计(不写代码)
**目标**:对齐需求边界和数据模型,避免返工。
**产出物**:
- 候选方案对比表(2-3 个方案,标明取舍)
- 数据模型草稿(表名、核心字段、关联关系)
- 接口清单(Method + Path + 简要说明)
**流程**:
1. 分析业务需求,列出候选技术方案
2. 给出推荐方案及理由
3. 提出需要用户决策的问题(如:是否需要软删除?JSON 字段还是关联表?)
> ⚠️ **等待用户确认**:数据模型和接口设计确认后再进入 Step 2
**注意事项**:
- JSON 字段(如 auth_config)需要用 `@TableName(autoResultMap = true)` + `JacksonTypeHandler`,否则反序列化为 null
- 高频写入的表(如 health 记录)不要继承 BaseEntity(避免逻辑删除和审计字段的写放大)
- 敏感字段(如 api_key)不能出现在任何响应 DTO 中,用 `authConfigured: boolean` 代替
---
## Step 2 --- 更新 schema.sql
**目标**:数据库 DDL 与设计对齐。
**产出物**:
- `hify-app/src/main/resources/db/schema.sql` 新增表 DDL
- `hify-app/src/main/resources/db/schema-h2.sql` H2 兼容版本(JSON → CLOB)
**验证**:
```bash
# 用 mock profile 启动,H2 会自动执行 schema-h2.sql
java -jar hify-app/target/hify-app-0.0.1-SNAPSHOT.jar --spring.profiles.active=mock
# 访问 H2 控制台确认表已创建
open http://localhost:8080/h2-console
```
**注意事项**:
- H2 不支持 `JSON` 类型,必须用 `CLOB` 替代,且两个 schema 文件都要维护
- 索引命名规范:`idx_{表名}_{字段名}`
- 所有外键在应用层维护,不建数据库级外键约束
---
## Step 3 --- Entity + Mapper
**目标**:ORM 层与数据库表对齐。
**产出物**:
- `entity/XxxEntity.java`(继承 BaseEntity 或独立)
- `mapper/XxxMapper.java`(继承 BaseMapper,复杂查询加 `@Select`)
**验证**:
```bash
mvn clean install -DskipTests -pl hify-{module} -am
```
**注意事项**:
- 有 JSON 字段的 Entity 必须加 `@TableName(autoResultMap = true)`,字段上加 `@TableField(typeHandler = JacksonTypeHandler.class)`
- MyBatis-Plus 3.5.9 分页插件在独立模块 `mybatis-plus-jsqlparser`,缺少会导致 `PaginationInnerInterceptor` 找不到
- 自定义查询方法返回 `Optional<T>` 时用 `@Select` + default 方法封装
---
## Step 4 --- DTO
**目标**:定义请求/响应对象,隔离内部实体。
**产出物**:
- `dto/XxxCreateRequest.java`(`@Valid` 校验注解)
- `dto/XxxUpdateRequest.java`
- `dto/XxxQueryRequest.java`(分页参数继承或包含 page/pageSize)
- `dto/XxxDetailResponse.java`(静态工厂方法 `from(entity, ...)`)
**注意事项**:
- 响应 DTO 不能暴露 authConfig / password 等敏感字段
- 分页响应统一用 `PageResult.of(list, total, page, pageSize)`,返回 `Result<PageResult<T>>`
- ⚠️ 不要让 `PageResult` 继承 `Result`,否则序列化后 `data` 字段是数组,`total` 在外层,前端拦截器解包后丢失 `total`
- `PageResult` 正确结构:`{ "data": { "list": [...], "total": N, "page": 1, "pageSize": 20 } }`
---
## Step 5 --- Service(业务逻辑)
**目标**:实现核心业务,接口与实现分离。
**产出物**:
- `service/XxxService.java`(接口)
- `service/impl/XxxServiceImpl.java`(实现)
**流程**:
1. CRUD 基础逻辑(含唯一性校验、级联查询)
2. 特殊业务逻辑(如连通性测试、健康检查)
3. 缓存注解(`@Cacheable` / `@CacheEvict`)
**验证**:
```bash
mvn clean install -DskipTests -pl hify-{module} -am
```
**注意事项**:
- 跨模块调用走 Service 接口,不直接引用其他模块的 Mapper 或 Entity
- 外部 HTTP 调用(如 LLM API 连通性测试)必须设超时,用 `LlmHttpClient` 的 `get(url, headers, testClient)`
- 健康检查定时任务加 `@ConditionalOnProperty(name = "hify.health-check.enabled", havingValue = "true", matchIfMissing = true)`,mock profile 设为 false
- 使用 `@Qualifier("llmExecutor")` 注入线程池,禁止 `new Thread()` 或默认线程池
---
## Step 6 --- Controller
**目标**:暴露 REST 接口,只做参数校验和 Service 调用。
**产出物**:
- `controller/XxxController.java`
**验证**:
```bash
mvn clean install -DskipTests
java -jar hify-app/target/hify-app-0.0.1-SNAPSHOT.jar --spring.profiles.active=mock
```
逐条跑 curl:
```bash
# 创建
curl -s -X POST http://localhost:8080/api/v1/{resource} \
-H 'Content-Type: application/json' \
-d '{...}' | jq .
# 列表
curl -s 'http://localhost:8080/api/v1/{resource}?page=1&pageSize=10' | jq .
# 详情
curl -s http://localhost:8080/api/v1/{resource}/1 | jq .
# 更新
curl -s -X PUT http://localhost:8080/api/v1/{resource}/1 \
-H 'Content-Type: application/json' \
-d '{...}' | jq .
# 删除
curl -s -X DELETE http://localhost:8080/api/v1/{resource}/1 | jq .
```
> ⚠️ **等待用户确认**:所有 curl 返回预期结果后再进入前端对接
**注意事项**:
- Spring Boot 3.2 必须在 `maven-compiler-plugin` 加 `<parameters>true</parameters>`,否则 `@PathVariable Long id` 参数名无法识别,导致 400 错误
- Controller 只调用 Service,不写业务逻辑,不直接操作 Mapper
---
## Step 7 --- 前端 API 文件
**目标**:封装后端接口,定义 TypeScript 类型。
**产出物**:
- `hify-web/src/api/{module}.ts`
**内容**:
- 请求/响应类型定义(与后端 DTO 字段对齐)
- 导出各接口方法(使用 `request.ts` 的 get/post/put/del)
**注意事项**:
- 前端 `request.ts` 拦截器会自动解包 `response.data.data`,API 方法的返回类型直接写业务数据类型,不需要包 `Result<T>`
- 列表接口返回类型写 `PageResult<T>`(包含 list/total/page/pageSize),对应后端解包后的 `data` 字段
---
## Step 8 --- 前端页面对接
**目标**:替换 mock 数据,接入真实 API。
**产出物**:
- 更新 `views/{module}/XxxList.vue`
**流程**:
1. 把 HifyTable 的 `api` prop 换成真实 API 方法
2. 表单提交换成 create/update API
3. 删除换成 delete API + useConfirm
4. 按需添加操作按钮(如测试连接)
5. 按需添加状态列(健康状态、关联数量等)
**验证**:
```bash
# 确保后端已启动
java -jar hify-app/target/hify-app-0.0.1-SNAPSHOT.jar --spring.profiles.active=mock
# 启动前端
cd hify-web && npm run dev
```
在浏览器 DevTools → Network 确认:
- 请求打到了后端(状态码 200,非 ERR_CONNECTION_REFUSED)
- 响应 `data.list` 是数组,`data.total` 是数字
- 表格有数据渲染(或显示"暂无数据"而非一直转圈)
> ⚠️ 如果页面一直转圈:先看 Network 标签确认请求状态码,再排查后端是否启动
**注意事项**:
- Vite 代理:`/api` → `http://localhost:8080`,前端 baseURL 设为 `/api`,后端路径 `/api/v1/xxx` 完整保留
- 前端 `env.d.ts` 不要写 `declare module '*.vue' { ... }`,会覆盖 Volar 的真实类型推断,导致组件 ref 的 expose 方法找不到
---
## 常见坑速查
| 现象 | 原因 | 修复 |
|------|------|------|
| 页面一直转圈 | 后端未启动 / 请求 pending | 先看 Network 状态码 |
| 列表有数据但 total=0 不显示分页 | PageResult 继承 Result 导致 data 是数组,total 在外层被拦截器丢弃 | PageResult 改为普通 POJO,data 包含 {list,total} |
| @PathVariable 400 错误 | 缺少 `-parameters` 编译参数 | pom.xml compiler plugin 加 `<parameters>true</parameters>` |
| JSON 字段反序列化 null | 缺少 autoResultMap=true 或 JacksonTypeHandler | Entity 加注解 |
| mock profile 启动失败 Bean 冲突 | RedisConfig 未排除 | 加 `@Profile("!mock")` |
| H2 启动报 SQL 错误 | schema.sql 用了 MySQL 专属语法(如 JSON 类型) | 维护独立 schema-h2.sql,JSON→CLOB |
| hify-common 改动后运行旧代码 | spring-boot:run 用了旧 jar | 改 hify-common 后必须先 `mvn install` |
Claude Code 生成第一版,再review 重点:
1、产出物是否明确?不是"做需求分析"就完了,而是"产出需求分析文档,包含功能范围、数据模型 DDL、设计决策及理由"。Claude Code 需要知道做到什么程度算完。
2、决策点是否标注?数据模型设计完、后端做完准备做前端之前------这些是你要拍板的地方,Skill 里要写"等待用户确认后再进入下一步"。
3、踩过的坑有没有写进去?比如,Entity 的 JSON 字段必须用 TypeHandler、schema.sql 要同步更新、前端对接时要更新路由配置。这些是 13 讲实际踩过的,写进 Skill 下次就不会重复踩。