字典注解

类注解

import lombok.NonNull;
import java.lang.annotation.*;

/**
 * 数据字典类注解
 *
 * @author huxiang
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DictClass {

    /**
     * js字典文件名称 ,不填生成公用js 字典文件
     */
    String dictJsonFile() default "dict";

    /**
     * 字典key
     */
    @NonNull
    String dictKey();
}

属性注解

import lombok.NonNull;

import java.lang.annotation.*;

/**
 * 数据字典属性注解
 *
 * @author huxiang
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DictField {

    /**
     * 字典值样式 生成js用
     */
    String cssClass() default "";

    /**
     * 字典值对应描述-中文
     */
    @NonNull
    String dictLabelZH();

    /**
     * 字典值对应描述-英文
     */
    @NonNull
    String dictLabelEN();

}

属性拓展注解

import lombok.NonNull;

import java.lang.annotation.*;

/**
 * 数据字典拓展属性注解---用于标准数据字典外需要额外生成key-value
 *
 * @author huxiang
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DictFieldEx {

    /**
     * 拓展属性名
     */
    @NonNull
    String[] propKey();

    /**
     * 拓展属性值
     */
    @NonNull
    String[] propValue();
}

工具类

import com.paratera.console.dict.annotation.DictClass;
import com.paratera.console.dict.annotation.DictField;
import com.paratera.console.dict.annotation.DictFieldEx;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import .Resource;
import .support.PathMatchingResourcePatternResolver;
import org.springframework.util.StringUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 数据字典工具类
 *
 * @author huxiang
 */
@Slf4j
public class DictUtils {

    /**
     * 缓存后台数据字典数据
     */
    private static ConcurrentHashMap<String, String> dictMap = new ConcurrentHashMap<String, String>();

    /**
     * 空格常量
     */
    private static String SPACE1 = "  ";
    private static String SPACE2 = "    ";
    private static String SPACE3 = "      ";

    /**
     * 数据字典常量类路径
     */
    private static String constansFilePath = "classpath:com/paratera/console/dict/constants/*.class";

    /**
     * 数据字典 全限定类名前缀
     */
    private static String constansClassPrefix = "com.paratera.console.dict.constants.";

    /**
     * 生成前端json 字典文件路径
     */
    private static String jsonDirUrl = DictUtils.class.getResource("/").getPath() + "dict/";

    /**
     * 初始化数据字典数据到缓存(可以在引入项目启动时加载)
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static void initDictMapCache() throws IOException, ClassNotFoundException, NoSuchMethodException,
        IllegalAccessException, InvocationTargetException, InstantiationException {
        PathMatchingResourcePatternResolver util = new PathMatchingResourcePatternResolver();
        Resource[] files = util.getResources(constansFilePath);
        for (Resource resource : files) {
            String className = constansClassPrefix
                + resource.getFilename().substring(0, resource.getFilename().lastIndexOf("."));
            Class<?> clazz = Class.forName(className);
            if (clazz.isAnnotationPresent(DictClass.class)) {
                DictClass dictClass = clazz.getAnnotation(DictClass.class);
                String dictKey = dictClass.dictKey();
                Field[] fields = clazz.getDeclaredFields();
                Object instance = clazz.getDeclaredConstructor().newInstance();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(DictField.class)) {
                        DictField dictField = field.getAnnotation(DictField.class);
                        dictMap.put(dictKey + ":" + field.get(instance) + ":zh", dictField.dictLabelZH());
                        dictMap.put(dictKey + ":" + field.get(instance) + ":en", dictField.dictLabelEN());
                    }
                }
            }
        }
    }

    /**
     * 从缓存中获取数据字典值对应的text
     *
     * @param dictKey
     * @param dictValue
     * @param language
     * @return
     */
    public static String getDictText(String dictKey, String dictValue, String language) {
        return dictMap.get(dictKey + ":" + dictValue + ":" + language);
    }

