SpringBoot 使用轉(zhuǎn)換器將前端參數(shù)轉(zhuǎn)換為枚舉

前言
最近遇到一個(gè)小伙伴問前端枚舉轉(zhuǎn)換問題,才意識到可以通過轉(zhuǎn)換器(Converter)自動將前端傳入的字段值使用枚舉接收。
我自己搗鼓了一番,現(xiàn)在記錄筆記分享一下!有興趣的小伙伴可以自己嘗試一下!
這里使用的是 MyBatis-Plus 和 SpringBoot 2.3.4.RELEASE
1
實(shí)現(xiàn)過程
配置轉(zhuǎn)換器
/**
* @author liuzhihang
* @date 2021/8/31 16:29
*/
@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 里實(shí)現(xiàn) addFormatters 方法即可,然后 new 一個(gè) ConverterFactory。
WebMvcConfigurer 相信大家都不陌生,一般添加一些攔截器,通用校驗(yàn) token、日志等等都會用到。具體可以參考這篇文章:幾行代碼輕松實(shí)現(xiàn)跨系統(tǒng)傳遞 traceId,再也不用擔(dān)心對不上日志了!,里面有一些其他的應(yīng)用。
就這些,很簡單的實(shí)現(xiàn)。下面介紹下項(xiàng)目的內(nèi)容和代碼,方便理解。
項(xiàng)目代碼
請求參數(shù):
POST http://localhost:8818/user/listByStatus
Content-Type: application/json
{
"orderStatus": 1
}
Controller
/**
* @author liuzhihang
* @date 2021/8/30 11:08
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private OrderService orderService;
@PostMapping(value = "/listByStatus")
public ResultVO<UserResponse> listByStatus(@Validated @RequestBody UserRequest request) {
log.info("請求參數(shù):{}", request);
List<TransOrder> orderList = orderService.getByOrderStatus(request.getOrderStatus());
UserResponse response = new UserResponse();
response.setRecords(orderList);
log.info("返回參數(shù):{}", response);
return ResultVO.success(response);
}
}
Entity
@Data
public class UserRequest {
private OrderStatusEnum orderStatus;
private ViewStatusEnum viewStatus;
}
@Data
public class UserResponse {
private List<TransOrder> records;
}
Web 傳入 orderStatus 為 1,而后端接收對象是 UserRequest 的 orderStatus 字段是個(gè) OrderStatusEnum 類型的枚舉。
這里就需要自動將數(shù)字類型的字段轉(zhuǎn)換為枚舉字段。這個(gè)枚舉會直接通過 MyBatis-Plus 查詢。
為什么要這么用呢?
其實(shí)原因很簡單,使用枚舉限制數(shù)據(jù)庫字段的類型,比如數(shù)據(jù)庫狀態(tài)只有 0、1、2,那就和代碼里的枚舉對應(yīng)起來。防止傳入其他值。
枚舉
public interface BaseEnum {
Object getCode();
}
public enum OrderStatusEnum implements BaseEnum {
INIT(0, "初始狀態(tài)"),
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,所有的枚舉都繼承這個(gè)接口,并實(shí)現(xiàn) getCode 方法。
@EnumValue:MyBatis-Plus 的枚舉,和數(shù)據(jù)庫字段映射用的
@JsonValue:返回給前端時(shí),這個(gè)枚舉字段序列化時(shí),返回參數(shù)只顯示 code。
這樣就可以實(shí)現(xiàn)效果,請求參數(shù)為數(shù)字,接收對象字段為枚舉,返回字段也是 code。
效果

測試結(jié)果經(jīng)過驗(yàn)證,是可以勝任傳入數(shù)值和字符串的。
也可以結(jié)合異常處理器,返回通用異常。具體怎么用查一查 @ExceptionHandler 就知道了。
具體說明
在 addFormatters 方法中可以看到 registry.addConverterFactory() 接收的是一個(gè) ConverterFactory 對象。
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
S 就是傳入的字段類型(數(shù)字,字符串) R 是要轉(zhuǎn)換為的類型(枚舉) T 繼承了 R,其實(shí)就是參數(shù)對象中字段的類型
在 ConverterFactory 的 getConverter 方法則需要返回一個(gè)實(shí)際的轉(zhuǎn)換器 Converter
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
}
convert 方法的入?yún)⑹且粋€(gè) source,就是要轉(zhuǎn)換為什么類型的,這里就是數(shù)字/字符串,然后返回一個(gè)枚舉即可。
注意這里加了 @FunctionalInterface 就意味著這里是可以用 lambda 表達(dá)式的。
2
優(yōu)化
一般 WebConfig 中除了實(shí)現(xiàn) addFormatters 方法外,還會實(shí)現(xiàn) addInterceptors 等等,這樣寫難免會很長,所以可以改為下面這種。
@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("/**");
// app token校驗(yàn)
registry.addInterceptor(appTokenInterceptor)
.addPathPatterns("/app/**");
}
@Override
public void addFormatters(FormatterRegistry registry) {
// 枚舉轉(zhuǎn)換
registry.addConverterFactory(enumConverterFactory);
}
}
這種就需要咱們創(chuàng)建 EnumConverterFactory 類并實(shí)現(xiàn) ConverterFactory 接口了,還得注入到 Spring 容器中
@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 不正確");
};
}
}
要是實(shí)在覺得 lambda 看不慣,并且也不夠優(yōu)雅,那可以使用下面的方式。
@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 不正確");
}
}
3
總結(jié)
當(dāng)然這里也有一些其他的優(yōu)化點(diǎn),比如可以使用緩存將 Convert 緩存起來。
不過我也遇到一個(gè)其他的問題,就是我 debug 斷點(diǎn)竟然一直沒有斷到轉(zhuǎn)換器中,不知道有沒有小伙伴嘗試過?
- <End /> -
歷史文章 | 相關(guān)推薦

