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

枚举使用规范

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

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

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

MybatisPlus配置

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

  1. 定义顶层枚举接口继承IEnum接口, 重写getValue()方法

    1. /**
    2. * 枚举顶层接口
    3. *
    4. * @author xinzhang
    5. * @date 2020/8/14 16:15
    6. */
    7. public interface CaptionEnum extends IEnum<Integer> {
    8. /**
    9. * 说明文字
    10. *
    11. * @return string
    12. */
    13. String caption();
    14. }

    后续定义枚举都需要实现顶层接口 ```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; }

  1. @Override
  2. public Integer getValue() {
  3. return this.value;
  4. }

}

  1. 2. 配置枚举文件夹路径
  2. 示例: application.yml
  3. ```java
  4. mybatis-plus:
  5. type-enums-package: top.xinzhang0618.buge.enums

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

枚举统一访问接口

系统中的枚举, 应该提供统一访问的接口, 前端根据接口封装枚举选择器
补充: 对于数据字典, 同样也应该提供统一访问的接口
EnumController

  1. package top.xinzhang0618.buge.service.controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.PathVariable;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import top.xinzhang0618.buge.core.Assert;
  7. import top.xinzhang0618.buge.core.enums.CaptionEnum;
  8. import top.xinzhang0618.buge.core.util.StringUtils;
  9. import top.xinzhang0618.buge.service.exception.RestException;
  10. import top.xinzhang0618.buge.vo.EnumVO;
  11. import java.lang.reflect.Method;
  12. import java.util.*;
  13. import java.util.concurrent.ConcurrentHashMap;
  14. /**
  15. * 枚举控制器
  16. *
  17. * @author xinzhang
  18. * @date 2020/8/25 16:57
  19. */
  20. @RestController
  21. @RequestMapping("/enum")
  22. public class EnumController {
  23. private static final Map<Class<?>, List<EnumVO>> ENUM_CACHE = new ConcurrentHashMap<>();
  24. private static final String ENUM_PACKAGE_PREFIX = "top.xinzhang0618.buge.enums.";
  25. private static final String VALUES = "values";
  26. @GetMapping("/{name}")
  27. public List<EnumVO> list(@PathVariable("name") String enumName) {
  28. List<EnumVO> list;
  29. try {
  30. Class<?> aClass = Class.forName(ENUM_PACKAGE_PREFIX + enumName);
  31. list = ENUM_CACHE.computeIfAbsent(aClass, v -> new ArrayList<>());
  32. if (!Assert.isEmpty(list)) {
  33. return list;
  34. }
  35. Method method = aClass.getMethod(VALUES);
  36. CaptionEnum[] captionEnums = (CaptionEnum[]) method.invoke(null);
  37. Arrays.stream(captionEnums).forEach(c -> list.add(new EnumVO(c)));
  38. } catch (Exception e) {
  39. throw new RestException(StringUtils.format("enum: {0} not found!", enumName));
  40. }
  41. return list;
  42. }
  43. }

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

  1. /**
  2. * 枚举控制器
  3. *
  4. * @author xinzhang
  5. * @date 2020/8/25 16:57
  6. */
  7. @RestController
  8. @RequestMapping("/enum")
  9. public class EnumController {
  10. private static final Map<Class<?>, List<EnumVO>> ENUM_CACHE = new ConcurrentHashMap<>();
  11. /**
  12. * 枚举包, 支持在该包下建立子包, 支持通配符
  13. */
  14. private static final String ENUM_PACKAGE = "top.xinzhang0618.buge";
  15. private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
  16. private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
  17. /**
  18. * 查询枚举值列表
  19. * @param enumName 枚举名不能重复
  20. * @return
  21. */
  22. @GetMapping("/{name}")
  23. public List<EnumVO> list(@PathVariable("name") String enumName) {
  24. List<EnumVO> list;
  25. try {
  26. Resource resource = RESOURCE_PATTERN_RESOLVER.getResource(
  27. ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils
  28. .convertClassNameToResourcePath(ENUM_PACKAGE) + "/**/" + enumName + ".class");
  29. ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
  30. Class<?> aClass = Class.forName(classMetadata.getClassName());
  31. list = ENUM_CACHE.computeIfAbsent(aClass, v -> new ArrayList<>());
  32. if (!Assert.isEmpty(list)) {
  33. return list;
  34. }
  35. Method method = aClass.getMethod("values");
  36. CaptionEnum[] captionEnums = (CaptionEnum[]) method.invoke(null);
  37. Arrays.stream(captionEnums).forEach(c -> list.add(new EnumVO(c)));
  38. } catch (Exception e) {
  39. throw new RestException(StringUtils.format("enum: {0} not found!", enumName));
  40. }
  41. return list;
  42. }
  43. }

