You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot API输入数据版本化实现方案咨询

基于Spring Boot的API输入版本化解决方案

针对你的需求,结合Accept请求头的版本化策略,可以通过版本化DTO+内部统一领域模型的方式实现新旧客户端的兼容,同时避免重复代码,还能方便后续版本废弃。以下是具体实现方案:

一、核心思路

  1. 用不同版本的DTO对应客户端的JSON结构,DTO仅负责与客户端的协议适配
  2. 所有版本的DTO都转换为内部统一的领域模型,业务逻辑和XML生成完全基于该模型,避免重复代码
  3. 通过Spring的内容协商机制,根据Accept头自动匹配对应版本的DTO处理逻辑
  4. 监控各版本请求量,安全识别并废弃无人使用的旧版本

二、具体实现步骤

1. 配置Spring内容协商(识别Accept头版本)

在application.yml中配置媒体类型与版本的映射,让Spring能识别Accept: application/vnd.yourcompany.v1+json和Accept: application/vnd.yourcompany.v2+json这类请求头:

spring:
  mvc:
    contentnegotiation:
      favor-path-extension: false
      favor-parameter: false
      media-types:
        v1: application/vnd.yourcompany.v1+json
        v2: application/vnd.yourcompany.v2+json

或者通过配置类自定义:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
            .ignoreAcceptHeader(false)
            .defaultContentType(MediaType.APPLICATION_JSON)
            .mediaType("v1", MediaType.valueOf("application/vnd.yourcompany.v1+json"))
            .mediaType("v2", MediaType.valueOf("application/vnd.yourcompany.v2+json"));
    }
}

2. 定义版本化DTO与统一领域模型

为避免重复代码,DTO仅保留与客户端交互的结构,核心字段复用内部模型:

内部统一领域模型(业务逻辑唯一依赖)

@Data
@Builder(toBuilder = true)
public class CustomerDomain {
    private PersonalDetails personalDetails;
    private ContractDetails contractDetails;
}

@Data
@Builder
public class PersonalDetails {
    private String dateOfBirth;
    private String occupation;
}

@Data
@Builder
public class ContractDetails {
    private String startDate;
}

V1版本DTO(对应旧JSON结构)

@Data
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@Builder(toBuilder = true)
public class CustomerV1DTO {
    @NotEmpty 
    private String dateOfBirth;

    @NotNull
    @Pattern(regexp="^[A-Z]{3}", message=INVALID_CODE)
    private String occupation;

    @Valid @NotNull private ContractDetails contractDetails;

    // 转换为内部领域模型
    public CustomerDomain toDomain() {
        return CustomerDomain.builder()
                .personalDetails(PersonalDetails.builder()
                        .dateOfBirth(this.dateOfBirth)
                        .occupation(this.occupation)
                        .build())
                .contractDetails(this.contractDetails)
                .build();
    }
}

V2版本DTO(对应新JSON结构)

@Data
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
@Builder(toBuilder = true)
public class CustomerV2DTO {
    @Valid @NotNull private PersonalDetails personalDetails;
    @Valid @NotNull private ContractDetails contractDetails;

    // 转换为内部领域模型
    public CustomerDomain toDomain() {
        return CustomerDomain.builder()
                .personalDetails(this.personalDetails)
                .contractDetails(this.contractDetails)
                .build();
    }
}

3. Controller中处理不同版本请求

通过consumes属性指定对应版本的媒体类型,自动绑定到对应DTO:

@RestController
@RequestMapping("/api/customers")
public class CustomerController {

    @PostMapping(consumes = "application/vnd.yourcompany.v1+json")
    public ResponseEntity<Void> handleV1Request(@Valid @RequestBody CustomerV1DTO customerV1) {
        processCustomer(customerV1.toDomain());
        return ResponseEntity.ok().build();
    }

    @PostMapping(consumes = "application/vnd.yourcompany.v2+json")
    public ResponseEntity<Void> handleV2Request(@Valid @RequestBody CustomerV2DTO customerV2) {
        processCustomer(customerV2.toDomain());
        return ResponseEntity.ok().build();
    }

    // 统一业务逻辑:转换为XML并发送
    private void processCustomer(CustomerDomain domain) {
        // 此处编写生成XML、调用外部应用的逻辑
    }
}

4. 版本废弃与监控

  • 标记废弃:在旧版本Controller方法上添加@Deprecated注解,同时在响应头中告知客户端:
    @PostMapping(consumes = "application/vnd.yourcompany.v1+json")
    @Deprecated
    public ResponseEntity<Void> handleV1Request(@Valid @RequestBody CustomerV1DTO customerV1) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Deprecated", "true");
        headers.add("Sunset", "2024-12-31T00:00:00Z"); // 废弃截止日期
        processCustomer(customerV1.toDomain());
        return ResponseEntity.ok().headers(headers).build();
    }
    
  • 监控与删除:通过Spring Boot Actuator、Prometheus等工具统计各版本请求量,当某版本持续无调用时,即可安全删除对应的DTO和Controller方法。

三、替代方案:单模型兼容(小版本变更适用)

如果只是小结构变更,不想定义多DTO,可以用Jackson注解在单个模型中兼容新旧结构:

@Data
@Builder(toBuilder = true)
public class Customer {
    @Valid @NotNull 
    private PersonalDetails personalDetails;

    // 旧版本字段仅用于反序列化V1请求
    @JsonProperty(value = "dateOfBirth", access = JsonProperty.Access.WRITE_ONLY)
    private String dateOfBirth;

    @JsonProperty(value = "occupation", access = JsonProperty.Access.WRITE_ONLY)
    private String occupation;

    @Valid @NotNull private ContractDetails contractDetails;

    // 自动将旧字段映射到personalDetails
    @JsonSetter
    public void setDateOfBirth(String dateOfBirth) {
        if (personalDetails == null) personalDetails = new PersonalDetails();
        personalDetails.setDateOfBirth(dateOfBirth);
    }

    @JsonSetter
    public void setOccupation(String occupation) {
        if (personalDetails == null) personalDetails = new PersonalDetails();
        personalDetails.setOccupation(occupation);
    }
}

这种方式适合小范围结构调整,大版本变更还是推荐多DTO+统一领域模型的方案,更易维护和扩展。

内容的提问来源于stack exchange,提问作者runnerpaul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 06:30:47