• web系统字典统一中文翻译问题


    几乎每个web系统都离不开各种状态码。订单新建,待支付,未支付,已支付,待发货。
    消息已读未读,任务待标记待审批已审批待流转已完成未完成。等等。
    复杂一点的,会有多级状态码。

    状态码超出3个的,一般都会纳入统一的字典管理。字典系统作为一个独立的微服务部署。
    使用Redis作为缓存。其它系统使用字典的时候只需接入该服务,调用相应接口即可。

    这本身没什么问题,也没什么可讲的。
    但在字典翻译的时候还是会出现一些五花八门的问题。

    问题

    数据库里存储的是字典的code码,但是前端展示的时候得是中文。字典翻译指的是,后端接口将数据库查询出来的code转为中文的过程。

    如果没有统一的管理,它就会有一些问题。

    比如有的字典表字段只需存一个值,有的呢,需要存储多个,通过,或者_进行分隔。有的呢,又是单独的表存储。

    那么,此时映射到对象就分成了String('1,2,3')或者集合List

    还有,List,这种属于嵌套对象,对象A里面有个集合,集合元素为B对象,B对象里面有字典需要翻译。

    有的呢,前端只需要展示中文,有的时候,前端既需要展示中文,同时也需要字典原始code码来做一些条件判断。

    有的时候呢,对于字典码String('1,2,3'),前端需要后端返回字典1,字典2,字典3,有的时候呢,前端更想后端返回字典1\n字典2\n字典3直接换行。

    还有的呢,多级字典码,数据库存的是全路径1_2_3,前端需要返回中文的全路径,比如一级_二级_三级,有的呢,只需要返回叶子节点,三级

    更有甚者,有的项目呢,比较乱,没有约定好。
    比如二级字典码101_202本身就带了_,当前端在传递多级字典全路径的时候,使用了_作分割符,本来应该返回101,101_202变成了101_101_202

    实现

    首先在接口代码当中自行查询字典服务,然后翻译,这样子肯定是不优雅的。

    更好的方法是,统一进行处理。

    一是在springboot序列化输出的时候进行,看具体的序列化框架。

    比如fastjson

    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter() {
                @Override
                public void write(Object object, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
                    try {
                        // todo
                    } catch (Exception e) {
                        log.error("response dict converter error", e);
                    }
                    super.write(object, type, contentType, outputMessage);
                }
            };
    

    又或者jackson

    @Configuration
    public class DictConfig {
        
        @Autowired
        private DictService dictService;
        
        @Bean
        public MappingJackson2HttpMessageConverter converter() {
            return new MappingJackson2HttpMessageConverter() {
    
                @Override
                protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
                    try {
                        // todo
                    } catch (Exception e) {
                        log.error("response dict converter error", e);
                    }
                    super.writeInternal(object, type, outputMessage);
                }
                
            };
        }
    }
    

    二是通过AOP切面统一进行

    定义一个字典注解

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DictNest {
    
        /**
         * 使用哪个分组, 即字典code
         * @return
         */
        String group();
    
        /**
         * 多级标签组装连接符,默认为'_'
         * @return
         */
        String joinStr() default "_";
    
        /***
         * 数据库里多级字典连接符,默认为'_'
         * @return
         */
        String splitStr() default "_";
    
        /**
         * 是否只返回叶子节点,默认为false即返回全路径,如果设置为true则只返回最后一级
         * 举个例子,字典值为二级字典102_206
         * 如果此值设置为false返回给前端 102中文_206中文
         * 如果此值设置为true返回给前端  106中文
         * @return
         */
        boolean onlyLeaf() default false;
        
        /**
         * 把字典值指定给此属性
         * @return
         */
        String field() default ""; 
    }
    

    对属性简单说明一下

    group()即字典code。

    splitStr()即数据库存放多个字典时的拼接符,在翻译的时候根据此对字符串进行分割。这里应与字符code的连接符区别开来。

    joinStr(),多个字典code在翻译成中文后,进行拼接的连接符。

    field(),它必须是当前对象的一个属性字段名,为空时中文替换掉code,不为空时,将翻译好的中文赋给此属性字段,原字段字典保留不变。

    onlyLeaf() 是否只返回叶子节点,默认为false即返回全路径,如果设置为true则只返回最后一级。

    定义一个切面

    
    import com.github.pagehelper.Page;
    import com.google.common.collect.Maps;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    
    import javax.annotation.Resource;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.*;
    import java.util.stream.Collectors;
    
    /**
     * @projectName: test
     * @package: com.test
     * @className: 
     * @author: nyp
     * @date: 2022/11/1 14:11
     * @version: 1.0
     */
    @Aspect
    @Component
    @Slf4j
    public class CommonAspect2 {
    
        @Resource
        private DictApi dictApi;
    
        @Pointcut("execution(public * com.test.controller.*.*(..))")
        public void LogAspect() {
        }
    
       
        @AfterReturning(value = "LogAspect()", returning = "returnValue")
        public void logResultVOInfo(JoinPoint joinPoint, Object returnValue) throws Exception {
            List data = new ArrayList<>();
    	// 这里自己改造一下,这里根据公司自定义返回代码来解析的
            if (returnValue instanceof PageResult) {
                Object data2 = ((PageResult) returnValue).getData();
                if (data2 instanceof Page) {
                    Page pageInfo = (Page) data2;
                    data = pageInfo.getResult();
                } else {          
    		// ingore
                }
            } else if (returnValue instanceof Result) {
                Object data2 = ((Result) returnValue).getData();
                data = new ArrayList<>();
                if (data2 instanceof ArrayList) {
                    List finalData = data;
                    ((ArrayList) data2).forEach(e -> finalData.add(e));
                } else {
                    data.add(data2);
                }
            }
            try {
                if (data == null) {
                    return;
                }
                for (Object e : data) { (e == null) {
                        break;
                    }
                    Field[] fields = e.getClass().getDeclaredFields();
                    Class clazz = e.getClass();
                    extracted(e, fields, clazz);
                }
            } catch (Exception exception) {
                log.error("", exception);
            }
        }
    
        private void extracted(Object e, Field[] fields, Class clazz) throws Exception {
            for (Field field : fields) {
                if (field.getGenericType().toString().contains("List")) {
                    Method method = getMethod(clazz, field.getName(), "get");
                    List subList = (List) method.invoke(e);
                    if (subList == null) {
                        continue;
                    }
                    List chineseList = new ArrayList<>();
                    for (Object obj : subList) {
                        if(obj instanceof String){
                            List subObjs = Arrays.stream(((String) obj).split(",")).collect(Collectors.toList());
    
                            for (String subObj : subObjs){
                                Annotation[] annotations = field.getDeclaredAnnotations();
                                for (Annotation a : annotations) {
                                    if(a instanceof DictNest){
                                        DictNest dictNest = field.getAnnotation(DictNest.class);
                                        String chinese = getDictMap(dictNest, subObj);
                                        chineseList.add(chinese);
                                    }
                                }
                            }
                        } else {
                            extracted(obj, obj.getClass().getDeclaredFields(), obj.getClass());
                        }
                    }
                    if(!CollectionUtils.isEmpty(chineseList)){
                        field.setAccessible(true);
                        field.set(e, chineseList);
                    }
                } else{
                    annotation(e, fields, field);
                }
            }
        }
    
        private void annotation(Object e, Field[] fields, Field field) throws IllegalAccessException {
            Annotation[] annotations = field.getDeclaredAnnotations();
            for (Annotation a : annotations) {
                // 字典翻译
                dict(e, fields, field, a);
            }
        }
    
        private void dict(Object e, Field[] fields, Field field, Annotation a) throws IllegalAccessException {
            if (a instanceof DictNest) {
                DictNest dictNest = field.getAnnotation(DictNest.class);
                field.setAccessible(true);
                Object value = field.get(e);
                if (value == null) {
                    return;
                }
                String chinese = getDictMap(dictNest, value.toString());
                if (chinese == null) {
                    return;
                }
                // 是否需要把值赋给指定的属性
                String otherFiled = dictNest.field();
                if (StringUtils.isBlank(otherFiled)) {
                    field.set(e, chinese);
                } else {
                    for (Field other : fields) {
                        if (other.getName().equals(otherFiled)) {
                            other.setAccessible(true);
                            other.set(e, chinese);
                        }
                    }
                }
            }
        }
    
        public String getDictMap(DictNest dictNest, String keys) {
            Map map = Maps.newHashMap();
    	// 这里应该换成自己的字典查询方式
            Map dict = dictApi.findByGroupCode(dictNest.group());
            if (dict == null) {
                return null;
            }
            for (Map.Entry entry : dict.entrySet()) {
                if (null != entry.getKey() && null != entry.getValue()) {
                    map.put(entry.getKey().toString(), entry.getValue().toString());
                }
            }
            List dictResult = new ArrayList<>();
            String[] keyList = keys.split(dictNest.splitStr());
            if (!dictNest.onlyLeaf()) {
                for (String key : keyList) {
                    dictResult.add(map.get(key));
                }
            } else {
                dictResult.add(map.get(keyList[keyList.length - 1]));
            }
            return dictResult.stream().collect(Collectors.joining(dictNest.joinStr()));
        }
    
        private Method getMethod(Class clazz, String fieldName, String type) throws NoSuchMethodException {
            Method method;
            String methodName = type + fieldName.substring(0, 1).toUpperCase(Locale.ROOT) + fieldName.substring(1);
            if ("set".equals(type)) {
                method = clazz.getDeclaredMethod(methodName, String.class);
            } else {
                method = clazz.getDeclaredMethod(methodName);
            }
            method.setAccessible(true);
            return method;
        }
    }
    
    
    

    大佬们自行重构一下代码。

    效果

    先定义一个测试对象,赋上测试值

    @Data
    public class TestVO {
    
        @DictNest(group = "test_state")
        private String connectionStatus = "1";
        @DictNest(group = "test_state")
        private List connectionStatusList = new ArrayList(){{
            add("1");
            add("2");
        }};
        @DictNest(group = "test_state", splitStr = ",")
        private String connectionStatusStr = "1,2";
    
        @DictNest(group = "test_state", joinStr = "\n" , splitStr = ",")
        private String connectionStatusStr2 = "1,2";
    
        @DictNest(group = "test_result", joinStr = "\n", field = "stateStr")
        private String state = "101_205";
    
        private String stateStr;
    
        @DictNest(group = "test_result", onlyLeaf = true)
        private String stateStr2 = "101_205";
    
        private List subs = new ArrayList(){{
            add(new Sub("1"));
            add(new Sub("2"));
        }};
    
        @Data
        @AllArgsConstructor
        public static class Sub{
            @DictNest(group = "lg_conn_state")
            private String connectionStatus;
        }
    }
    

    这里面有单个的字典,有String和List类型的多个注解,有多级字典,分别返回全路径和叶子节点。
    有同时返回字典code及中文。有嵌套对象,里面有字典。

    如果没加字典注解,原始返回值:

    {
    	"code":"200",
    	"data":{
    		"connectionStatus":"1",
    		"connectionStatusList":[
    			"1",
    			"2"
    		],
    		"connectionStatusStr":"1,2",
    		"connectionStatusStr2":"1,2",
    		"state":"101_205",
    		"stateStr":null,
    		"stateStr2":"101_205",
    		"subs":[
    			{
    				"connectionStatus":"1"
    			},
    			{
    				"connectionStatus":"2"
    			}
    		]
    	},
    	"msg":"success",
    	"success":true
    }
    

    加了字典注解最终的效果:

    {
    	"code":"200",
    	"data":{
    		"connectionStatus":"完成",
    		"connectionStatusList":[
    			"完成",
    			"未完成"
    		],
    		"connectionStatusStr":"完成_未完成",
    		"connectionStatusStr2":"完成\n未完成",
    		"state":"101_205",
    		"stateStr":"一级字典名称\n二级字典名称",
    		"stateStr2":"二级字典名称",
    		"subs":[
    			{
    				"connectionStatus":"完成"
    			},
    			{
    				"connectionStatus":"未完成"
    			}
    		]
    	},
    	"msg":"success",
    	"success":true
    }
    
  • 相关阅读:
    程序员的护城河
    vscode初次远程连接服务器报错解决
    【python自动化应用】借助ChatGPT与Python轻松实现办公自动化 —— AIC松鼠活动第九期
    进销存系统和ERP系统怎么选?有什么区别?
    用企业微信如何做私域运营?
    unity C#设置文件为不可见
    html笔记
    .NET7 gRPC JSON转码+OpenAPI
    奖补50万,2022年武汉市推动工业互联网平台化发展专项资金申报条件以及奖励补贴标准
    centos8.2 OS日志重定向到串口设备ttyS0,通过bmc查看调试
  • 原文地址:https://www.cnblogs.com/eryuan/p/17579856.html