Skip to content

03:API 模块:Form、VO、枚举与接口契约

1. API 模块解决什么问题

正式项目把接口输入和输出放在 hc-crm-center-api

text
form/  : 前端发给后端的数据
vo/    : 后端返回给前端的数据
enums/ : 有固定含义的状态或选项
api/   : 其他服务调用 CRM 时使用的契约

这样其他服务只依赖 API 模块,不需要依赖整个 Service 实现。

Demo 现在是单模块。为了学习,你先在 Demo 中创建同名分层目录,不需要马上拆 Maven 模块:

text
com/hc/my/demo/
├── form/
├── vo/
├── dto/
├── converter/
└── enums/

2. Form:接口输入对象

新增客户不要直接用 DemoCustomer 接收请求。Entity 是数据库模型,暴露给前端会让接口与表结构强耦合。

创建 form/CustomerSaveForm.java

java
@Data
public class CustomerSaveForm {

    @NotBlank(message = "客户名称不能为空")
    private String customerName;

    @NotBlank(message = "联系人不能为空")
    private String contactPerson;

    @NotBlank(message = "联系电话不能为空")
    private String contactPhone;

    private String brandName;
    private String flowPhaseName;
    private String customerLevel;
}

@NotBlank 来自 Bean Validation。Controller 参数上加 @Valid 后,空字符串会在进入 Service 前被拦截。

正式参考:hc-crm-center-api/.../form/CustomerInfoAddForm.java。它除了基础字段,还校验海外客户、行业、地区和统一社会信用代码等业务规则。

3. EditForm:编辑输入对象

创建 CustomerEditForm,它需要 id:

java
@Data
public class CustomerEditForm extends CustomerSaveForm {

    @NotNull(message = "客户 id 不能为空")
    private Long id;
}

正式项目将新增和编辑分成 CustomerInfoAddFormCustomerInfoEditForm。这样可以表达“新增没有 id,编辑必须有 id”的差异。

4. VO:接口输出对象

详情接口应该返回 CustomerDetailVo,而不是 Entity:

java
@Data
public class CustomerDetailVo {
    private Long id;
    private String customerName;
    private String contactPerson;
    private String contactPhone;
    private String brandName;
    private String flowPhaseName;
    private String customerLevel;
    private LocalDateTime createdAt;
}

VO 的好处:

  • 可以隐藏数据库内部字段;
  • 可以增加“是否可编辑”“行业名称”等计算字段;
  • 数据库字段改变时,接口不一定要变;
  • 详情能组合多张表的数据。

正式参考:CustomerInfoVo。其中的 editableindustryNamecustomerBusinessInfo 都不是 customer_info 单表的原始字段。

5. DTO:Service 内部传输对象

初期单表 CRUD 可以让 Service 直接接 Form。等开始保存客户和联系人两张表时,新增 CustomerDto

text
Form -> DTO -> Entity

DTO 存在的理由是:Controller 输入格式不应该绑死 Service 的内部编排方式。正式 CustomerInfoDto 就同时携带客户基础信息、地区、跟进、发票和银行信息。

6. Converter:对象转换只在这里做

创建 CustomerConverter

java
public final class CustomerConverter {

    private CustomerConverter() {}

    public static DemoCustomer toEntity(CustomerSaveForm form) {
        DemoCustomer entity = new DemoCustomer();
        entity.setCustomerName(form.getCustomerName());
        entity.setContactPerson(form.getContactPerson());
        entity.setContactPhone(form.getContactPhone());
        entity.setBrandName(form.getBrandName());
        entity.setFlowPhaseName(form.getFlowPhaseName());
        entity.setCustomerLevel(form.getCustomerLevel());
        return entity;
    }
}

不要在 Controller 和 Service 的各个角落重复 setXxx。正式项目的 CustomerInfoConverter 负责 Form -> DTO、Form -> Entity、Entity -> VO。

7. 枚举:不要直接散落魔法值

如果合作阶段只有固定值,创建枚举:

java
public enum FollowPhaseEnum {
    VIP("VIP.已成交"),
    A("A.待签约"),
    B("B.高意向");

    private final String label;
}

数据库中通常存稳定编码,接口展示时再转为中文标签。不要随意修改已入库、已对外发送的枚举编码。

8. 本章练习与验收

  1. 在 Demo 创建 formvodtoconverterenums 包。
  2. CustomerSaveFormCustomerEditForm
  3. CustomerDetailVo
  4. 写一个 CustomerConverter.toEntity 方法。
  5. 暂时不接接口,先执行 mvn test 确认可以编译。

下一章再将它们接到 Controller 和 Service 中。

Lucking