这里主要介绍下MybatisPlus的枚举配置以及枚举使用的规范
枚举使用规范
- 前后端交互直接使用枚举值, 如”SAVED”, 而不应该使用int/String类型, 如1.”1”,”已保存”等
- 实体类中的枚举应直接使用枚举类型, 而不是int常量
(在
- 方法传参同上, 应直接使用枚举类型
MybatisPlus配置
官方文档: https://baomidou.com/guide/enum.html#jackson
定义顶层枚举接口继承IEnum接口, 重写getValue()方法
/**
* 枚举顶层接口
*
* @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;
}
}
2. 配置枚举文件夹路径
示例: application.yml
```java
mybatis-plus:
type-enums-package: top.xinzhang0618.buge.enums
这俩配置完后即定义好了枚举类型在代码与数据库之间的交互
枚举统一访问接口
系统中的枚举, 应该提供统一访问的接口, 前端根据接口封装枚举选择器
补充: 对于数据字典, 同样也应该提供统一访问的接口
EnumController
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支持通配符以及子文件夹
/**
* 枚举控制器
*
* @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
@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());
}
}
接口示例:
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
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
<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
<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>
使用示例:
选择器
<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>
列表字段转换
<template v-slot:publishType="record">
<ty-enum-text
enum-name="PublishType"
:value="record.publishType"
></ty-enum-text>
</template>