java-dto-converter
DTO 与 Converter
为分层架构项目创建规范的 DTO(数据传输对象)和 MapStruct Converter(对象转换器)。
DTO 命名体系
请求 DTO
| 用途 | 命名 | 示例 |
|---|---|---|
| 创建 | {Resource}CreateReq |
PolicyCreateReq |
| 更新 | {Resource}UpdateReq |
PolicyUpdateReq |
| 分页查询 | {Resource}PageReq |
PolicyPageReq |
| 批量操作 | {Resource}BatchReq |
TaskBatchReq |
响应 DTO
| 用途 | 命名 | 示例 |
|---|---|---|
| 列表卡片 | {Resource}Card |
PolicyCard |
| 详情 | {Resource}Detail |
PolicyDetail |
| 通用响应 | {Resource}Resp |
VideoSourceResp |
| 批量结果 | {Resource}BatchResp |
TaskBatchResp |
通用 DTO
| 类 | 用途 |
|---|---|
IdResp |
创建操作返回 ID |
PageReq |
分页请求基类 (page, size) |
PageResult<T> |
偏移分页结果 (records, total, page, size) |
CursorPageResult<T> |
游标分页结果 (records, nextCursor, hasMore, count) |
@Data
@Schema(description = "分页请求基类")
public class PageReq {
@Schema(description = "页码", example = "1")
@Min(value = 1, message = "页码不能小于1")
private Integer page = 1;
@Schema(description = "每页大小", example = "20")
@Range(min = 1, max = 100, message = "每页大小需在1-100之间")
private Integer size = 20;
}
@Data
@Schema(description = "创建操作返回ID")
public class IdResp {
@Schema(description = "资源ID")
private Long id;
public IdResp(Long id) { this.id = id; }
}
包组织
DTO 按业务模块分子目录:
dto/
├── policy/
│ ├── PolicyCreateReq.java
│ ├── PolicyUpdateReq.java
│ ├── PolicyCard.java
│ └── PolicyDetail.java
├── task/
│ ├── AnalysisTaskCreateReq.java
│ └── ...
├── ApiResponse.java
├── IdResp.java
├── PageReq.java
├── PageResult.java
└── CursorPageResult.java
DTO 注解规范
请求 DTO 模板
@Data
@Schema(description = "策略创建请求")
public class PolicyCreateReq {
@Schema(description = "策略名称", example = "火灾预警策略")
@NotBlank(message = "策略名称不能为空")
private String name;
@Schema(description = "策略描述")
private String description;
@Schema(description = "告警等级")
@NotNull(message = "告警等级不能为空")
private AlertLevelEnum alertLevel;
@Schema(description = "关联设备ID列表")
@NotEmpty(message = "设备ID列表不能为空")
private List<String> deviceIds;
}
更新 DTO 模板
更新 DTO 与创建 DTO 的区别:字段通常全部可选(仅传入需要修改的字段),不加 @NotBlank 等必填校验。
@Data
@Schema(description = "策略更新请求")
public class PolicyUpdateReq {
@Schema(description = "策略名称", example = "火灾预警策略V2")
private String name;
@Schema(description = "策略描述")
private String description;
@Schema(description = "告警等级")
private AlertLevelEnum alertLevel;
@Schema(description = "关联设备ID列表")
private List<String> deviceIds;
}
设计策略: 若业务要求全量更新(每次提交完整数据),则 UpdateReq 可加必填校验,与 CreateReq 类似。
响应 DTO 模板
@Data
@Schema(description = "策略卡片")
public class PolicyCard {
@Schema(description = "策略ID")
private Long id;
@Schema(description = "策略名称")
private String name;
@Schema(description = "是否启用")
private Boolean enabled;
@Schema(description = "告警等级")
private AlertLevelEnum alertLevel;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}
详情继承卡片
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Schema(description = "策略详情")
public class PolicyDetail extends PolicyCard {
@Schema(description = "策略描述")
private String description;
@Schema(description = "时间计划列表")
private List<TimePlanResp> timePlans;
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}
继承场景必须:
@EqualsAndHashCode(callSuper = true)+@ToString(callSuper = true),否则父类字段不参与 equals/toString。
分页请求继承基类
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "策略分页查询请求")
public class PolicyPageReq extends PageReq {
@Schema(description = "策略名称(模糊查询)")
private String name;
@Schema(description = "告警等级列表(多选)")
private List<AlertLevelEnum> alertLevels;
@Schema(description = "开始时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;
}
常用注解速查
| 注解 | 用途 | 示例 |
|---|---|---|
@Schema(description, example) |
OpenAPI 字段说明 | @Schema(description = "用户名", example = "张三") |
@NotBlank |
字符串非空 | 必填 String 字段 |
@NotNull |
非 null | 必填枚举/对象字段 |
@NotEmpty |
集合非空 | 必填 List 字段 |
@JsonFormat(pattern) |
JSON 日期格式 | "yyyy-MM-dd HH:mm:ss" |
@JsonProperty |
JSON 字段名 | @JsonProperty("sourceId") |
@DateTimeFormat(pattern) |
查询参数日期解析 | GET 请求的日期参数 |
枚举序列化
DTO 中使用枚举类型时,需明确 JSON 序列化/反序列化策略:
@Getter
@AllArgsConstructor
public enum AlertLevelEnum {
HIGH("high", "高"),
MEDIUM("medium", "中"),
LOW("low", "低");
@JsonValue // 序列化时输出 value 值(如 "high")
private final String value;
private final String label;
@JsonCreator // 反序列化时按 value 匹配
public static AlertLevelEnum fromValue(String value) {
for (AlertLevelEnum e : values()) {
if (e.value.equals(value)) return e;
}
throw new IllegalArgumentException("未知告警等级: " + value);
}
}
| 注解 | 用途 |
|---|---|
@JsonValue |
控制枚举序列化输出(推荐使用业务值而非 name/ordinal) |
@JsonCreator |
控制枚举反序列化匹配逻辑 |
MapStruct Converter
统一配置(推荐)
所有 Converter 共享的配置,自动忽略未映射字段,无需逐个 @Mapping(ignore=true):
@MapperConfig(
componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE,
unmappedSourcePolicy = ReportingPolicy.IGNORE
)
public interface ConverterConfig {
}
推荐策略: 优先使用
config = ConverterConfig.class(简洁、统一)。仅在需要显式控制映射关系(如字段名不同、常量赋值)时才使用@Mapping。
Converter 接口模板
@Mapper(config = ConverterConfig.class)
public interface PolicyConverter {
// Entity → DTO
PolicyCard toCard(AlertPolicy entity);
PolicyDetail toDetail(AlertPolicy entity);
List<PolicyCard> toCards(List<AlertPolicy> entities);
// 创建: DTO → Entity
AlertPolicy toEntity(PolicyCreateReq req);
// 更新: DTO 合并到已有 Entity(仅覆盖非 null 字段)
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateEntity(PolicyUpdateReq req, @MappingTarget AlertPolicy entity);
}
不使用 ConverterConfig 的写法: 将
@Mapper(config = ConverterConfig.class)替换为@Mapper(componentModel = "spring"),并手动添加@Mapping(target = "id", ignore = true)等忽略注解。
方法命名规范
| 方法 | 用途 |
|---|---|
toEntity(Req) |
请求 DTO → 新 Entity(创建) |
updateEntity(Req, @MappingTarget Entity) |
请求 DTO 合并到已有 Entity(更新) |
toCard(Entity) |
Entity → 列表卡片 DTO |
toDetail(Entity) |
Entity → 详情 DTO |
toResp(Entity) |
Entity → 通用响应 DTO |
toCards(List) |
批量转换 |
字段映射
// 忽略字段
@Mapping(target = "id", ignore = true)
// 字段名不同
@Mapping(source = "sort", target = "sortOrder")
// 常量值
@Mapping(target = "status", constant = "DISABLED")
// 表达式
@Mapping(target = "syncTime", expression = "java(java.time.LocalDateTime.now())")
// 嵌套属性
@Mapping(target = "typeName", source = "entity.modelType.label")
后处理 (@AfterMapping)
用于 MapStruct 自动映射后的补充逻辑(组装显示名称、计算派生字段等):
@AfterMapping
default void enrichCard(AlertPolicy entity, @MappingTarget PolicyCard card) {
// 组装显示名称
card.setDisplayName(entity.getName() + " (" + entity.getAlertLevel().getLabel() + ")");
}
@AfterMapping
default void setDefaultValues(@MappingTarget AlertPolicy entity) {
if (entity.getEnabled() == null) entity.setEnabled(false);
if (entity.getStatus() == null) entity.setStatus(PolicyStatusEnum.DISABLED);
}
null 值处理: 简单的 null → 默认值场景优先使用
@BeanMapping(nullValuePropertyMappingStrategy)或ConverterConfig级别配置,@AfterMapping保留给需要自定义逻辑的场景。
自定义转换 (default 方法)
用于枚举、时间戳等需要逻辑的转换:
@Mapper(config = ConverterConfig.class)
public interface AlertConverter {
AlertCard toCard(AlertRecord entity);
// 自定义时间戳转换(MapStruct 自动调用匹配的类型转换方法)
default LocalDateTime timestampToLocalDateTime(long timestamp) {
if (timestamp == 0) return null;
return LocalDateTime.ofInstant(
Instant.ofEpochSecond(timestamp),
ZoneId.systemDefault()
);
}
// 自定义枚举转换
default OnlineStatus convertOnlineStatus(String videoUrlStatus) {
return OnlineStatus.fromVideoUrlStatus(videoUrlStatus);
}
}
组合 Converter (uses)
当 Entity 含有嵌套对象需要转换时,使用 uses 引入其他 Converter:
@Mapper(config = ConverterConfig.class, uses = {TimePlanConverter.class})
public interface PolicyConverter {
// MapStruct 自动调用 TimePlanConverter 转换嵌套的 TimePlan → TimePlanResp
PolicyDetail toDetail(AlertPolicy entity);
}
@Mapper(config = ConverterConfig.class)
public interface TimePlanConverter {
TimePlanResp toResp(TimePlan entity);
List<TimePlanResp> toResps(List<TimePlan> entities);
}
必须忽略的字段
当 DTO → Entity 转换时,以下字段必须 ignore(由框架自动填充):
| 字段 | 原因 |
|---|---|
id |
数据库自增 |
deleted |
默认值 0 |
createTime / updateTime |
MetaObjectHandler 自动填充 |
createUser / updateUser |
MetaObjectHandler 自动填充 |
versionNum |
默认值 1 |
当 Entity → DTO 转换时,以下字段由 Facade 层填充,Converter 应 ignore:
- 关联数据字段(如
agentNames,regionPath,deviceGroups) - 计算字段(如
sourceCount,taskCount) - URL 转换字段(如存储路径 → 下载链接)