skills/jianyun8023/my-skills/java-dto-converter

java-dto-converter

SKILL.md

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 转换字段(如存储路径 → 下载链接)
Weekly Installs
2
First Seen
Feb 10, 2026
Installed on
amp2
github-copilot2
codex2
kimi-cli2
gemini-cli2
cursor2