[故事背景]
由于之前自己一直在都开发其他的模块,最近开始加入到同事们的模块中,这一看不要紧,满屏的魔法数字让我差点喘不过气来。虽然也有些同事想到了使用静态变量来处理,但是明明就是一类的东西非要分开用不同的罐子来装它,不觉得别扭吗?于是决定记录一下之前自己使用枚举的经验;先贴一下魔法数字供各位客观欣赏
@Override
public ReturnObject putCanceUser(UcasCancellationBo ucasCancellationBo) {
UcasCancellationInfo ucasCancellationInfo = new UcasCancellationInfo();
ucasCancellationInfo.setId(ucasCancellationBo.getId());
ucasCancellationInfo.setIsDelete(1);
int count = ucasCancellationInfoMapper.updateById(ucasCancellationInfo);
if(count>0){
return ReturnObject.success();
}else {
return ReturnObject.fail("操作失败");
}
}
[枚举映射数据库字段]
如果你也使用mybatis框架来做持久化层的话,那么可以使用mybatisplus的通用枚举功能,这里也有好几种用法;我只记录自己喜欢用的,其他方法可以自行查看官网
首先配置枚举包扫描路径
mybatis-plus:
# 扫描通用枚举
type-enums-package: com.xxjqr.rbac.enums
[方法一 @EnumValue与@JsonValue]
在枚举中使用注解 @EnumValue与@JsonValue
@Getter
@AllArgsConstructor
public enum SexEnum implements BaseEnum {
MALE(1,"男"),
FEMALE(0,"女"),
UNKNOW(2,"未知")
;
@EnumValue
private final Integer code;
@JsonValue
private final String desc;
}
当SexEnum作为一个实体对象的属性时,那么该属性所映射的数据库字段就是@EnumValue作用的值
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class TbUser extends Model<TbUser> {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long id;
/**
* 性别
*/
private SexEnum sex;
/**
* 状态 0 禁用 1正常
*/
private StatusEnum status;
/**
* 是否删除 1:已删除;0:未删除
*/
@TableLogic
private Integer isDelete;
@Override
protected Serializable pkVal() {
return this.id;
}
}
比如如下伪代码,就会把性别为0的girl插入数据库中
TbUser user = new TbUser(SexEnum.FEMALE)
userMapper.insert(user)
同样,当从数据库中读取数据时,数据库的值也会对应转换成相应的枚举对象
TbUser use = userMapper.selectById(id)
use.sex == SexEnum.FEMALE
如果该实体对象返回到前端,那么该枚举属性对应的值就是@JsonValue修饰的字段
[方法二 实现IEnum接口]
重写getValue等同于使用@EnumValue注解 重写toString等同于使用@JsonValue注解
@AllArgsConstructor
@Getter
//IEnum<Integer> Inteter泛型表示映射到数据库的字段类型
public enum BusinessStatusEnum implements IEnum<Integer>{
FAIL(0, "失败"),
SUCCESS(1, "成功"),
;
private final Integer code;
private final String desc;
@Override
public Integer getValue() {
return code;
}
@Override
public String toString() {
return this.desc;
}
}
然后还需要配置一下jackson的反序列化枚举 write_enums_using_to_string
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write_enums_using_to_string: true
[反序列化参数转枚举]
现在代码中一部分的魔法数字已经被干掉了,但仍有一些顽疾是需要其他方式处理的。比如,前端在调用增删改查接口的时候,会传入类似 status='1',{'sex':1}之类的参数,那如何在这个接口接参(专业点说叫json反序列化)的过程中把魔法数字转换成枚举对象呢?
[一:get请求与post formdata传参]
这类的请求,我们可以直接使用spring提供的convert转换器来转换
[1] 定义公共枚举接口
public interface BaseEnum {
Integer getCode();
}
[2] 继承公共枚举接口
@AllArgsConstructor
@Getter
public enum BusinessStatusEnum implements BaseEnum{
FAIL(0, "失败"),
SUCCESS(1, "成功"),
;
@EnumValue
private final Integer code;
@JsonValue
private final String desc;
}
@Getter
@AllArgsConstructor
public enum MenuTypeEnum implements BaseEnum {
CATALOG(1,"目录"),
MENU(2,"菜单"),
BUTTON(3,"按钮");
@EnumValue
@JsonValue
private final Integer code;
private final String desc;
}
[3] 定义String->Enum的converter转换器
/*
因为我加了write_numbers_as_strings配置,所有数值类型全都转换成了string
spring:
jackson:
generator:
write_numbers_as_strings: true
所以这里写的转换器是String->Enum
*/
public class StringToEnumConverter<T extends BaseEnum> implements Converter<String, BaseEnum> {
private Map<String, T> enumMap = new HashMap<>();
/**
* @描述 StringToEnumConverter构造函数,起到缓存的作用,先把对应的code->enum映射关系缓存起来,匹配到了直接获取
* @码农 丁昌江
* @日期 2021/5/24 15:16
*/
public StringToEnumConverter(Class<T> enumType) {
//把所有枚举对象读取出来
T[] enums = enumType.getEnumConstants();
for (T e : enums) {
//枚举对象的code作为key,枚举对象作为value
enumMap.put(e.getCode().toString(), e);
}
}
/**
* @描述 code->enum 核心代码
* @码农 丁昌江
* @日期 2021/5/24 15:33
*/
@Override
public T convert(String source) {
if (StrUtil.isEmpty(source)) {
return null;
}
T t = enumMap.get(source);
if (ObjectUtil.isNull(t)) {
throw new IllegalArgumentException("无法匹配对应的枚举类型");
}
return t;
}
}
[4] 定义对应的转换器工厂
public class StringToEnumConverterFactory implements ConverterFactory<String, BaseEnum> {
private static final Map<Class, Converter> CONVERTERS = new HashMap<>();
/**
* 从转换器工厂中找到对应的转换器返回
* @param targetType 转换后的类型
* @return 返回一个转化器
*/
@Override
public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
StringToEnumConverter converter = (StringToEnumConverter) CONVERTERS.get(targetType);
if (converter == null) {
converter = new StringToEnumConverter(targetType);
CONVERTERS.put(targetType, converter);
}
return converter;
}
}
[5] 将转换器工厂添加到springboot配置中
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 枚举类的转换器工厂 addConverterFactory
*/
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new StringToEnumConverterFactory());
}
}
[拓展]
有时候会不会有这种需求呢,前端传过来的sex='FEMAIL',这种情况下如何转换成对应的枚举对象呢?
[1] 公共枚举接口添加getName()方法
public interface BaseEnum {
Integer getCode();
String getName();
}
[2] 枚举对象重写getName()
@Getter
@AllArgsConstructor
public enum SexEnum implements BaseEnum {
MALE(1,"男"),
FEMALE(0,"女"),
UNKNOW(2,"未知")
;
@EnumValue
private final Integer code;
@JsonValue
private final String desc;
@Override
public String getName() {
return name();
}
}
[3] 转换器新增一个name->enum的缓存
public class StringToEnumConverter<T extends BaseEnum> implements Converter<String, BaseEnum> {
private Map<String, T> enumMap = new HashMap<>();
private Map<String, T> nameEnumMap = new HashMap<>();
/**
* @描述 StringToEnumConverter构造函数,起到缓存的作用,先把对应的code->enum映射关系缓存起来,匹配到了直接获取
* @码农 丁昌江
* @日期 2021/5/24 15:16
*/
public StringToEnumConverter(Class<T> enumType) {
//把所有枚举对象读取出来
T[] enums = enumType.getEnumConstants();
for (T e : enums) {
//枚举对象的code作为key,枚举对象作为value
enumMap.put(e.getCode().toString(), e);
//枚举对象的name作为key,枚举对象作为value
nameEnumMap.put(e.getName(), e);
}
}
/**
* @描述 code->enum 核心代码
* @码农 丁昌江
* @日期 2021/5/24 15:33
*/
@Override
public T convert(String source) {
if (StrUtil.isEmpty(source)) {
return null;
}
T t = enumMap.get(source);
// 如果code->enum的缓存中无法获取,那么尝试从name->enum的缓存中获取
if (ObjectUtil.isNull(t)) {
t = nameEnumMap.get(source);
if (ObjectUtil.isNull(source)) {
throw new IllegalArgumentException("无法匹配对应的枚举类型");
}
}
return t;
}
}
[二:post json 传参]
当post请求使用json格式传参,也就是后端使用@RequstBody注解的时候,springboot默认使用的是jackson反序列化工具,那么我们就可以从jackson上安排
[1] 使用@JsonCreator注解可以自定义枚举创建方式
@Getter
@AllArgsConstructor
public enum StatusEnum implements BaseEnum{
ENABLE(1,"正常"),
DISABLE(0,"禁用")
;
@EnumValue
@JsonValue
private final Integer code;
private final String desc;
//当版本降为2.10.5时能正常运行,2.11版本只能将@JsonCreator修改为@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static StatusEnum valueOf(int code) {
//StatusEnum.values() == values()
for (StatusEnum statusEnum : values()) {
if (statusEnum.code == code) {
return statusEnum;
}
}
return null;
}
@Override
public String getName() {
return name();
}
}
当参数为时,就能自动转换成相应的枚举对象
{
"status":"0"
}
[拓展]
下面的文章来自知乎 ,讲述了更多的用法,但是目前鄙人没有这种需求,大家去看看吧!