1.定义VersionRequestMappingHandlerMapping
/**
* 客户端在传入Accept 参数添加当前请求的api版本,实现客户端版本平滑升级及向后的兼容
* <p>
* 使用:
* 配置CustomRequestMappingHandlerMapping替换默认的DefaultAnnotationHandlerMapping
* handlerAdapter也需要作调整org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
* <p>
* 在使用fastjson作为messageConverter时需要添加application/*+json;charset=UTF-8作为支持的mediaType
*
*/
public class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
VersionedResource typeAnnotation = AnnotationUtils.findAnnotation(handlerType, VersionedResource.class);
return createCondition(typeAnnotation);
}
@Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
VersionedResource methodAnnotation = AnnotationUtils.findAnnotation(method, VersionedResource.class);
return createCondition(methodAnnotation);
}
private RequestCondition<?> createCondition(VersionedResource versionMapping) {
if (versionMapping != null) {
return new VersionedResourceRequestCondition( versionMapping.from(), versionMapping.to());
}
return null;
}
}
2.定义VersionedResourceRequestCondition
public class VersionedResourceRequestCondition extends AbstractRequestCondition<VersionedResourceRequestCondition> {
private Logger logger = LoggerFactory.getLogger(VersionedResourceRequestCondition.class);
private final Set<VersionRange> versions;
private Pattern regexPattern = Pattern.compile("(\\d+\\.\\d+)");
public VersionedResourceRequestCondition(String from, String to) {
this(versionRange(from, to));
}
public VersionedResourceRequestCondition(Collection<VersionRange> versions) {
this.versions = Collections.unmodifiableSet(new HashSet<>(versions));
}
private static Set<VersionRange> versionRange(String from, String to) {
Set<VersionRange> versionRanges = new HashSet<>();
if (StringUtils.hasText(from)) {
String toVersion = (StringUtils.hasText(to) ? to : Version.MAX_VERSION);
VersionRange versionRange = new VersionRange(from, toVersion);
versionRanges.add(versionRange);
}
return versionRanges;
}
@Override
protected Collection<?> getContent() {
return versions;
}
@Override
protected String getToStringInfix() {
return " && ";
}
@Override
public VersionedResourceRequestCondition combine(VersionedResourceRequestCondition other) {
logger.debug("Combining:\n{}\n{}", this, other);
Set<VersionRange> newVersions = new LinkedHashSet<VersionRange>(this.versions);
newVersions.addAll(other.versions);
return new VersionedResourceRequestCondition(newVersions);
}
@Override
public VersionedResourceRequestCondition getMatchingCondition(HttpServletRequest request) {
String accept = request.getHeader("api_version");
if (StringUtils.hasText(accept)) {
Matcher matcher = regexPattern.matcher(accept);
if (matcher.matches()) {
String version = matcher.group(1);
logger.debug("Version={}", version);
AntPathMatcher antPathMatcher = new AntPathMatcher();
for (VersionRange versionRange : versions) {
if (versionRange.includes(version)) {
return this;
}
}
}
}
logger.warn("original url:[{}] with header api_version:[{}],handler versions:[{}],Didn't find matching version", request.getRequestURI(), accept, versions);
return null;
}
@Override
public int compareTo(VersionedResourceRequestCondition other, HttpServletRequest request) {
return 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("version={");
for (VersionRange range : versions) {
sb.append(range).append(",");
}
sb.append("}");
return sb.toString();
}
}
3.定义VersionRange
public class VersionRange {
private Version from;
private Version to;
public VersionRange(String from, String to) {
this.from = new Version(from);
this.to = new Version(to);
}
public boolean includes(String other) {
Version otherVersion = new Version(other);
if (from.compareTo(otherVersion) <= 0 && to.compareTo(otherVersion) >= 0) {
return true;
}
return false;
}
@Override
public String toString() {
return "range[" + from + "-" + to + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
VersionRange that = (VersionRange) o;
return from.equals(that.from) && to.equals(that.to);
}
@Override
public int hashCode() {
return Objects.hash(from, to);
}
}
4.定义Version
public class Version implements Comparable<Version> {
public static final String MAX_VERSION = "99.99";
private static final int VERSION_LENGTH = 2;
private final int major;
private final int minor;
public Version(String version) {
String[] tokens = version.split("\\.");
if (tokens.length != VERSION_LENGTH) {
throw new IllegalArgumentException("Invalid version " + version + ". The version must have major and minor number.");
}
major = Integer.parseInt(tokens[0]);
minor = Integer.parseInt(tokens[1]);
}
@Override
public int compareTo(Version o) {
if (this.major > o.major) {
return 1;
} else if (this.major < o.major) {
return -1;
} else if (this.minor > o.minor) {
return 1;
} else if (this.minor < o.minor) {
return -1;
} else {
return 0;
}
}
@Override
public String toString() {
return "v" + major + "." + minor;
}
}
5.定义版本号注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface VersionedResource {
String from() default "";
String to() default Version.MAX_VERSION;
}
6.在配置类中注入VersionRequestMappingHandlerMapping
/**
* 注入自定义的RequestMappingHandlerMapping
*/
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
VersionRequestMappingHandlerMapping vrmhm = new VersionRequestMappingHandlerMapping();
vrmhm.setOrder(0);
return vrmhm;
}
7.在controller中应用@VersionedResource注解
@VersionedResource(from = "1.0")
@PostMapping("/insert")
public ApiResponse<?> insert(@RequestBody @Valid WebRequestDTO request) {
// 处理业务逻辑
}