- name
- java-crud-module
- description
- Use when scaffolding a new CRUD business module in a Spring Boot + MyBatis-Plus layered project. Covers the full 9-file flow: migration, entity, mapper, DTOs, converter, service, facade, and controller.
# CRUD 模块脚手架
为 Spring Boot + MyBatis-Plus 分层架构项目创建完整的业务模块。
**前置**: 遵守 `java-architecture-guide` 中的分层原则。
## 适用场景
- 新建标准业务资源模块
- 为已有项目补齐一套完整 CRUD 骨架
- 需要快速生成符合项目约定的 9 个核心文件
## 不适用
- 只改一个已有字段、接口或查询逻辑的局部需求
- 非 Spring Boot + MyBatis-Plus 分层项目
- 事件驱动、聚合根复杂编排或非典型 CRUD 场景
## 模块创建 Checklist
按此顺序创建,每步依赖前一步的产出:
```
Step 1: Migration SQL → 建表,定义数据模型
Step 2: Entity → 映射表结构到 Java 类
Step 3: Mapper → 空接口,继承 BaseMapper
Step 4: DTOs → 请求/响应对象
Step 5: Converter → MapStruct Entity ↔ DTO 转换
Step 6: Service 接口 → 定义业务方法签名
Step 7: Service 实现 → 实现业务逻辑
Step 8: Facade → 跨服务编排、事务管理
Step 9: Controller → HTTP 入口、参数校验
```
完成后更新 CHANGELOG.md。
## 命名规范速查
以资源名 `AlertPolicy`(表名 `alert_policy`)为例:
| 文件 | 命名 | 包路径 |
|------|------|--------|
| Migration | `YYYYMMDDHHMMSS_create_alert_policy.sql` | `migrations/{module}/scripts/` |
| Entity | `AlertPolicy.java` | `{service-module}/.../entity/` |
| Mapper | `AlertPolicyMapper.java` | `{service-module}/.../repository/` |
| Create DTO | `PolicyCreateReq.java` | `{service-module}/.../dto/policy/` |
| Update DTO | `PolicyUpdateReq.java` | `{service-module}/.../dto/policy/` |
| Card DTO | `PolicyCard.java` | `{service-module}/.../dto/policy/` |
| Detail DTO | `PolicyDetail.java` | `{service-module}/.../dto/policy/` |
| Converter | `PolicyConverter.java` | `{service-module}/.../converter/` |
| Service | `PolicyService.java` / `PolicyServiceImpl.java` | `{service-module}/.../service/` |
| Facade | `PolicyFacade.java` | `{web-module}/.../facade/` |
| Controller | `PolicyController.java` | `{web-module}/.../controller/` |
| API 路径 | `/api/v1/policies` | — |
**规则**:
- 类名 PascalCase,表名 snake_case,API 路径 kebab-case 复数
- DTO 包按模块子目录组织(`dto/policy/`, `dto/task/`)
- Facade 和 Controller 在 web 模块,其余在 service 模块
## 典型目录结构
```
project/
├── {service-module}/src/main/java/com/example/service/
│ ├── entity/ # Step 2
│ ├── repository/ # Step 3 (Mapper)
│ ├── dto/
│ │ └── {module}/ # Step 4
│ ├── converter/ # Step 5
│ └── service/
│ └── impl/ # Step 6-7
├── {web-module}/src/main/java/com/example/web/
│ ├── facade/ # Step 8
│ └── controller/ # Step 9
└── migrations/{module}/scripts/ # Step 1
```
## 各步骤要点
### Step 1: Migration SQL
参考 `java-db-migration` Skill。必备列:id, deleted, create_time, update_time。
### Step 2: Entity
```java
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("alert_policy")
public class AlertPolicy extends BizEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("name")
private String name;
// 枚举字段
@TableField("status")
private PolicyStatusEnum status;
// JSON 字段
@TableField(value = "config_json", typeHandler = JacksonTypeHandler.class)
private Map<String, Object> configJson;
// 逻辑删除
@TableLogic
private Boolean deleted;
}
```
### Step 3: Mapper
```java
@Mapper
public interface AlertPolicyMapper extends BaseMapper<AlertPolicy> {
// 空接口 - 不添加任何方法
}
```
**禁止** Mapper XML 自定义 SQL,所有查询在 Service 层用 `LambdaQueryWrapper` 构建。
### Step 4-5: DTOs + Converter
参考 `java-dto-converter` Skill。
### Step 6-7: Service
```java
// 接口
public interface PolicyService {
AlertPolicy getById(Long id);
Long create(PolicyCreateReq req);
void update(Long id, PolicyUpdateReq req);
void delete(Long id);
PageResult<PolicyCard> getPage(int page, int size, String name);
}
// 实现
@Slf4j
@Service
@RequiredArgsConstructor
public class PolicyServiceImpl implements PolicyService {
private final AlertPolicyMapper policyMapper;
private final PolicyConverter converter;
@Override
public AlertPolicy getById(Long id) {
AlertPolicy entity = policyMapper.selectById(id);
if (entity == null) {
throw new BusinessException(ErrorCode.POLICY_NOT_FOUND, "策略不存在: " + id);
}
return entity;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long create(PolicyCreateReq req) {
validateNameUnique(req.getName(), null);
AlertPolicy entity = converter.toEntity(req);
entity.setStatus(PolicyStatusEnum.DISABLED);
policyMapper.insert(entity);
return entity.getId();
}
}
```
### Step 8: Facade
```java
@Component
@Slf4j
@RequiredArgsConstructor
public class PolicyFacade {
private final PolicyService policyService;
private final TaskService taskService; // 可注入多个 Service
@Transactional(rollbackFor = Exception.class)
public Long create(PolicyCreateReq req) {
return policyService.create(req);
}
// 读操作:查询 + 丰富关联数据
public PolicyDetail getDetail(Long id) {
PolicyDetail detail = policyService.getDetail(id);
// 丰富关联数据
enrichWithRelatedData(detail);
return detail;
}
}
```
### Step 9: Controller
参考 `java-api-endpoint` Skill。
## 完成后检查
- [ ] 9 个文件全部创建
- [ ] 分层调用方向正确 (Controller → Facade → Service → Mapper)
- [ ] Service 没有注入其他 Service
- [ ] Mapper 是空接口
- [ ] 无 Mapper XML 自定义 SQL(查询均在 Service 构建)
- [ ] 写操作有 @Transactional(rollbackFor = Exception.class)
- [ ] DTO 有 @Schema 注解
- [ ] Controller 有 @Tag / @Operation 注解
- [ ] CHANGELOG.md 已更新
## 完整代码参考
详细的 9 文件完整代码示例见 [reference.md](reference.md)。
## 常见错误
| 错误做法 | 正确做法 |
|----------|----------|
| 跳过 Migration,先拍脑袋写 Entity | 先定表结构,再映射 Entity 和 DTO |
| Controller 直接调 Service,省略 Facade | 保持 Controller → Facade → Service → Mapper |
| Mapper 里写自定义 SQL 或 XML | 让查询逻辑留在 Service,用 `LambdaQueryWrapper` 构建 |
| Create/Update/Detail 复用一个 DTO | 按创建、更新、卡片、详情拆分契约 |
| 没确认项目是否真的维护 `CHANGELOG.md` 就硬更新 | 先遵循项目现有发布文档约定,再决定是否更新 |
View on GitHub