场景
Spring项目中,使用swagger去自动生成接口文档.
当存在一个enum枚举时,会有很多VO和param的DTO去引用它.
如果修改这个enum,相关联的很多DTO和其他文件的注释description就需要关联修改,
否则就会造成前后端掌握的枚举值不一致的情况.
话不多说,直接上代码.
1. 实现.
package cn.king.core.configuration;
import cn.king.core.enums.BusinessEnum;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.schema.Annotations;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.swagger.schema.ApiModelProperties;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 对Swagger展示进行修改 ,支持将枚举变量的描述按照枚举类定义展示
* <p>
* 个性化处理 对应的枚举需实现cn.king.core.enums.BusinessEnum.class
*
* @Description
* @Author HHJ
* @Date 2020-12-28 11:55
* @see ApiModelProperty
*/
@Slf4j
//(纳入spring容器)
//@Primary
//@Component
public class SwaggerEnumDisplayConfig implements ModelPropertyBuilderPlugin {
/**
* 自定义 notes前缀CLASS
*/
public static final String CLASS = "";
/**
* 自定义 组装时的分隔符
*/
public static final String DELIMITER = ", ";
/**
* 自定义 BUSINESS_ENUM_CLASS
*/
public static final Class<BusinessEnum> BUSINESS_ENUM_CLASS = cn.king.core.enums.BusinessEnum.class;
/**
* 固定
*/
public static final String DESCRIPTION = "description";
@Override
public void apply(ModelPropertyContext context) {
// 获取当前字段的类型
final Class<?> fieldType = context.getBeanPropertyDefinition().get().getField().getRawType();
// 为枚举字段设置注释
Optional<ApiModelProperty> annotation = Optional.absent();
if (context.getAnnotatedElement().isPresent()) {
annotation = annotation.or(ApiModelProperties.findApiModePropertyAnnotation(context.getAnnotatedElement().get()));
}
if (context.getBeanPropertyDefinition().isPresent()) {
annotation = annotation.or(Annotations.findPropertyAnnotation(context.getBeanPropertyDefinition().get(), ApiModelProperty.class));
}
// 没有@ApiModelProperty 或者 notes 属性没有值,直接返回
String notes = (annotation.get()).notes();
if (!annotation.isPresent() || StringUtils.isBlank(notes)) {
return;
}
// 个性化处理
if (!handle(notes)) {
return;
}
// @ApiModelProperties中的notes指定的class类型
Class<?> clzz;
try {
clzz = Class.forName(notes);
} catch (ClassNotFoundException e) {
// 如果指定的类型无法转化,直接忽略
return;
}
//不是一个 BusinessEnum 类型的Enum,则返回
if (!isBusinessEnum(clzz)) {
return;
}
// 如果对应的class是一个BusinessEnum修饰的枚举类,获取其中的枚举值
Object[] subItemRecords = clzz.getEnumConstants();
// 循环枚举值
List<String> displayValues = Arrays.stream(subItemRecords)
.filter(Objects::nonNull)
.map(item -> getString(item))
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 组装最终的描述
String joinText = " (" + String.join(DELIMITER, displayValues) + ")";
try {
Field mField = ModelPropertyBuilder.class.getDeclaredField(DESCRIPTION);
mField.setAccessible(true);
joinText = mField.get(context.getBuilder()) + joinText;
} catch (Exception e) {
log.error(e.getMessage());
}
// 设置描述
context.getBuilder().description(joinText).type(context.getResolver().resolve(fieldType));
}
/**
* 个性化处理
*
* @param notes
* @return
*/
private boolean handle(String notes) {
// 自定义处理
if (!notes.toLowerCase().startsWith(CLASS)) {
return false;
}
return true;
}
/**
* 是否支持将对象自定义组装
*
* @param item
* @return
*/
private String getString(Object item) {
BusinessEnum value = (BusinessEnum) item;
return value.getCode() + ":" + value.getMessage();
}
/**
* 是否支持isBusinessEnum
* 可以自己定义
*
* @param clzz
* @return
*/
private boolean isBusinessEnum(Class<?> clzz) {
if (!Enum.class.isAssignableFrom(clzz)) {
return false;
}
Class<?>[] interfacesArray = clzz.getInterfaces();
for (Class<?> item : interfacesArray) {
//判断对应的class是一个BusinessEnum修饰的枚举类
if (item == BUSINESS_ENUM_CLASS) {
return true;
}
}
return false;
}
@Override
public boolean supports(DocumentationType documentationType) {
return true;
}
}
2.个性化依赖.对应的枚举需实现cn.king.core.enums.BusinessEnum.class
package cn.king.core.enums;
/**
* @description:
* @author: Admin
* @create: 2020-12-27 13:39
**/
public interface BusinessEnum {
/**
* getCode
*
* @return
*/
int getCode();
/**
* getMessage
*
* @return
*/
String getMessage();
}
3.应用.例如有业务枚举如下:
package cn.king.model.enums;
import cn.king.core.enums.BusinessEnum;
import lombok.Getter;
/**
* 短信类型枚举
*
* @Description
* @Author HHJ
* @Date 2020-12-28 17:18
*/
@Getter
public enum SmsFeaturesTypeEnum implements BusinessEnum {
USER_LOGIN(1, "1", "用户登录"),
USER_REGISTER(2, "2", "用户注册"),
ADMIN_LOGIN(3, "3", "管理员登录"),
ADMIN_REGISTER(4, "4", "管理员注册"),
USER_UPDATE_PWD(5, "5", "用户找回密码"),
USER_WECHAT_BIND(6, "6", "微信绑定"),
SMS_CODE_7(7, "7", "其他7"),
SMS_CODE_8(8, "8", "其他8"),
SMS_CODE_9(9, "9", "其他9"),
other(10, "10", "其他"),
;
private int code;
private String features;
private String message;
SmsFeaturesTypeEnum(int code, String features, String message) {
this.code = code;
this.features = features;
this.message = message;
}
public static SmsFeaturesTypeEnum getByFeatures(String features) {
SmsFeaturesTypeEnum[] values = values();
int length = values.length;
for (int i = 0; i < length; ++i) {
SmsFeaturesTypeEnum value = values[i];
if (value.getFeatures().equals(features)) {
return value;
}
}
return null;
}
public static SmsFeaturesTypeEnum getByCode(int code) {
SmsFeaturesTypeEnum[] values = values();
int length = values.length;
for (int i = 0; i < length; ++i) {
SmsFeaturesTypeEnum value = values[i];
if (value.getCode() == code) {
return value;
}
}
return null;
}
public static boolean isValid(int code) {
return getByCode(code) != null;
}
public static boolean isFeatures(String features) {
return getByFeatures(features) != null;
}
}
4.效果展示.Swagger接口文档 红色部分是根据枚举自定义添加的内容.
参考文档