1,字典表Or枚举类?
项目里有很多标识状态的字段,比如订单状态:0-未支付,1-已支付,2-已取消。或者性别sex: 0-未知,1-男,2-女 。等等。一般这种我们都会建相应的枚举类,比如性别枚举:
public enum SexEnum {
UNKNOWN(0,"未知"),
MAN(1,"男"),
WOMAN(2,"女");
private final int code;
private final String text;
SexEnum(int code, String text) {
this.code = code;
this.text = text;
}
//省略getter方法
}
项目里一般都有字典表,我们写了枚举类,还需要把他们存在字典表么?
以前我也没搞清楚,现在清楚了。存字典表里,可以用AOP进行统一字典翻译。比如你返回给前端一个分页列表,列表有sex字段,你就需要在接口文档写上:sex(0-未知,1-男,2-女)。像性别这种还好,一般不会再新增状态。但是其他类型字段,以后再增加一种类型,接口文档就要改,前端代码也要改。但是如果后端返回列表时,把这些字段都给前端翻译好,即使以后再增加类型,前端直接展示,就不用改代码了。比如返回的列表里,遇到这种类型值的,比如以前返回sex=1,现在返回俩字段:sex=1, sex_text=男,前端直接拿到sex_text做展示即可。
有了字典,还需要枚举类或者常量类么?需要的。因为你的代码里,常常是需要关于sex类型的判断的,比如:
if(SexEnum.MAN.getCode() == user.getSex()){
//do something
}
这就需要在代码里再维护一份和字典一样的常量或枚举。至于用枚举还是常量类,无所谓。
2.AOP翻译字典表
一个注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字典
* create by lihaoyang on 2020/10/28
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
/**
* 数据字典code
* @return
*/
String dictCode();
}
注解标在实体类属性上:
/**
* 性别(0-默认未知,1-男,2-女)
*/
@Dict(dictCode = "sex")
private Integer sex;
一个切面类:
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nb.nbbase2.beans.Result;
import com.nb.nbbase2.constant.SysConstant;
import com.nb.nbbase2.service.SysDictService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 字典aop类
* create by lihaoyang on 2020/10/28
*/
@Slf4j
@Aspect //标识该类是一个切面
@Component
public class DictAspect {
@Autowired
private SysDictService sysDictService;
@Autowired
private ObjectMapper objectMapper;
//切入点
@Pointcut("execution(public * com.nb.nbbase2.*.*Controller.*(..))")
public void dictPointCut() {
}
@Around("dictPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Object result = point.proceed();
long start=System.currentTimeMillis();
//解析字典
this.parseDictText(result);
long end=System.currentTimeMillis();
log.debug("AOP解析字典耗时{}ms",(end-start));
return result;
}
/**
* 解析字典
* @param result
*/
private void parseDictText(Object result) {
//如果control返回的是Result对象
if(result instanceof Result){
//如果是分页, TODO:是不是也可以处理不分页的list??或者单个对象?道理一样,这里就不写了
if(((Result) result).getResult() instanceof IPage){
//存放字典转换加工处理后的结果列表
List<JSONObject> items = new ArrayList<>();
IPage iPage = (IPage) ((Result) result).getResult();
//遍历分页列表,
for(Object record : iPage.getRecords()){
//解决@JsonFormat注解解析不了的问题,这个我没实验,先这样吧!
//一行数据的json:如{"sex":1,"id":9,"userType":3,"email":"ttrrr@qq.com","username":"admin后台","status":0}
String json = null;
try {
json = objectMapper.writeValueAsString(record);
} catch (JsonProcessingException e) {
log.error("翻译字典json解析失败:"+e.getMessage(),e);
}
//存放加工处理后的一行数据,准备往里头加字段text用
JSONObject itemObj = JSONObject.parseObject(json);
System.err.println("对象==========> "+itemObj.toJSONString());
//反射获取到所有属性
for(Field field : getAllFields(record)){
Dict dict = field.getAnnotation(Dict.class);
if(dict != null){
//获取实体类成员变量上的@Dict注解的dictCode值 比如@Dict(dictCode = "sex")
String dictCode = dict.dictCode();
//获取属性的值,比如sex=1
String filedValue = String.valueOf(itemObj.get(field.getName()));
//翻译字典,加_text
String itemText = convertDictValue2Text(dictCode,filedValue);
itemObj.put(field.getName()+ SysConstant.DICT_LABEL_SUFFIX,itemText);
//date类型默认转换string格式化日期
//if (field.getType().getName().equals("java.util.Date")&&field.getAnnotation(JsonFormat.class)==null&&item.get(field.getName())!=null){
// SimpleDateFormat aDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// item.put(field.getName(), aDate.format(new Date((Long) item.get(field.getName()))));
//}
}
}
items.add(itemObj);
}
//////set给分页////
iPage.setRecords(items);
}
}
}
/**
* 根据字典code和字典值,转换为字典text文本
* @param dictCode 字典表code 比如sex
* @param dictValue 字典项value 比如男=1
* @return
*/
private String convertDictValue2Text(String dictCode,String dictValue){
if(StringUtils.isEmpty(dictValue)){
return null;
}
StringBuilder dictText = new StringBuilder();
//可能一个字段存多个字典值,比如:允许性别字段(1,2)男女都允许,逗号分隔的,这里需要分隔一下,循环处理。
//如果没有一个字典存多个字典的需求,直接用一次查询翻译就好了??
String[] dictValueArr = dictValue.split(",");
if(ArrayUtils.isNotEmpty(dictValueArr)){
for(String dictVal : dictValueArr){
String text = null;
if(dictVal.trim().length() != 0){
text = sysDictService.findTextByCodeAndDictItemValue(dictCode,dictVal);
}
if(text != null){
if(!"".equals(dictText.toString())){
dictText.append(",");
}
dictText.append(text);
}
}
}
return dictText.toString();
}
/**
* 获取类的所有属性,包括父类
*
* @param object
* @return
*/
public static Field[] getAllFields(Object object) {
Class<?> clazz = object.getClass();
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
//对于继承的对象,父类的属性也得获取一下
clazz = clazz.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
}
查询用户列表,返回json效果:
{
"message":"成功",
"code":200,
"result":{
"records":[
{
"userType_text":"学生",
"sex_text":"未知",
"sex":0,
"allowSex_text":"男,女",
"id":6,
"userType":2,
"status_text":"启用",
"allowSex":"1,2",
"email":"asd@qq.com",
"username":"张三",
"status":1
},
{
"userType_text":"学生",
"sex_text":"男",
"sex":1,
"allowSex_text":"男",
"id":7,
"userType":2,
"status_text":"启用",
"allowSex":"1,",
"email":"dsasd@qq.com",
"username":"李四",
"status":1
},
{
"userType_text":"老师",
"sex_text":"未知",
"sex":0,
"allowSex_text":"男",
"id":8,
"userType":1,
"status_text":"启用",
"allowSex":"1",
"email":"gfdsa@qq.com",
"username":"张老师",
"status":1
},
{
"userType_text":"管理员",
"sex_text":"男",
"sex":1,
"allowSex_text":"男,女",
"id":9,
"userType":3,
"status_text":"禁用",
"allowSex":"1,2",
"email":"ttrrr@qq.com",
"username":"admin后台",
"status":0
}
],
"total":4,
"size":10,
"current":1,
"orders":[
],
"optimizeCountSql":true,
"hitCount":false,
"searchCount":true,
"pages":1
},
"timestamp":1605668907169
}
可以看到,性别sex、用户状态status、用户类型userType 都对应多了一个_text结尾的翻译字段。
对于字典表,可以存到redis缓存起来:
字典表Service,会自动缓存到redis
@Cacheable(value = SysConstant.SYS_DICT_CACHE,key = "#dictCode+':'+#dictItemValue")
@Override
public String findTextByCodeAndDictItemValue(String dictCode, String dictItemValue) {
log.info("字典查库,dictCode {} ,dictValue:{}",dictCode,dictItemValue);
return sysDictMapper.findTextByCodeAndDictItemValue(dictCode,dictItemValue);
}
当新增或修改字典的时候用
@CacheEvict(value=SysConstant.SYS_DICT_CACHE, allEntries=true)//也可以夹在service方法
注解会删除缓存数据
@RequestMapping(value = "/edit", method = RequestMethod.POST)
@CacheEvict(value=SysConstant.SYS_DICT_CACHE, allEntries=true)//也可以夹在service方法
public Result<SysDictItem> edit(@RequestBody SysDictItem sysDictItem) {
Result<SysDictItem> result = new Result<SysDictItem>();
SysDictItem sysDict = sysDictItemService.getById(sysDictItem.getId());
if(sysDict==null) {
result.error500("未找到对应实体");
}else {
boolean ok = sysDictItemService.updateById(sysDictItem);
if(ok) {
result.success("编辑成功!");
}
}
return result;
}
public class SysConstant {
//字典后后缀
public static final String DICT_LABEL_SUFFIX ="_text";
/**
* 字典信息缓存
*/
public static final String SYS_DICT_CACHE = "sys:cache:dict";
/**
* 测试缓存key
*/
public static final String TEST_DEMO_CACHE = "test:demo";
}
mybatis-plus的通用枚举:听同事说他们以前用mybatis-plus的枚举,多么多么好用,看了一下,这么搞得:
实体类属性类型直接是你的枚举类:教师职称
/**
* 职称(引用字典表)
* 原生枚举(带{@link com.baomidou.mybatisplus.annotation.EnumValue}):
*/
private TeacherTitleEnum title;
教师职称枚举类:
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* 教师职称
* @author lihaoyang
* @date 2020/11/12
*/
public enum TeacherTitleEnum {
ZHUJIAO(1, "助教"),
JIANGSHI(2, "讲师"),
FUJIAOSHOU(3, "副教授"),
JIAOSHOU(4,"教授");
@EnumValue//标记数据库存的值是code
private final int code;
private final String desc;
TeacherTitleEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
@JsonValue //标记响应json值
public String getDesc() {
return desc;
}
}
测试:
@Test
public void insert(){
// SysTeacher teacher = new SysTeacher();
// teacher.setTeacherName("小羊");
// teacher.setTitle(TeacherTitleEnum.FUJIAOSHOU);
// teacherMapper.insert(teacher);
List<SysTeacher> list = teacherMapper.selectList(null);
for (SysTeacher tea : list) {
System.err.println(JSONObject.toJSONString(tea));
}
}
////打印:
//
// {"id":1,"teacherName":"牛贝","title":"JIANGSHI"}
// {"id":2,"teacherName":"西决","title":"ZHUJIAO"}
// {"id":3,"teacherName":"石竹","title":"FUJIAOSHOU"}
// {"id":4,"teacherName":"樟树鱼","title":"JIAOSHOU"}
// {"id":5,"teacherName":"小羊","title":"FUJIAOSHOU"}
// {"id":6,"teacherName":"小羊","title":"FUJIAOSHOU"}
// {"id":7,"teacherName":"小羊","title":"FUJIAOSHOU"}
// {"id":8,"teacherName":"小羊","title":"FUJIAOSHOU"}
// {"id":9,"teacherName":"小羊","title":"FUJIAOSHOU"}
他并没有把枚举的汉字描述翻译出来,而是把枚举的变量值翻译出来了。前端拿到怎么处理??
完整代码:https://github.com/lhy1234/Project-Practice/tree/master/aop_dict
欢迎关注个人公众号交流学习: