Skip to content

04:Service 模块:Controller、Service、DTO、Converter

1. 一次请求如何流动

以“新增客户”为例,正确的责任划分是:

text
HTTP JSON
  -> CustomerInfoController
  -> CustomerSaveForm(校验)
  -> CustomerConverter
  -> CustomerService
  -> CustomerServiceImpl(业务规则)
  -> DemoCustomerMapper
  -> MySQL

2. Controller 只做三件事

Controller 只应负责:

  1. 定义 URL、HTTP 方法和参数位置;
  2. @Valid 校验 Form;
  3. 调用 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());
    }
}

正式项目使用内部 BusinessExceptionResult.fail 等统一机制。你要先理解“错误不能直接抛成 500 堆栈给前端”的原则。

7. 本章练习

  1. 创建 CustomerServiceCustomerServiceImpl
  2. 将 Demo 的分页服务逐步合并或委托给 CustomerService,避免一个资源出现多个职责混乱的 Service。
  3. 在 Controller 中增加 /save 的方法签名,但暂时只返回固定值。
  4. /save 的 Form 加 @Valid,用空 customerName 调用接口,观察校验结果。

下一章学习数据层,之后再把真正的保存 SQL 接上。

Lucking