Appearance
04:Service 模块:Controller、Service、DTO、Converter
1. 一次请求如何流动
以“新增客户”为例,正确的责任划分是:
text
HTTP JSON
-> CustomerInfoController
-> CustomerSaveForm(校验)
-> CustomerConverter
-> CustomerService
-> CustomerServiceImpl(业务规则)
-> DemoCustomerMapper
-> MySQL2. Controller 只做三件事
Controller 只应负责:
- 定义 URL、HTTP 方法和参数位置;
- 用
@Valid校验 Form; - 调用 Service 并包装
Result。
示例:
java
@PostMapping("/save")
public Result<Boolean> save(@Valid @RequestBody CustomerSaveForm form) {
return Result.data(customerService.save(form));
}Controller 不应该出现 SQL、复杂循环、跨表保存、远程接口调用或大段 if/else。
正式参考:controller/web/client/CustomerInfoController.java。它接收 CustomerInfoAddForm,检查名称重复,然后调用 Service。
3. Service 接口先描述“能做什么”
创建接口:
java
public interface CustomerService {
boolean save(CustomerSaveForm form);
boolean edit(CustomerEditForm form);
CustomerDetailVo findById(Long id);
boolean deleteById(Long id);
boolean deleteByIds(List<Long> ids);
}接口不写实现细节,它是业务能力的目录。正式项目的 CustomerInfoService 也是同样的作用。
4. ServiceImpl 负责业务规则
创建 service/impl/CustomerServiceImpl:
java
@Service
@RequiredArgsConstructor
public class CustomerServiceImpl implements CustomerService {
private final DemoCustomerMapper demoCustomerMapper;
@Override
public boolean save(CustomerSaveForm form) {
// 1. 检查名称是否重复
// 2. Form 转 Entity
// 3. 设置创建时间
// 4. mapper.insert(entity)
return true;
}
}ServiceImpl 的规则示例:
- 客户名称不能重复;
- 更新前客户必须存在;
- 不能删除不存在的数据;
- 保存主表和子表时必须开启事务;
- 查询详情时可以组合其他表数据。
5. 为什么正式项目还有 DTO
一个 Form 面向前端,一个 Entity 面向数据库。复杂业务时两者都不适合直接在 Service 间传递。
例如正式客户新增 Form 中包含:地区、工商信息、跟进产品、跟进记录、发票、银行信息。Service 需要的是一个能统一编排这些字段的 CustomerInfoDto。
推荐在 Demo 的学习顺序:
text
单表 CRUD:Form -> Entity
两表保存:Form -> DTO -> 多个 Entity
多接口复用:坚持使用 Converter,避免在 Service 中到处复制字段6. Result 与异常
Demo 的 Result.data(...) 只表示成功。后续请补充:
java
public static <T> Result<T> fail(String msg) {
return new Result<>(400, msg, false, null);
}第一阶段可以在 Service 中抛 IllegalArgumentException;下一阶段再增加全局异常处理器:
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public Result<Void> handle(IllegalArgumentException e) {
return Result.fail(e.getMessage());
}
}正式项目使用内部 BusinessException、Result.fail 等统一机制。你要先理解“错误不能直接抛成 500 堆栈给前端”的原则。
7. 本章练习
- 创建
CustomerService和CustomerServiceImpl。 - 将 Demo 的分页服务逐步合并或委托给
CustomerService,避免一个资源出现多个职责混乱的 Service。 - 在 Controller 中增加
/save的方法签名,但暂时只返回固定值。 - 给
/save的 Form 加@Valid,用空customerName调用接口,观察校验结果。
下一章学习数据层,之后再把真正的保存 SQL 接上。
