Appearance
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;
}正式项目将新增和编辑分成 CustomerInfoAddForm、CustomerInfoEditForm。这样可以表达“新增没有 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。其中的 editable、industryName、customerBusinessInfo 都不是 customer_info 单表的原始字段。
5. DTO:Service 内部传输对象
初期单表 CRUD 可以让 Service 直接接 Form。等开始保存客户和联系人两张表时,新增 CustomerDto:
text
Form -> DTO -> EntityDTO 存在的理由是: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. 本章练习与验收
- 在 Demo 创建
form、vo、dto、converter、enums包。 - 写
CustomerSaveForm和CustomerEditForm。 - 写
CustomerDetailVo。 - 写一个
CustomerConverter.toEntity方法。 - 暂时不接接口,先执行
mvn test确认可以编译。
下一章再将它们接到 Controller 和 Service 中。
