Skip to content

枚举的使用

这里主要介绍下MybatisPlus的枚举配置以及枚举使用的规范

枚举使用规范

  1. 前后端交互直接使用枚举值, 如"SAVED", 而不应该使用int/String类型, 如1."1","已保存"等
  2. 实体类中的枚举应直接使用枚举类型, 而不是int常量

(在<Effective Java>第30条也说了, 枚举类型是真正的final, 他们是单例的泛型化, 提供了编译时的类型安全; int常量的写法早已过时)

  1. 方法传参同上, 应直接使用枚举类型

MybatisPlus配置

官方文档: https://baomidou.com/guide/enum.html#jackson

  1. 定义顶层枚举接口继承IEnum接口, 重写getValue()方法
Java
/**
 * 枚举顶层接口
 *
 * @author xinzhang
 * @date 2020/8/14 16:15
 */
public interface CaptionEnum extends IEnum<Integer> {
    /**
     * 说明文字
     *
     * @return string
     */
    String caption();
}

后续定义枚举都需要实现顶层接口

Java
/**
 * 性别
 *
 * @author xinzhang
 * @date 2020/8/19 16:15
 */
public enum Gender implements CaptionEnum {
    MALE("男", 0),
    FEMALE("女", 1);

    private final String caption;
    private final int value;

    Gender(String caption, int value) {
        this.caption = caption;
        this.value = value;
    }

    @Override
    public String caption() {
        return this.caption;
    }


    @Override
    public Integer getValue() {
        return this.value;
    }
}
  1. 配置枚举文件夹路径

示例: application.yml

Java
mybatis-plus:
  type-enums-package: top.xinzhang0618.buge.enums

这俩配置完后即定义好了枚举类型在代码与数据库之间的交互

枚举统一访问接口

系统中的枚举, 应该提供统一访问的接口, 前端根据接口封装枚举选择器

补充: 对于数据字典, 同样也应该提供统一访问的接口

EnumController

Java
package top.xinzhang0618.buge.service.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.xinzhang0618.buge.core.Assert;
import top.xinzhang0618.buge.core.enums.CaptionEnum;
import top.xinzhang0618.buge.core.util.StringUtils;
import top.xinzhang0618.buge.service.exception.RestException;
import top.xinzhang0618.buge.vo.EnumVO;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 枚举控制器
 *
 * @author xinzhang
 * @date 2020/8/25 16:57
 */
@RestController
@RequestMapping("/enum")
public class EnumController {
    private static final Map<Class<?>, List<EnumVO>> ENUM_CACHE = new ConcurrentHashMap<>();
    private static final String ENUM_PACKAGE_PREFIX = "top.xinzhang0618.buge.enums.";
    private static final String VALUES = "values";

    @GetMapping("/{name}")
    public List<EnumVO> list(@PathVariable("name") String enumName) {
        List<EnumVO> list;
        try {
            Class<?> aClass = Class.forName(ENUM_PACKAGE_PREFIX + enumName);
            list = ENUM_CACHE.computeIfAbsent(aClass, v -> new ArrayList<>());
            if (!Assert.isEmpty(list)) {
                return list;
            }
            Method method = aClass.getMethod(VALUES);
            CaptionEnum[] captionEnums = (CaptionEnum[]) method.invoke(null);
            Arrays.stream(captionEnums).forEach(c -> list.add(new EnumVO(c)));
        } catch (Exception e) {
            throw new RestException(StringUtils.format("enum: {0} not found!", enumName));
        }
        return list;
    }
}

20210816更新,** 优化EnumController, 结合spring的包扫描, 使enumPackage支持通配符以及子文件夹**

Java
/**
 * 枚举控制器
 *
 * @author xinzhang
 * @date 2020/8/25 16:57
 */
@RestController
@RequestMapping("/enum")
public class EnumController {

    private static final Map<Class<?>, List<EnumVO>> ENUM_CACHE = new ConcurrentHashMap<>();
    /**
     * 枚举包, 支持在该包下建立子包, 支持通配符
     */
    private static final String ENUM_PACKAGE = "top.xinzhang0618.buge";
    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

    /**
     * 查询枚举值列表
     * @param enumName 枚举名不能重复
     * @return
     */
    @GetMapping("/{name}")
    public List<EnumVO> list(@PathVariable("name") String enumName) {
        List<EnumVO> list;
        try {
            Resource resource = RESOURCE_PATTERN_RESOLVER.getResource(
                    ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils
                            .convertClassNameToResourcePath(ENUM_PACKAGE) + "/**/" + enumName + ".class");
            ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
            Class<?> aClass = Class.forName(classMetadata.getClassName());
            list = ENUM_CACHE.computeIfAbsent(aClass, v -> new ArrayList<>());
            if (!Assert.isEmpty(list)) {
                return list;
            }
            Method method = aClass.getMethod("values");
            CaptionEnum[] captionEnums = (CaptionEnum[]) method.invoke(null);
            Arrays.stream(captionEnums).forEach(c -> list.add(new EnumVO(c)));
        } catch (Exception e) {
            throw new RestException(StringUtils.format("enum: {0} not found!", enumName));
        }
        return list;
    }
}

