【无需打开直接搜索微信;-】 操作使用教程: ...
2025-09-08 0
“又双叒叕改枚举了?!”
前端同学看着后端刚提交的,稍稍增加了一个新状态值的接口文档,默默叹了口气,熟练的打开了自己维护的 constants.ts 文件。后端同学在心里嘀咕“不就加个状态吗?前端改个下拉框不就行啦?至于每次都要我同步吗?”
这熟悉的一幕,几乎每天都会上演。为了减少前后端同学的扯皮,有没有一种方式,能让一份定义,两端通用?答案是:有!而且比你想象的更优雅、更强大。 今天就来揭秘我在项目中使用的这种让前后端都拍案叫绝的“通用枚举”,彻底让前后端同学不在扯皮?
共提供两种枚举的返回类型,比如有枚举类UserType如下
@Getter@AllArgsConstructorpublic enum UserType{ NORMAL(1, "普通用户", ""), ADMIN(2, "管理员", ""), ; private final Integer value; private final String name; private final String description;}
第一种返回枚举名称:ADMIN
第二种返回枚举JSON:{”value”:2, ”name”:”管理员”, ”description”:””}
定义一个通用枚举接口 IBaseEnum,所有的枚举类都必须实现 IBaseEnum,提供一些通用的方法
/** * @description: 通用枚举,所有枚举都必须实现该接口 * @author: Walker * @date: 2025-05-26 01:09:09 * @version: 1.0.0 */public interface IBaseEnum<T> { /** * 字典值 * * @return value */ T getValue(); /** * 字典名称 * * @return name */ String getName(); /** * 字典描述 * * @return description */ String getDescription(); /** * 通过value获取枚举 * * @param value value * @param clazz clazz * @param <E> E * @return result */ static <E extends Enum<E> & IBaseEnumJson<T>, T> E getEnum(T value, Class<E> clazz) { Objects.requireNonNull(value); return getEnums(clazz).stream() .filter(e -> ObjectUtil.equal(e.getValue(), value)) .findFirst() .orElse(null); } /** * 通过value获取name * * @param value value * @param clazz clazz * @param <E> E * @return name */ static <E extends Enum<E> & IBaseEnumJson<T>, T> String getName(T value, Class<E> clazz) { Objects.requireNonNull(value); return getEnums(clazz).stream() .filter(e -> ObjectUtil.equal(e.getValue(), value)) .map(IBaseEnumJson::getName) .findFirst() .orElse(null); } /** * 通过name获取value * * @param name name * @param clazz clazz * @param <E> E * @return value */ static <E extends Enum<E> & IBaseEnumJson<T>, T> T getCode(String name, Class<E> clazz) { Objects.requireNonNull(name); return getEnums(clazz).stream() .filter(e -> ObjectUtil.equal(e.getName(), name)) .map(IBaseEnumJson::getValue) .findFirst() .orElse(null); } /** * 获取所有枚举 * * @param clazz clazz * @param <E> E * @return enums */ static <E extends Enum<E> & IBaseEnumJson<T>, T> EnumSet<E> getEnums(Class<E> clazz) { return EnumSet.allOf(clazz); }}
为枚举中的需要序列化的字段添加 *@JsonValue* 即可实现简单枚举。我们通常会在枚举描述的字段上添加 *@JsonValue* 注解,但是前端传参就只能传递这个枚举描述字段的值(通常是中文),就不能传枚举名称了。为此需要使用 *@JsonCreator* 注解来自定义处理的逻辑。
/** * 为什么不直接使用@JsonCreator: * 1、在接口中使用该注解添加到from方法上是不生效的 * 2、如果将接口换成抽象类是可以的,但是枚举无法继承抽象类 * 3、单独在每个实现IBaseEnumSimple接口的枚举中添加的话,代价太大了 * // @JsonCreator * // public static UserType from(Object object) { * // return IBaseEnumSimple.from(object, UserType.class); * // } * * @description: 通用枚举,所有枚举都必须实现该接口 * @author: Walker * @date: 2025-05-25 20:37:37 * @version: 1.0.0 */@JsonDeserialize(using = DictSimpleDeserializer.class)public interface IBaseEnumSimple<T> extends IBaseEnum<T> { // 可以不在此处做限制,开放给具体的继承类使用 @JsonValue String getName(); /** * 直接在此方法添加@JsonCreator注解是行不通的,除非在实现IBaseEnumSimple的枚举类中单独使用 * * @param object object * @param enumClass enumClass * @param <E> E * @return Enum */ static <E extends Enum<E> & IBaseEnumSimple<?>> E from(Object object, Class<E> enumClass) { if (object == null || enumClass == null) { return null; } return Arrays.stream(enumClass.getEnumConstants()) .filter(e -> e.matches(object)).findFirst() .orElseThrow(() -> new IllegalArgumentException("无效枚举值: " + object)); } /** * 枚举匹配逻辑,子类可覆盖 * * @param object object * @return boolean */ default boolean matches(Object object) { // 按照value,name或枚举名称匹配 // return String.valueOf(this.getValue()).equals(object.toString()) // || this.getName().equals(object.toString()) // || this.toString().equals(object.toString()); // 通过枚举名称匹配 return this.toString().equals(object.toString()); }}
/** * @description: 简单枚举反序列化器 * @author: Walker * @date: 2025-05-26 01:40:40 * @version: 1.0.0 */public class DictSimpleDeserializer<E extends Enum<E> & IBaseEnumSimple<?>> extends JsonDeserializer<E> implements ContextualDeserializer { private Class<E> enumClass; @Override @SuppressWarnings("unchecked") public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) { this.enumClass = (Class<E>) context.getContextualType().getRawClass(); return this; } @Override public E deserialize(JsonParser p, DeserializationContext context) throws IOException { String value = p.getValueAsString(); return IBaseEnumSimple.from(value, enumClass); }}
枚举接口添加 @JsonFormat(shape = JsonFormat.Shape.*OBJECT*) 即可返回枚举对应的 JSON 字符串
/** * @description: 通用枚举,所有枚举都必须实现该接口 * @author: Walker * @date: 2025-01-11 00:55:55 * @version: 1.0.0 */@JsonFormat(shape = JsonFormat.Shape.OBJECT)public interface IBaseEnumJson<T> extends IBaseEnum<T> {}
简单枚举使用示例,前端可直接传递枚举名称如:NORMAL、ADMIN,也可以通过修改 IBaseEnumSimple 中的 matches 方法修改匹配逻辑
@Getter@AllArgsConstructorpublic enum UserType implements IBaseEnumSimple<Integer> { NORMAL(1, "普通用户", ""), ADMIN(2, "管理员", ""), ; @EnumValue private final Integer value; private final String name; private final String description;}
JSON枚举使用示例,前端可以直接传递枚举名称如:NORMAL、ADMIN
@Getter@AllArgsConstructorpublic enum UserType implements IBaseEnumJson<Integer> { NORMAL(1, "普通用户", ""), ADMIN(2, "管理员", ""), ; @EnumValue private final Integer value; private final String name; private final String description;}
数据字典实体、数据字典数据实体
public class SysCodeMaster extends BaseEntity { private String id; private String name; private String code; private String description; private Integer status;}
public class SysCodeItem extends BaseEntity { private String id; private String codeId; private String name; private String value; private String description; private Double displayOrder; private Integer status;}
数据字典注解、数据字典数据注解
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface CodeMaster { // 数据字典名 String name(); // 字典编码 String code(); // 数据字典项 CodeItem[] values() default {}; // 数据字典项对应的枚举类 Class<? extends IBaseEnum<?>> enumClass() default UnspecifiedEnum.class; // 枚举表述 String description() default "";}
@Target(ElementType.ANNOTATION_TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface CodeItem { // 名称 String name(); // 值 String value(); // 描述 String description() default "";}
数据字典自动注册Runner
@ConditionalOnProperty(name = "top.walker.dict.register.enabled", havingValue = "true", matchIfMissing = false)@Component@RequiredArgsConstructorpublic class CodeMasterRunner implements ApplicationRunner { private final Logger logger = LoggerFactory.getLogger(CodeMasterRunner.class); private final ISysCodeMasterService codeMasterService; @Override public void run(ApplicationArguments args) throws Exception { logger.info("code master register start......"); // 创建扫描器 ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); // 添加过滤器,查找实现了IEntity接口的类 scanner.addIncludeFilter(new AssignableTypeFilter(IEntity.class)); // 扫描BASE_PACKAGES中所有实现IEntity的类 Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(ApplicationConstants.BASE_PACKAGES); for (BeanDefinition beanDefinition : beanDefinitions) { Class<?> clazz = Class.forName(beanDefinition.getBeanClassName()); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(CodeMaster.class)) { CodeMaster codeMaster = field.getAnnotation(CodeMaster.class); SysCodeMaster sysCodeMaster = SysCodeMaster.builder() .name(codeMaster.name()) .code(codeMaster.code()) .description(codeMaster.description()) .build(); logger.info("dic : {}", JSONUtil.toJsonStr(sysCodeMaster)); Map<String, SysCodeItem> codeItemMap = new HashMap<>(); // 解析enumClass Class<? extends IBaseEnum<?>> enumClass = codeMaster.enumClass(); if (!UnspecifiedEnum.class.equals(enumClass) && enumClass.isEnum()) { IBaseEnum<?>[] enumConstants = enumClass.getEnumConstants(); for (IBaseEnum<?> enumConstant : enumConstants) { SysCodeItem sysCodeItem = SysCodeItem.builder() .codeId(sysCodeMaster.getId()) .name(enumConstant.getName()) .value(String.valueOf(enumConstant.getValue())) .description(enumConstant.getDescription()) .build(); codeItemMap.put(String.valueOf(enumConstant.getValue()), sysCodeItem); } } // values 和 enumClass 同时存在时,values 优先级更高,所以 values 放在后面 // 解析values for (CodeItem codeItem : codeMaster.values()) { SysCodeItem sysCodeItem = SysCodeItem.builder() .name(codeItem.name()).value(codeItem.value()).description(codeItem.description()) .build(); codeItemMap.put(codeItem.value(), sysCodeItem); } List<SysCodeItem> codeItems = Arrays.asList(codeItemMap.values().toArray(new SysCodeItem[0])); logger.info(" {}", JSONUtil.toJsonStr(codeItems)); // 注册数据字典 this.registerCode(sysCodeMaster, codeItems); } } } logger.info("code master register end......"); } /** * 注册数据字典和数据字典数据 * * @param sysCodeMaster 数据字典 * @param codeItems 数据字典数据 */ private void registerCode(SysCodeMaster sysCodeMaster, List<SysCodeItem> codeItems) { // TODO codeMasterService.saveOrUpdateCode(sysCodeMaster, codeItems); }}
枚举注解的两种使用方式
public class User extends BaseEntity { private String name; private Integer age; @CodeMaster(name = "性别", code = "sys_sex", values = { @CodeItem(name = "男", value = "1"), @CodeItem(name = "女", value = "2"), @CodeItem(name = "未知", value = "3"), }) private UserSex sex; @CodeMaster(name = "类型", code = "sys_type_test", enumClass = UserType.class) private UserType type;}
在application配置中开启枚举自动注册
top: walker: dict: register: enabled: true
相关文章
按照苹果的预告,9月9日,苹果就会正式发布新一代的iPhone17系列手机了。而这次,还是一共发布4款机型,分别是iPhone17、iPhone17...
2025-09-08 0
您好:这款游戏可以开挂,确实是有挂的,很多玩家在这款游戏中打牌都会发现很多用户的牌特别好,总是好牌,而且好像能看到-人的牌一样。所以很多小伙伴就怀疑这...
2025-09-08 0
亲,这款游戏可以开挂的,确实是有挂的,很多玩家在这款游戏中打牌都会发现很多用户的牌特别好,总是好牌,而且好像能看到-人的牌一样。所以很多小伙伴就怀疑这...
2025-09-08 0
9月4日,2025年荣成市全国科普月主场活动在荣成市蜊江中学启幕。活动由荣成市科学技术学会、荣成市教育和体育局联合主办,荣成市蜊江中学、荣成市实验中学...
2025-09-08 0
本篇文章给大家谈谈微乐吉林麻将有没有挂,以及微乐吉林麻将开挂神器下载对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。 微乐麻将维护到几点 微乐麻...
2025-09-08 1
发表评论