EnumVO

  1. @Getter
  2. @Setter
  3. public class EnumVO {
  4. private String title;
  5. private String caption;
  6. private Integer value;
  7. public EnumVO(CaptionEnum captionEnum) {
  8. this.setCaption(captionEnum.caption());
  9. this.setValue(captionEnum.getValue());
  10. this.setTitle(captionEnum.toString());
  11. }
  12. }

接口示例:

  1. GET http://39.106.55.179:30001/enum/ImageType
  2. {
  3. "code": 0,
  4. "msg": "",
  5. "result": [
  6. {
  7. "caption": "系统图标",
  8. "title": "SYSTEM_ICON",
  9. "value": 1
  10. },
  11. {
  12. "caption": "活动图",
  13. "title": "ACTIVITY_IMAGE",
  14. "value": 2
  15. },
  16. {
  17. "caption": "二维码",
  18. "title": "QR_CODE",
  19. "value": 3
  20. }
  21. ],
  22. "successs": true
  23. }

前端

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

  1. import { AxiosWrapper } from "@/lib/axios.wrapper";
  2. export class EnumApi {
  3. static enumMap = new Map();
  4. static enumPending = new Map();
  5. static async getEnum(enumName) {
  6. if (this.enumMap.has(enumName)) {
  7. return new Promise(resolve => {
  8. return resolve(this.enumMap.get(enumName));
  9. });
  10. } else {
  11. let promise;
  12. if (this.enumPending.has(enumName)) {
  13. promise = this.enumPending.get(enumName);
  14. } else {
  15. promise = AxiosWrapper.get(`/enum/${enumName}`);
  16. this.enumPending.set(enumName, promise);
  17. }
  18. let data = await promise;
  19. const valueMap = new Map();
  20. for (const item of data) {
  21. valueMap.set(item.title, item);
  22. }
  23. this.enumMap.set(enumName, valueMap);
  24. return new Promise(resolve => {
  25. return resolve(valueMap);
  26. });
  27. }
  28. }
  29. }

enum.selector.vue

  1. <template>
  2. <a-select
  3. v-if="multiple"
  4. v-model="selectedValue"
  5. mode="multiple"
  6. style="width: 100%"
  7. >
  8. <a-select-option
  9. v-for="item in map.values()"
  10. :key="item.value"
  11. :value="item.title"
  12. >
  13. {{ item.caption }}
  14. </a-select-option>
  15. </a-select>
  16. <a-radio-group v-else v-model="selectedValue">
  17. <a-radio-button
  18. v-for="item in map.values()"
  19. :key="item.value"
  20. :value="item.title"
  21. >
  22. {{ item.caption }}
  23. </a-radio-button>
  24. </a-radio-group>
  25. </template>
  26. <script>
  27. import { Selector } from "@/lib/mixins";
  28. import { EnumApi } from "./api";
  29. export default {
  30. mixins: [Selector],
  31. props: {
  32. enumName: {
  33. type: String,
  34. required: true
  35. },
  36. notShow: {
  37. type: String,
  38. default: null
  39. }
  40. },
  41. data() {
  42. return {
  43. map: new Map()
  44. };
  45. },
  46. created() {
  47. this.init();
  48. },
  49. methods: {
  50. async init() {
  51. EnumApi.getEnum(this.enumName).then(data => {
  52. if (data) {
  53. const dataMap = new Map();
  54. data.forEach(element => {
  55. if (element.title !== this.notShow) {
  56. dataMap.set(element.title, element);
  57. }
  58. });
  59. this.map = dataMap;
  60. }
  61. });
  62. }
  63. }
  64. };
  65. </script>

enum.text.vue

  1. <template>
  2. <span>{{ text }}</span>
  3. </template>
  4. <script>
  5. import { EnumApi } from "./api";
  6. export default {
  7. props: {
  8. enumName: {
  9. type: String,
  10. required: true
  11. },
  12. value: String
  13. },
  14. data() {
  15. return {
  16. text: "",
  17. valueMap: null
  18. };
  19. },
  20. watch: {
  21. value(val) {
  22. this.mapText(val);
  23. }
  24. },
  25. created() {
  26. this.init();
  27. },
  28. methods: {
  29. async init() {
  30. this.valueMap = await EnumApi.getEnum(this.enumName);
  31. this.mapText(this.value);
  32. },
  33. mapText(value) {
  34. if (this.valueMap && value) {
  35. this.text = this.valueMap.get(value).caption;
  36. } else {
  37. this.text = "";
  38. }
  39. }
  40. }
  41. };
  42. </script>

使用示例:
选择器

  1. <a-col :span="6">
  2. <ty-search-item label="投放状态">
  3. <ty-enum-selector
  4. v-model="searchQuery.publishStatus"
  5. multiple
  6. enumName="PublishStatus"
  7. @change="searchPublish"
  8. ></ty-enum-selector>
  9. </ty-search-item>
  10. </a-col>

列表字段转换

  1. <template v-slot:publishType="record">
  2. <ty-enum-text
  3. enum-name="PublishType"
  4. :value="record.publishType"
  5. ></ty-enum-text>
  6. </template>