EnumVO

Java
@Getter
@Setter
public class EnumVO {
    private String title;
    private String caption;
    private Integer value;

    public EnumVO(CaptionEnum captionEnum) {
        this.setCaption(captionEnum.caption());
        this.setValue(captionEnum.getValue());
        this.setTitle(captionEnum.toString());
    }
}

接口示例:

Java
GET http://39.106.55.179:30001/enum/ImageType

{
    "code": 0,
    "msg": "",
    "result": [
        {
            "caption": "系统图标",
            "title": "SYSTEM_ICON",
            "value": 1
        },
        {
            "caption": "活动图",
            "title": "ACTIVITY_IMAGE",
            "value": 2
        },
        {
            "caption": "二维码",
            "title": "QR_CODE",
            "value": 3
        }
    ],
    "successs": true
}

前端

前端统一封装枚举选择器,vue+ant Design 示例代码如下:

api.js

Java
import { AxiosWrapper } from "@/lib/axios.wrapper";
export class EnumApi {
  static enumMap = new Map();
  static enumPending = new Map();

  static async getEnum(enumName) {
    if (this.enumMap.has(enumName)) {
      return new Promise(resolve => {
        return resolve(this.enumMap.get(enumName));
      });
    } else {
      let promise;
      if (this.enumPending.has(enumName)) {
        promise = this.enumPending.get(enumName);
      } else {
        promise = AxiosWrapper.get(`/enum/${enumName}`);
        this.enumPending.set(enumName, promise);
      }

      let data = await promise;
      const valueMap = new Map();
      for (const item of data) {
        valueMap.set(item.title, item);
      }
      this.enumMap.set(enumName, valueMap);
      return new Promise(resolve => {
        return resolve(valueMap);
      });
    }
  }
}

enum.selector.vue

Java
<template>
  <a-select
    v-if="multiple"
    v-model="selectedValue"
    mode="multiple"
    style="width: 100%"
  >
    <a-select-option
      v-for="item in map.values()"
      :key="item.value"
      :value="item.title"
    >
      {{ item.caption }}
    </a-select-option>
  </a-select>
  <a-radio-group v-else v-model="selectedValue">
    <a-radio-button
      v-for="item in map.values()"
      :key="item.value"
      :value="item.title"
    >
      {{ item.caption }}
    </a-radio-button>
  </a-radio-group>
</template>
<script>
import { Selector } from "@/lib/mixins";
import { EnumApi } from "./api";
export default {
  mixins: [Selector],
  props: {
    enumName: {
      type: String,
      required: true
    },
    notShow: {
      type: String,
      default: null
    }
  },
  data() {
    return {
      map: new Map()
    };
  },
  created() {
    this.init();
  },
  methods: {
    async init() {
      EnumApi.getEnum(this.enumName).then(data => {
        if (data) {
          const dataMap = new Map();
          data.forEach(element => {
            if (element.title !== this.notShow) {
              dataMap.set(element.title, element);
            }
          });
          this.map = dataMap;
        }
      });
    }
  }
};
</script>

enum.text.vue

Java
<template>
  <span>{{ text }}</span>
</template>
<script>
import { EnumApi } from "./api";
export default {
  props: {
    enumName: {
      type: String,
      required: true
    },
    value: String
  },
  data() {
    return {
      text: "",
      valueMap: null
    };
  },
  watch: {
    value(val) {
      this.mapText(val);
    }
  },
  created() {
    this.init();
  },
  methods: {
    async init() {
      this.valueMap = await EnumApi.getEnum(this.enumName);
      this.mapText(this.value);
    },
    mapText(value) {
      if (this.valueMap && value) {
        this.text = this.valueMap.get(value).caption;
      } else {
        this.text = "";
      }
    }
  }
};
</script>

使用示例:

选择器

Java
 <a-col :span="6">
        <ty-search-item label="投放状态">
          <ty-enum-selector
            v-model="searchQuery.publishStatus"
            multiple
            enumName="PublishStatus"
            @change="searchPublish"
          ></ty-enum-selector>
        </ty-search-item>
      </a-col>

列表字段转换

Java
 <template v-slot:publishType="record">
        <ty-enum-text
          enum-name="PublishType"
          :value="record.publishType"
        ></ty-enum-text>
      </template>