    /**
     * 生成json字典文件,可用于前端使用,统一前后端字典值
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static void genJsonFile() throws IOException, ClassNotFoundException, InstantiationException,
        IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        PathMatchingResourcePatternResolver util = new PathMatchingResourcePatternResolver();
        Resource[] files = util.getResources(constansFilePath);
        StringBuffer sb = new StringBuffer();
        sb.append("{ \n");
        for (int j = 0; j < files.length; j++) {
            Resource resource = files[j];
            String className = constansClassPrefix
                + resource.getFilename().substring(0, resource.getFilename().lastIndexOf("."));
            Class<?> clazz = Class.forName(className);
            if (clazz.isAnnotationPresent(DictClass.class)) {
                DictClass dictClass = clazz.getAnnotation(DictClass.class);
                String jsonFile = dictClass.dictJsonFile();
                // 私有json输出流
                if (jsonFile != null && !jsonFile.equals("dict")) {
                    StringBuffer ss = new StringBuffer();
                    ss.append("{ \n");
                    // 这个地方第二个参数设置为0,主要是区别公用字典的逻辑
                    settingjsonInfo(ss, 0, clazz, dictClass, jsonFile);
                    ss.append("\n}");
                    OutputStreamWriter osw = new OutputStreamWriter(
                        new FileOutputStream(new File(jsonDirUrl + jsonFile + ".json")), "utf-8");
                    osw.append(sb.toString());
                    osw.close();
                } else {
                    settingjsonInfo(sb, j, clazz, dictClass, jsonFile);
                    if (j == files.length - 1) {
                        sb.append("\n");
                    }
                }
            }
        }
        sb.append("\n}");
        // 公用dict.json输出流
        OutputStreamWriter outStreamWriter = new OutputStreamWriter(
            new FileOutputStream(new File(jsonDirUrl + "dict.json")), "utf-8");
        outStreamWriter.append(sb.toString());
        outStreamWriter.close();
        log.debug("json文件已生成,路径为:" + jsonDirUrl + "dict.json");
    }

    /**
     * 设置json内容
     *
     * @param sb
     * @param j
     * @param clazz
     * @param dictClass
     * @param jsonFile
     * @throws IOException
     */
    private static void settingjsonInfo(StringBuffer sb, int j, Class<?> clazz, DictClass dictClass, String jsonFile)
        throws IOException, InstantiationException, IllegalAccessException, NoSuchMethodException,
        InvocationTargetException {
        File file = new File(jsonDirUrl + jsonFile + ".json");
        if (file.exists()) {
            log.debug("json文件:" + jsonFile + ".json存在,开始覆盖!");
        } else {
            log.debug("json文件:" + jsonFile + ".json不存在,开始创建!");
            File fileParent = new File(jsonDirUrl);
            if (!fileParent.exists()) {
                fileParent.mkdir();
            }
            file.createNewFile();
        }
        if (j != 0) {
            sb.append(",\n");
        }

        // 添加常量配置
        Field[] fields = clazz.getDeclaredFields();
        Object instance = clazz.getDeclaredConstructor().newInstance();
        boolean dictFieldGenerated = false;
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            if (field.isAnnotationPresent(DictField.class)) {

                if (dictFieldGenerated) {
                    sb.append(",");
                } else {
                    sb.append(SPACE1 + "\"" + dictClass.dictKey() + "\": [");
                }
                sb.append("{\n");
                if (field.getGenericType().toString().equals("class java.lang.Integer")) {
                    sb.append(SPACE2 + "\"dictValue\":" + field.get(instance) + ",\n");
                } else {
                    sb.append(SPACE2 + "\"dictValue\":\"" + field.get(instance) + "\",\n");
                }
                DictField dictField = field.getAnnotation(DictField.class);
                sb.append(SPACE2 + "\"dictClass\":\"" + dictField.cssClass() + "\",\n");
                sb.append(SPACE2 + "\"dictLabel\": {\n");
                sb.append(SPACE3 + "\"zh\": \"" + dictField.dictLabelZH() + "\",\n");
                sb.append(SPACE3 + "\"en\": \"" + dictField.dictLabelEN() + "\"\n");
                //拼接拓展属性
                settingDictFieldEx(sb, field);
                if (!dictFieldGenerated) {
                    dictFieldGenerated = true;
                }

            }
        }
        if (dictFieldGenerated) {
            sb.append("]");
        }
    }

    /**
     * 根据className生成数据字典json数据
     *
     * @param className
     * @return
     */
    public static String getJsonDictByClass(String className) {
        //如果缓存有,从缓存取,没有,解析后缓存
        if (dictMap.containsKey(className)) {
            return dictMap.get(className);
        }
        Class<?> clazz = null;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error("反射获取Class失败:" + e.getMessage());
        }
        StringBuffer sb = new StringBuffer();
        if (clazz.isAnnotationPresent(DictClass.class)) {
            DictClass dictClass = clazz.getAnnotation(DictClass.class);
            // 添加常量配置
            Field[] fields = clazz.getDeclaredFields();
            Object instance = null;
            try {
                instance = clazz.getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                log.error("反射获取对象实例失败:" + e.getMessage());
            }
            boolean dictFieldGenerated = false;
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                if (field.isAnnotationPresent(DictField.class)) {
                    if (dictFieldGenerated) {
                        sb.append(",");
                    } else {
                        sb.append("[");
                    }
                    sb.append("{\n");
                    try {
                        if (field.getGenericType().toString().equals("class java.lang.Integer")) {
                            sb.append(SPACE2 + "\"dictValue\":" + field.get(instance) + ",\n");
                        } else {
                            sb.append(SPACE2 + "\"dictValue\":\"" + field.get(instance) + "\",\n");
                        }

                    } catch (IllegalAccessException e) {
                        log.error("反射获取对象实例字段值失败:" + e.getMessage());
                    }
                    sb.append(SPACE2 + "\"dictLabel\": {\n");
                    DictField dictField = field.getAnnotation(DictField.class);
                    sb.append(SPACE3 + "\"zh\": \"" + dictField.dictLabelZH() + "\",\n");
                    sb.append(SPACE3 + "\"en\": \"" + dictField.dictLabelEN() + "\"\n");
                    //拼接拓展属性
                    settingDictFieldEx(sb, field);
                    if (!dictFieldGenerated) {
                        dictFieldGenerated = true;
                    }
                }
            }
            sb.append("]");
        }
        //缓存
        dictMap.put(className, sb.toString());
        return sb.toString();
    }

    /**
     * 拓展注解处理@DictFieldEx
     *
     * @param sb
     * @param field
     */
    private static void settingDictFieldEx(StringBuffer sb, Field field) {
        DictFieldEx dictFieldEx = field.getAnnotation(DictFieldEx.class);
        if (dictFieldEx != null) {
            sb.append(SPACE2 + "},\n");
            String[] keys = dictFieldEx.propKey();
            String[] values = dictFieldEx.propValue();
            for (int k = 0; k < keys.length; k++) {
                String label = null;
                //如果value是json字符串,则不再加双引号包裹
                boolean flag = values[k].trim().startsWith("{") && values[k].trim().endsWith("}");
                if (flag) {
                    label = SPACE2 + "\"" + keys[k] + "\":" + values[k];
                } else {
                    label = SPACE2 + "\"" + keys[k] + "\":" + "\"" + values[k];
                    label = label + "\"";
                }
                if (k == keys.length - 1) {
                    sb.append(label);
                    continue;
                }
                sb.append(label).append(",\n");
            }
        } else {
            sb.append(SPACE2 + "}\n");
        }
        sb.append(SPACE1 + "}");
    }
}

