SpringBoot 使用轉(zhuǎn)換器將前端參數(shù)轉(zhuǎn)換為枚舉
前言
最近遇到一個小伙伴問前端枚舉轉(zhuǎn)換問題,才意識到可以通過轉(zhuǎn)換器(Converter)自動將前端傳入的字段值使用枚舉接收。
我自己搗鼓了一番,現(xiàn)在記錄筆記分享一下!有興趣的小伙伴可以自己嘗試一下!
這里使用的是 MyBatis-Plus 和 SpringBoot 2.3.4.RELEASE
1實現(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 里實現(xiàn) addFormatters 方法即可,然后 new 一個 ConverterFactory。
WebMvcConfigurer 相信大家都不陌生,一般添加一些攔截器,通用校驗 token、日志等等都會用到。具體可以參考這篇文章:幾行代碼輕松實現(xiàn)跨系統(tǒng)傳遞 traceId,再也不用擔(dān)心對不上日志了!,里面有一些其他的應(yīng)用。
就這些,很簡單的實現(xiàn)。下面介紹下項目的內(nèi)容和代碼,方便理解。
項目代碼
- 請求參數(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 字段是個 OrderStatusEnum 類型的枚舉。
這里就需要自動將數(shù)字類型的字段轉(zhuǎn)換為枚舉字段。這個枚舉會直接通過 MyBatis-Plus 查詢。
為什么要這么用呢?
其實原因很簡單,使用枚舉限制數(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,所有的枚舉都繼承這個接口,并實現(xiàn) getCode 方法。
@EnumValue:MyBatis-Plus 的枚舉,和數(shù)據(jù)庫字段映射用的
@JsonValue:返回給前端時,這個枚舉字段序列化時,返回參數(shù)只顯示 code。
這樣就可以實現(xiàn)效果,請求參數(shù)為數(shù)字,接收對象字段為枚舉,返回字段也是 code。
效果
測試結(jié)果
測試結(jié)果經(jīng)過驗證,是可以勝任傳入數(shù)值和字符串的。
也可以結(jié)合異常處理器,返回通用異常。具體怎么用查一查 @ExceptionHandler 就知道了。
具體說明
在 addFormatters 方法中可以看到 registry.addConverterFactory() 接收的是一個 ConverterFactory 對象。
- public interface ConverterFactory<S, R> {
- <T extends R> Converter<S, T> getConverter(Class<T> targetType);
- }
- S 就是傳入的字段類型(數(shù)字,字符串)
- R 是要轉(zhuǎn)換為的類型(枚舉)
- T 繼承了 R,其實就是參數(shù)對象中字段的類型
在 ConverterFactory 的 getConverter 方法則需要返回一個實際的轉(zhuǎn)換器 Converter
- @FunctionalInterface
- public interface Converter<S, T> {
- @Nullable
- T convert(S source);
- }
convert 方法的入?yún)⑹且粋€ source,就是要轉(zhuǎn)換為什么類型的,這里就是數(shù)字/字符串,然后返回一個枚舉即可。
注意這里加了 @FunctionalInterface 就意味著這里是可以用 lambda 表達(dá)式的。
2優(yōu)化
一般 WebConfig 中除了實現(xiàn) addFormatters 方法外,還會實現(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校驗
- registry.addInterceptor(appTokenInterceptor)
- .addPathPatterns("/app/**");
- }
- @Override
- public void addFormatters(FormatterRegistry registry) {
- // 枚舉轉(zhuǎn)換
- registry.addConverterFactory(enumConverterFactory);
- }
- }
這種就需要咱們創(chuàng)建 EnumConverterFactory 類并實現(xiàn) ConverterFactory 接口了,還得注入到 Spring 容器中
- @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)化點,比如可以使用緩存將 Convert 緩存起來。
不過我也遇到一個其他的問題,就是我 debug 斷點竟然一直沒有斷到轉(zhuǎn)換器中,不知道有沒有小伙伴嘗試過?