为了生成接口文档我们很多人会使用到swagger组件,在我们使用swagger注解进行属性配置的时候,经常会有如下一部分代码;如果这些属性取自字典或枚举的话就会变得复杂臃肿且不易维护:
@Data
@ApiModel("Xxx desc")
public class Xxx {
@ApiModelProperty(value = "课程状态;取自字典 course_state")
private Integer state;
@ApiModelProperty(value = "排序字段:startTime 课程开始时间、endTime 课程结束时间、orderIncomeNum 成单数、orderRefundNum 退单数、orderIncomeAmount 成单金额、orderRefundAmount 退单金额、transferNum 转化人数、refundNum 退款人数")
private String orderBy;
@ApiModelProperty(value = "排序方向:asc 升序、desc 降序")
private String direct;
...
}
我们之前已经建设了完善的字典组件支持,结合拓展swagger组件,自动适配枚举或字典类型编码,可以实现自动生成接口文档,这样只要修改了枚举定义或字典配置,文档会自动更新。效果如下:
定义一个字典属性枚举:
package com.xxx.xxx.api.enums;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author zhaochao
* @date 2021/6/11 14:19
* @desc 字典枚举属性,用于生成接口文档
*/
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DictionaryProperty {
/**
* 属性名
*/
String value();
/**
* 是否必须存在
*/
boolean required() default false;
/**
* 排序序号
*/
int position() default 0;
/**
* 是否隐藏
*/
boolean hidden() default false;
/**
* 字典枚举类
*/
Class<? extends DictionaryEnum> enumClass() default DictionaryEnum.class;
/**
* 字典类型编码
*/
String dictTypeCode() default "";
}
其中dictTypeCode是字典类型编码,属于某一类字典的唯一标识编码,enumClass是字典枚举类接口DictionaryEnum的实现;DictionaryEnum定义如下:
package com.xxx.xxx.api.enums;
/**
* @author zhaochao
* @date 2021/3/31 17:53
* @desc 字典枚举接口 实现以支持生成字典
*/
public interface DictionaryEnum {
/**
* 获取字典编码
*
* @return 返回object 使用的时候转换为string
*/
Object getCode();
/**
* 获取编码对应描述
* @return String
*/
String getValue();
/**
* 序号
* @return int
*/
int getOrder();
}
编写插件适配:
package com.xxx.xxx.plugin;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.nacos.common.utils.Objects;
import com.xxx.xxx.api.enums.DictionaryProperty;
import com.xxx.xxx.api.vo.DictionaryDataResVo;
import com.xxx.xxx.api.vo.DictionaryTypeResVo;
import com.xxx.xxx.api.enums.DictionaryEnum;
import com.xxx.xxx.service.DictionaryQueryService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.Annotations;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
/**
* @author 赵超
* @date 2021-6-11
* @desc 针对字典组件适配swagger的插件
*/
@Slf4j
@ConditionalOnProperty(prefix = "swagger", value = "enabled", havingValue = "true")
@Component
public class DictionaryPropertyPlugin implements ModelPropertyBuilderPlugin {
@Autowired
private DictionaryQueryService dictionaryQueryService;
@Override
public void apply(ModelPropertyContext context) {
Optional<DictionaryProperty> annotation = Optional.empty();
if (context.getAnnotatedElement().isPresent()) {
annotation = findDictionaryPropertyAnnotation(context.getAnnotatedElement().get());
}
if (context.getBeanPropertyDefinition().isPresent()) {
annotation = Annotations.findPropertyAnnotation(context.getBeanPropertyDefinition().get(), DictionaryProperty.class);
}
if (!annotation.isPresent()) {
return;
}
DictionaryProperty d = annotation.get();
context.getSpecificationBuilder()
.isHidden(d.hidden())
.required(d.required())
.position(d.position())
.description(getDesc(d));
}
private String getDesc(DictionaryProperty d) {
String dictTypeCode = d.dictTypeCode();
if (StringUtils.isNotBlank(dictTypeCode)) {
return d.value() + getDescByDictTypeCode(dictTypeCode);
}
return d.value() + getDescByDictionaryEnum(d.enumClass());
}
private String getDescByDictionaryEnum(Class<? extends DictionaryEnum> clazz) {
if (Objects.isNull(clazz) || Objects.equals(DictionaryEnum.class, clazz)) {
return "";
}
DictionaryEnum[] enums = clazz.getEnumConstants();
if (enums == null || enums.length == 0) {
return "";
}
StringBuilder sb = new StringBuilder(":");
DictionaryEnum e;
for (int i = 0; i < enums.length; i++) {
e = enums[i];
sb.append(e.getCode()).append(" ").append(e.getValue());
if (i != enums.length - 1) {
sb.append("、");
}
}
return sb.toString();
}
private String getDescByDictTypeCode(String dictTypeCode) {
try {
StringBuilder sb = new StringBuilder("(字典类型编码:" + dictTypeCode + ")");
DictionaryTypeResVo dictTypeRes = dictionaryQueryService.getByTypeCode(dictTypeCode);
if (Objects.isNull(dictTypeRes) || CollectionUtil.isEmpty(dictTypeRes.getData())) {
return sb.toString();
}
sb.append(":");
DictionaryDataResVo e;
List<DictionaryDataResVo> data = dictTypeRes.getData();
for (int i = 0; i < data.size(); i++) {
e = data.get(i);
sb.append(e.getCode()).append(" ").append(e.getValue());
if (i != data.size() - 1) {
sb.append("、");
}
}
return sb.toString();
} catch (Exception e) {
log.error("运行字典组件swagger插件出错");
}
return "";
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
private Optional<DictionaryProperty> findDictionaryPropertyAnnotation(AnnotatedElement annotated) {
Optional<DictionaryProperty> annotation = Optional.empty();
if (annotated instanceof Method) {
// If the annotated element is a method we can use this information to check superclasses as well
annotation = Optional.ofNullable(AnnotationUtils.findAnnotation(((Method) annotated), DictionaryProperty.class));
}
return annotation.map(Optional::of).orElse(Optional.ofNullable(AnnotationUtils.getAnnotation(annotated,
DictionaryProperty.class)));
}
}
修改原接口出入参对象的注解:
@Data
@ApiModel("Xxx desc")
public class Xxx {
@DictionaryProperty(value = "课程状态", dictTypeCode = "course_state")
private Integer state;
@DictionaryProperty(value = "排序字段", enumClass = OrderColumn.class)
private String orderBy;
@DictionaryProperty(value = "排序方向", enumClass = SortDirectionEnum.class)
private String direct;
...
@AllArgsConstructor
@Getter
public enum OrderColumn implements DictionaryEnum {
// 课程开始时间
START_TIME("startTime", "课程开始时间"),
// 课程结束时间
END_TIME("endTime", "课程结束时间"),
// 成单数
ORDER_INCOME_NUM("orderIncomeNum", "成单数"),
// 退单数
ORDER_REFUND_NUM("orderRefundNum", "退单数"),
// 成单金额
ORDER_INCOME_AMOUNT("orderIncomeAmount", "成单金额"),
// 退单金额
ORDER_REFUND_AMOUNT("orderRefundAmount", "退单金额"),
// 转化人数
TRANSFER_NUM("transferNum", "转化人数"),
// 转化人数
REFUND_NUM("refundNum", "退款人数"),
;
private final String column;
private final String desc;
public static OrderColumn parse(String orderBy) {
if (StringUtils.isBlank(orderBy)) {
return null;
}
for (OrderColumn value : values()) {
if (Objects.equals(orderBy, value.getColumn())) {
return value;
}
}
return null;
}
@Override
public Object getCode() {
return column;
}
@Override
public String getValue() {
return desc;
}
@Override
public int getOrder() {
return ordinal();
}
}
}
然后启动项目,打开swagger即可查看效果!