字典常量类配置示例

import com.paratera.console.dict.annotation.DictClass;
import com.paratera.console.dict.annotation.DictField;
import com.paratera.console.dict.annotation.DictFieldEx;

/**
 * 消息类型字典常量类
 * @date 2021-09-29 10:18:45
 */
@DictClass(dictKey = AlarmMessageTypeConstants.DICT_KEY)
public class AlarmMessageTypeConstants {

    public static final String DICT_KEY = "alarmMessageType";

    @DictField(dictLabelZH = "存储使用量预警通知", dictLabelEN = "Storage")
    @DictFieldEx(propKey = {"labelType", "thresholdType", "onOff", "thresholdProps"},
        propValue = {AlarmMessageCategoriesConstants.STORAGE, "1", "1", """
            {
                    "unit":"int",
                    "unitName":"百分比",
                    "descZH":"存储使用量预警阈值(最小70%, 最大100%)",
                    "descEN":"Storage usage alarm threshold(min 70%, max 100%)",
                    "defaultValue":80,
                    "minValue":70,
                    "maxValue":100,
                    "step":1
                }
            """})
    public static final Integer STORAGE = 1;

    @DictField(dictLabelZH = "作业长时间运行通知", dictLabelEN = "Job running overtime")
    @DictFieldEx(propKey = {"labelType", "thresholdType", "onOff", "thresholdProps"},
        propValue = {AlarmMessageCategoriesConstants.JOB, "1", "1", """
            {
                    "unit":"float",
                    "unitName":"天",
                    "descZH":"作业长时间运行预警阈值(天)",
                    "descEN":"Warning threshold for long-time operation of job (days)",
                    "defaultValue":1,
                    "minValue":0.5,
                    "maxValue":14,
                    "step":0.5
                }
            """})
    public static final Integer LONG_JOB = 2;

