自定义枚举注解
- 一、业务需求描述
- 1.问题描述
- 2.解决方案
- 二、创建一个描述注解
- 三、创建一个枚举注解
- 四、创建一个枚举
- 五、创建一个配置文件
- 六、场景实战
- 1.在 @RequestParam 前面使用
- 2.在非 Model 的实体类上使用
- 3.在 @RequestBody 对应的实体类中使用
- 七、效果展示
一、业务需求描述
1.问题描述
在字段使用 @ApiModelProperty 描述枚举字段含义,后续业务拓展,如果有新增加枚举值,每一个用到该枚举的字段的地方都要维护一遍,极其不方便,增加了维护成本。
2.解决方案
增加一个自定义枚举注解 @ApiPropertyEnum,来代替 @ApiModelProperty 。这样以后只需要维护对应的枚举类即可。
二、创建一个描述注解
主要功能:为了统一字段描述,用于映射枚举对应的字段,因为定义枚举字段因人而异
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SwaggerDisplayEnum {String code() default "code";String desc() default "desc";}
三、创建一个枚举注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiPropertyEnum {// 字段名称String name() default "";// 字段简要描述String value() default "";boolean hidden() default false;// 标识字段是否必填boolean required() default false;// 指定取值对应的枚举类Class<? extends Enum> enumClazz();
}
四、创建一个枚举
import com.xiaogang.annotation.SwaggerDisplayEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;/*** @Description: 模板业务类型枚举* @Author: hzg* @Date: 2024/9/5 13:52*/
@Getter
@AllArgsConstructor
@SwaggerDisplayEnum(code = "key", desc = "value")
public enum TemplateBusinessTypeEnum {ACQUISITION_TEMPLATE("1","收购订单模板"),SELL_TEMPLATE("2","销售订单模板"),LEASE_TEMPLATE("3","租赁单模板"),LEASE_ATR_TEMPLATE("4","租赁归还模板"),ALLOCATE_TEMPLATE("5","车辆调拨模板"),;String key;String value;}
五、创建一个配置文件
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.annotation.ApiPropertyEnum;
import com.ruoyi.common.annotation.SwaggerDisplayEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import springfox.documentation.builders.PropertySpecificationBuilder;
import springfox.documentation.builders.RequestParameterBuilder;
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.spi.service.ExpandedParameterBuilderPlugin;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.spi.service.contexts.ParameterExpansionContext;import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;/*** @Description: swagger 关于注解处理* @Auther: hzg* @Date: 2024/11/21 14:40* @version: 1.0*/
@Slf4j
@Configuration
public class SwaggerModelPropertyParameterConfig implements ModelPropertyBuilderPlugin, ParameterBuilderPlugin, ExpandedParameterBuilderPlugin {/*** 是否开启swagger*/@Value("${swagger.enabled}")private boolean enabled;@Overridepublic void apply(ModelPropertyContext context) {if (!enabled) {return;}// 为枚举字段设置注释descForModelEnumFields(context);}@Overridepublic void apply(ParameterContext context) {if (!enabled) {return;}// 为枚举字段设置注释descForMethodParameterEnumFields(context);}@Overridepublic void apply(ParameterExpansionContext context) {if (!enabled) {return;}// 为枚举字段设置注释descForMethodExpandedParameterEnumFields(context);}/*** 返回是否应根据给定的分隔符调用插件*/@Overridepublic boolean supports(DocumentationType documentationType) {return true;}/*** @Description: 针对 Model 里面字段枚举* @Author: hzg* @Date: 2024-11-21 16:57:23* @Params: [modelPropertyContext]* @Return: void*/private void descForModelEnumFields(ModelPropertyContext modelPropertyContext) {if (!modelPropertyContext.getBeanPropertyDefinition().isPresent()) {return;}Optional<ApiPropertyEnum> annotation = Optional.empty();// 找到 @ApiModelProperty 注解修饰的枚举类if (modelPropertyContext.getBeanPropertyDefinition().isPresent()) {annotation = Annotations.findPropertyAnnotation(modelPropertyContext.getBeanPropertyDefinition().get(), ApiPropertyEnum.class);}// 没有@ApiModelProperty 或者 notes 属性没有值,直接返回if (!annotation.isPresent()) {return;}// 生成需要拼接的取值含义描述内容String valueDesc = generateValueDesc(annotation.get());PropertySpecificationBuilder propertySpecificationBuilder = modelPropertyContext.getSpecificationBuilder();propertySpecificationBuilder.description(valueDesc);propertySpecificationBuilder.required(annotation.get().required());propertySpecificationBuilder.isHidden(annotation.get().hidden());}/*** @Description: 针对 非Model controller 方法请求参数【单个字段对象】枚举* @Author: hzg* @Date: 2024-11-21 16:58:35* @Params: [context]* @Return: void*/private void descForMethodParameterEnumFields(ParameterContext context) {Optional<ApiPropertyEnum> apiPropertyEnumOptional = context.resolvedMethodParameter().findAnnotation(ApiPropertyEnum.class);if (!apiPropertyEnumOptional.isPresent()) {return;}ApiPropertyEnum reference = apiPropertyEnumOptional.get();String desc = generateValueDesc(reference);if (StrUtil.isNotEmpty(reference.name())) {context.requestParameterBuilder().name(reference.name());}RequestParameterBuilder requestParameterBuilder = context.requestParameterBuilder();requestParameterBuilder.description(desc);requestParameterBuilder.required(reference.required());}/*** @Description: 针对 非Model controller 方法请求参数【实体对象】枚举* @Author: hzg* @Date: 2024-11-22 13:56:39* @Params: [context]* @Return: void*/private void descForMethodExpandedParameterEnumFields(ParameterExpansionContext context) {Optional<ApiPropertyEnum> apiPropertyEnumOptional = context.findAnnotation(ApiPropertyEnum.class);if (!apiPropertyEnumOptional.isPresent()) {return;}ApiPropertyEnum reference = apiPropertyEnumOptional.get();String desc = generateValueDesc(reference);RequestParameterBuilder requestParameterBuilder = context.getRequestParameterBuilder();requestParameterBuilder.description(desc);requestParameterBuilder.required(reference.required());context.getParameterBuilder().hidden(reference.hidden());}private String generateValueDesc(ApiPropertyEnum propertyReference) {Class<? extends Enum> rawPrimaryType = propertyReference.enumClazz();SwaggerDisplayEnum swaggerDisplayEnum = AnnotationUtils.findAnnotation(rawPrimaryType,SwaggerDisplayEnum.class);String enumFullDesc = Arrays.stream(rawPrimaryType.getEnumConstants()).filter(Objects::nonNull).map(enumConsts -> {assert swaggerDisplayEnum != null;Object fieldValue = ReflectUtil.getFieldValue(enumConsts, swaggerDisplayEnum.code());Object fieldDesc = ReflectUtil.getFieldValue(enumConsts, swaggerDisplayEnum.desc());return enumConsts + "(" + fieldValue + "," + fieldDesc + ")";}).collect(Collectors.joining(";"));return propertyReference.value() + "【" + enumFullDesc + "】";}}
六、场景实战
1.在 @RequestParam 前面使用
@ApiOperation(value = "查询8实体信息",tags = {"查询API"})
@GetMapping("/test1")
public TestDateTimeFormat test1(@ApiParam(value = "传ID", required = false, name = "id") String id,@ApiParam(value = "店铺状态") @RequestParam(name = "customerParticipantType", required = true) String customerParticipantType,@ApiPropertyEnum(value = "参数一",name = "param1",enumClazz = TemplateBusinessTypeEnum.class,required = true) @RequestParam(name = "param1", required = true) String param1){TestDateTimeFormat testDateTimeFormat = new TestDateTimeFormat();testDateTimeFormat.setName("xiaogang");testDateTimeFormat.setPhoneNumber("111111111111");testDateTimeFormat.setAddress("beijing");testDateTimeFormat.setBirthday(new Date());return testDateTimeFormat;
}
2.在非 Model 的实体类上使用
@ApiOperation(value = "查询7实体信息", tags = {"查询API"})
@GetMapping("/test1")
public TestJsonFormat test1(TestClass testDateTimeFormat) {TestJsonFormat testJsonFiled = new TestJsonFormat();testJsonFiled.setName("xiaogang");testJsonFiled.setPhoneNumber("111111111111");testJsonFiled.setAddress("beijing");testJsonFiled.setBirthday(new Date());return testJsonFiled;
}
@Data
@AllArgsConstructor
public class TestClass {@ApiPropertyEnum(value = "测试姓名", enumClazz = TemplateBusinessTypeEnum.class,required = true,hidden = false)private String name;private int age;private String address;}
3.在 @RequestBody 对应的实体类中使用
当然也可以直接在Response返回体实体对象中直接使用
@ApiOperation(value = "新增",tags = {"查询API"})
@PostMapping("/test2")
public void test2(@RequestBody TestDateTimeFormat testDateTimeFormat){log.info("实体类:{}",testDateTimeFormat);// 实体类:TestJsonFormat(name=小明, address=北京市, phoneNumber=123456789, birthday=Thu Oct 10 14:42:20 CST 2024)
}
@Data
@ApiModel(description = "日期实体")
public class TestDateTimeFormat {@ApiModelProperty(value = "姓名")private String name;@ApiModelProperty(value = "地址")private String address;@ApiPropertyEnum(value = "电话号码", enumClazz = TemplateBusinessTypeEnum.class,required = true)private String phoneNumber;@ApiModelProperty(value = "生日")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date birthday;}
七、效果展示