一、应用场景
我们在开发过程中可能会遇到以下情况
- 数据库存储的状态属性是code,比如设备状态存储的是0,1,对用户来讲看到0和1是没有意义的,前端应该展示其中文含义(如:设备状态: 未启动/启动)
在这种情况下,后端需要将code翻译成中文,前端直接展示即可。
小编自己实现了字段翻译的功能,介绍如下
二、代码逻辑
服务启动以后加载字典配置到缓存,缓存会被TranslateAspect切面使用,将原始值翻译成字典配置的中文。
三、具体用法
以下图为例,deviceStatus(被翻译的字段)是数据库的原始值
deviceStatusValue是翻译后的中文字段,@TranslateField注解要加在deviceStatusValue上面;
其中主要是2个配置
- typeCode = “DICT_DEVICE_STATUS” 字典类的code
- itemField ="deviceStatus" 以哪个字段的值作为字典项的code进行翻译
@TranslateService用在需要翻译的service上面,切面会拦截这个service出去的消息体,对消息体中添加@TranslateField注解的字段进行翻译,比如把上述设备运行状况的1翻译成自动运行,这个显示值可以在数据库配置,动态调整。
1.自定义注解(加在service上)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TranslateService {
}
2.自定义注解(加在字段上)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TranslateField {
String typeCode();
String itemField();
}
3.自动翻译切面
@Aspect
@Component
public class TranslateAspect {
private SwitchConfig switchConfig;
private DictLoadCache dictLoadCache;
public TranslateAspect(DictLoadCache dictLoadCache, SwitchConfig switchConfig) {
this.dictLoadCache = dictLoadCache;
this.switchConfig = switchConfig;
}
@Pointcut("@within(com.risen.helper.translate.annotation.TranslateService)")
private void pointcut() {
}
@Around("pointcut()")
public Object translateAroundInterceptor(ProceedingJoinPoint point) throws Throwable {
Object result = point.proceed();
Predicate predicate = s -> s;
if (predicate.test(switchConfig.getTranslateSwitch())) {
Optional.ofNullable(result).ifPresent(item -> {
returnObjectProcessor(item);
});
}
return result;
}
private void returnObjectProcessor(Object obj) {
if (!notEmpty(obj)) {
return;
}
Field[] fields = obj.getClass().getDeclaredFields();
Field[] superFields = new Field[]{};
Class superClass = obj.getClass().getSuperclass();
if (notEmpty(superClass)) {
superFields = superClass.getDeclaredFields();
}
Field[] allField = ArrayUtils.addAll(fields, superFields);
Map fieldMap = Stream.of(allField).collect(Collectors.toMap(s -> s.getName(), s -> s));
Stream.of(allField).forEach(field -> {
field.setAccessible(true);
Class fieldType = field.getType();
try {
Object fieldValue = field.get(obj);
if (List.class.isAssignableFrom(fieldType)) {
//如果是list需要递归
if (notEmpty(fieldValue)) {
List
四、字典缓存设计(可自行实现)
字典缓存实现类:服务启动以后加载字典配置到缓存,缓存会被上面的TranslateAspect使用,以此为基础进行翻译。
@Component
public class DictLoadCache extends AgentCacheAbstract> {
@Autowired
private DictItemMapper dictItemMapper;
@Autowired
private DictTypeMapper dictTypeMapper;
public DictLoadCache() {
super(null, null, null);
}
@Override
public void loadCache() {
LogUtil.info("start load dict cache...");
List itemList = dictItemMapper.selectList(new LambdaQueryWrapper());
List typeList = dictTypeMapper.selectList(new LambdaQueryWrapper());
Optional.ofNullable(typeList).ifPresent(type -> {
Optional.ofNullable(itemList).ifPresent(item -> {
Map> itemEntityMap = item.stream().collect(Collectors.groupingBy(DictItemEntity::getTypeCode));
Set typeSetInfo = type.stream().map(s -> s.getTypeCode()).collect(Collectors.toSet());
itemEntityMap.forEach((k, v) -> {
if (typeSetInfo.contains(k)) {
put(k, v.stream().map(s -> {
return new DictItemValueDTO(s);
}).collect(Collectors.toList()));
}
});
});
});
}
public String getDictNameByCacheKey(String typeCode, Object value) {
AtomicReference dictName = new AtomicReference("");
Predicate predicate = s -> !CollectionUtils.isEmpty(s);
List ListCache = get(typeCode);
if (predicate.test(ListCache)) {
ListCache.forEach(item -> {
if (item.getItemCode().equals(String.valueOf(value))) {
dictName.set(item.getItemName());
return;
}
});
}
return dictName.get();
}
}
字典缓存抽象类AgentCacheAbstract
@Component
public abstract class AgentCacheAbstract {
private Integer expire = 4;
private Integer maximumSize = 100000;
private Integer initialCapacity = 1024;
private Cache cache;
public abstract void loadCache();
public AgentCacheAbstract(Integer expire, Integer maximumSize, Integer initialCapacity) {
Optional.ofNullable(expire).ifPresent(s -> {
this.expire = s;
});
Optional.ofNullable(maximumSize).ifPresent(s -> {
this.maximumSize = s;
});
Optional.ofNullable(initialCapacity).ifPresent(s -> {
this.initialCapacity = s;
});
cache = Optional.ofNullable(cache).orElse(Caffeine.newBuilder()
.expireAfterWrite(this.expire, TimeUnit.HOURS)
.initialCapacity(this.initialCapacity)
.maximumSize(this.maximumSize)
.build());
}
public V get(K key) {
Predicate predicate = s -> ObjectUtils.isEmpty(s);
V t = cache.getIfPresent(key);
if (predicate.test(t)) {
loadCache();
}
return cache.getIfPresent(key);
}
public void put(K key, V obj) {
cache.put(key, obj);
}
public Boolean containKey(K key) {
Predicate predicate = s -> ObjectUtils.isEmpty(s);
V t = cache.getIfPresent(key);
if (predicate.test(t)) {
return false;
}
return true;
}
}
五、字典表设计
字典项表设计如下
字典类表设计如下