    @DictField(dictLabelZH = "账户余额预警通知", dictLabelEN = "Balance not enough")
    @DictFieldEx(propKey = {"labelType", "thresholdType", "onOff", "thresholdProps"},
        propValue = {AlarmMessageCategoriesConstants.FEE, "1", "1", """
            {
                    "unit":"int",
                    "unitName":"元",
                    "descZH":"账户余额预警阈值(元)",
                    "descEN":"Insufficient balance alarm threshold (元)",
                    "defaultValue":200,
                    "minValue":1,
                    "maxValue":50000,
                    "step":1
                }
            """})
    public static final Integer AMOUNT_AVAILABLE = 3;
}

JSON文件格式

{
	"alarmMessageType": [{
	    "dictValue":1,
	    "dictClass":"",
	    "dictLabel": {
	      "zh": "存储使用量预警通知",
	      "en": "Storage"
	    },
	    "labelType":"storage",
	    "thresholdType":"1",
	    "onOff":"1",
	    "thresholdProps":{
	        "unit":"int",
	        "unitName":"百分比",
	        "descZH":"存储使用量预警阈值(最小70%, 最大100%)",
	        "descEN":"Storage usage alarm threshold(min 70%, max 100%)",
	        "defaultValue":80,
	        "minValue":70,
	        "maxValue":100,
	        "step":1
	    }
	  },{
	    "dictValue":2,
	    "dictClass":"",
	    "dictLabel": {
	      "zh": "作业长时间运行通知",
	      "en": "Job running overtime"
	    },
	    "labelType":"job",
	    "thresholdType":"1",
	    "onOff":"1",
	    "thresholdProps":{
	        "unit":"float",
	        "unitName":"天",
	        "descZH":"作业长时间运行预警阈值(天)",
	        "descEN":"Warning threshold for long-time operation of job (days)",
	        "defaultValue":1,
	        "minValue":0.5,
	        "maxValue":14,
	        "step":0.5
	    }
	  },{
	    "dictValue":3,
	    "dictClass":"",
	    "dictLabel": {
	      "zh": "账户余额预警通知",
	      "en": "Balance not enough"
	    },
	    "labelType":"fee",
	    "thresholdType":"1",
	    "onOff":"1",
	    "thresholdProps":{
	        "unit":"int",
	        "unitName":"元",
	        "descZH":"账户余额预警阈值(元)",
	        "descEN":"Insufficient balance alarm threshold (元)",
	        "defaultValue":200,
	        "minValue":1,
	        "maxValue":50000,
	        "step":1
	    }
	  }]
}