前言
最近遇到一个小伙伴问前端枚举转换问题,才意识到可以通过转换器(Converter)自动将前端传入的字段值使用枚举接收。
我自己捣鼓了一番,现在记录笔记分享一下!有兴趣的小伙伴可以自己尝试一下!
这里使用的是 MyBatis-Plus 和 SpringBoot 2.3.4.RELEASE
实现过程
配置转换器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
@Configuration public class WebConfig implements WebMvcConfigurer {
@Override public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new ConverterFactory<Object, BaseEnum>() { @Override public <T extends BaseEnum> Converter<Object, T> getConverter(Class<T> targetType) {
T[] enums = targetType.getEnumConstants();
return source -> {
for (T e : enums) { if (e.getCode().equals(source)) { return e; } }
throw new IllegalArgumentException("枚举 Code 不正确"); }; } }); } }
|
直接在 WebMvcConfigurer 里实现 addFormatters 方法即可,然后 new 一个 ConverterFactory。
WebMvcConfigurer 相信大家都不陌生,一般添加一些拦截器,通用校验 token、日志等等都会用到。具体可以参考这篇文章:几行代码轻松实现跨系统传递 traceId,再也不用担心对不上日志了!,里面有一些其他的应用。
就这些,很简单的实现。下面介绍下项目的内容和代码,方便理解。
项目代码
1 2 3 4 5 6
| POST http://localhost:8818/user/listByStatus Content-Type: application/json
{ "orderStatus": 1 }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
@Slf4j @RestController @RequestMapping("/user") public class UserController {
@Autowired private OrderService orderService;
@PostMapping(value = "/listByStatus") public ResultVO<UserResponse> listByStatus(@Validated @RequestBody UserRequest request) {
log.info("请求参数:{}", request);
List<TransOrder> orderList = orderService.getByOrderStatus(request.getOrderStatus());
UserResponse response = new UserResponse();
response.setRecords(orderList);
log.info("返回参数:{}", response);
return ResultVO.success(response); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Data public class UserRequest {
private OrderStatusEnum orderStatus; private ViewStatusEnum viewStatus; }
@Data public class UserResponse {
private List<TransOrder> records;
}
|
Web 传入 orderStatus 为 1,而后端接收对象是 UserRequest 的 orderStatus 字段是个 OrderStatusEnum 类型的枚举。
这里就需要自动将数字类型的字段转换为枚举字段。这个枚举会直接通过 MyBatis-Plus 查询。
为什么要这么用呢?
其实原因很简单,使用枚举限制数据库字段的类型,比如数据库状态只有 0、1、2,那就和代码里的枚举对应起来。防止传入其他值。
1 2 3
| public interface BaseEnum { Object getCode(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public enum OrderStatusEnum implements BaseEnum {
INIT(0, "初始状态"), SUCCESS(1, "成功"), FAIL(2, "失败");
@EnumValue @JsonValue private final int code;
private final String desc;
OrderStatusEnum(int code, String desc) { this.code = code; this.desc = desc; }
@Override public Integer getCode() { return code; }
public String getDesc() { return desc; } }
|
这里先声明接口 BaseEnum,所有的枚举都继承这个接口,并实现 getCode 方法。
@EnumValue
:MyBatis-Plus 的枚举,和数据库字段映射用的
@JsonValue
:返回给前端时,这个枚举字段序列化时,返回参数只显示 code。
这样就可以实现效果,请求参数为数字,接收对象字段为枚举,返回字段也是 code。
效果

测试结果经过验证,是可以胜任传入数值和字符串的。
也可以结合异常处理器,返回通用异常。具体怎么用查一查 @ExceptionHandler
就知道了。
具体说明
在 addFormatters 方法中可以看到 registry.addConverterFactory() 接收的是一个 ConverterFactory 对象。
1 2 3 4
| public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType); }
|
- S 就是传入的字段类型(数字,字符串)
- R 是要转换为的类型(枚举)
- T 继承了 R,其实就是参数对象中字段的类型
在 ConverterFactory 的 getConverter 方法则需要返回一个实际的转换器 Converter
1 2 3 4 5 6 7
| @FunctionalInterface public interface Converter<S, T> {
@Nullable T convert(S source);
}
|
convert 方法的入参是一个 source,就是要转换为什么类型的,这里就是数字/字符串,然后返回一个枚举即可。
注意这里加了 @FunctionalInterface
就意味着这里是可以用 lambda 表达式的。
优化
一般 WebConfig 中除了实现 addFormatters 方法外,还会实现 addInterceptors 等等,这样写难免会很长,所以可以改为下面这种。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Configuration public class WebConfig implements WebMvcConfigurer {
@Autowired private LogInterceptor logInterceptor;
@Autowired private AppTokenInterceptor appTokenInterceptor;
@Autowired private EnumConverterFactory enumConverterFactory;
@Override public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor) .addPathPatterns("/**");
registry.addInterceptor(appTokenInterceptor) .addPathPatterns("/app/**");
}
@Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(enumConverterFactory); } }
|
这种就需要咱们创建 EnumConverterFactory 类并实现 ConverterFactory 接口了,还得注入到 Spring 容器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Component public class EnumConverterFactory implements ConverterFactory<Object, BaseEnum> {
@Override public <T extends BaseEnum> Converter<Object, T> getConverter(Class<T> targetType) {
T[] enums = targetType.getEnumConstants();
return source -> { for (T e : enums) { if (e.getCode().equals(source)) { return e; } }
throw new IllegalArgumentException("枚举 Code 不正确"); }; } }
|
要是实在觉得 lambda 看不惯,并且也不够优雅,那可以使用下面的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Component public class EnumConverterFactory implements ConverterFactory<Object, BaseEnum> {
@Override public <T extends BaseEnum> Converter<Object, T> getConverter(Class<T> targetType) {
return new EnumConverter<>(targetType); } } public class EnumConverter<T extends BaseEnum> implements Converter<Object, T> {
private final Class<T> targetType;
public EnumConverter(Class<T> targetType) { this.targetType = targetType; }
@Override public T convert(Object source) {
for (T e : targetType.getEnumConstants()) { if (e.getCode().equals(source)) { return e; } }
throw new IllegalArgumentException("枚举 Code 不正确"); } }
|
总结
当然这里也有一些其他的优化点,比如可以使用缓存将 Convert 缓存起来。
不过我也遇到一个其他的问题,就是我 debug 断点竟然一直没有断到转换器中,不知道有没有小伙伴尝试过?