基础工具组件starter-datajson-nacos设计与实现

一、功能描述

基于nacos管理配置服务,这里借助starter机制与nacos,mysql一起配合管理非核心业务的配置类数据记录,基于json存储,简化业务配置类数据的管理。

二、实现原理

通过自定义注解将实体数据与nacos配置数据进行绑定动态与nacos进行交互,并解析数据,通过接口访问。

2.1 配置说明

nacos:
  #:存储于数据库中的配置数据
  datajson:
    #:扫描配置的枚举entity模型
    entitypath: com.coderman.common.starter.entity
    #:如果数据库里有配置表,则通过这个配置开启回调接口,见示例:com.coderman.common.starter.service.ConfigCallBackServiceImpl
    #:进行回调之后则自动进行配置数据注册,后续直接在nacos中进行配置
    usecallback: true

2.2 代码实现

  1. 配置实体属性注解
/**
 * 标注entity的属性注解
 * @author fanchunshuai
 */
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ConfigField {
    /**
     * 字段描述
     * @return
     */
    String desc() default "";

    /**
     * 是否作为查询key
     * @return
     */
    boolean queryKey() default false;
}
  1. 配置实体注解
/**
 * 标注entity注解
 * @author fanchunshuai
 */
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NacosConfig {
    /**
     * 对应nacos的dataId
     * @return
     */
    String dataId() default "";

    /**
     * 对应nacos的groupId
     * @return
     */
    String groupId() default "";

    /**
     * 对应nacos的描述
     * @return
     */
    String desc() default "";

}
  1. 配置属性注解对应的bean
public class ConfigFieldBean {
    /**
     * 属性名
     */
    private String fieldName;

    /**
     * 字段描述
     * @return
     */
    private String desc;

    /**
     * 是否作为查询key
     * @return
     */
    private boolean queryKey;

    public String getFieldName() {
        return fieldName;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public boolean isQueryKey() {
        return queryKey;
    }

    public void setQueryKey(boolean queryKey) {
        this.queryKey = queryKey;
    }
}
  1. 配置实体注解对应的bean
public class NacosConfigBean {
    /**
     * 对应nacos的dataId
     * @return
     */
    private String dataId;

    /**
     * 对应nacos的groupId
     * @return
     */
    private String groupId;

    /**
     * 对应nacos的描述
     * @return
     */
    private String desc;

    /**
     * 简单类名
     */
    private String classSimpleName;

    /**
     * 模型类class
     */
    private Class<?> clazz;

    public Class<?> getClazz() {
        return clazz;
    }

    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }

    private List<ConfigFieldBean> configFieldBeanList;


    public String getClassSimpleName() {
        return classSimpleName;
    }

    public void setClassSimpleName(String classSimpleName) {
        this.classSimpleName = classSimpleName;
    }

    public String getDataId() {
        return dataId;
    }

    public void setDataId(String dataId) {
        this.dataId = dataId;
    }

    public String getGroupId() {
        return groupId;
    }

    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public List<ConfigFieldBean> getConfigFieldBeanList() {
        return configFieldBeanList;
    }

    public void setConfigFieldBeanList(List<ConfigFieldBean> configFieldBeanList) {
        this.configFieldBeanList = configFieldBeanList;
    }
}
  1. 配置类
@Configuration
@ConfigurationProperties(prefix = "nacos.datajson")
public class NacosDataJsonProperties {
    /**
     * 指定扫描类路径减少扫描时间
     */
    @Value("{entitypath}")
    private String entitypath;

    /**
     * 是否需要进行回调兼容之前的配置
     */
    @Value("{usecallback}")
    private String usecallback;

    public String getEntitypath() {
        return entitypath;
    }

    public void setEntitypath(String entitypath) {
        this.entitypath = entitypath;
    }

    public String getUsecallback() {
        return usecallback;
    }

    public void setUsecallback(String usecallback) {
        this.usecallback = usecallback;
    }
}
  1. 启动listener监听器
@Order(value = 1000)
@Component
public class NacosDataJsonConfigListener implements ApplicationListener<ApplicationStartedEvent> {
    private Logger logger = LoggerFactory.getLogger(NacosDataJsonConfigListener.class);

    @Autowired
    private DataJsonHelper dataJsonHelper;

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        try {
            logger.info("应用启动成功,准备初始化业务配置数据:DataJsonConfig.......................");
            dataJsonHelper.init();
            logger.info("应用启动成功,初始化业务配置数据成功:DataJsonConfig.......................");

        } catch (Exception e) {
            logger.error("初始化远程配置失败",e);
        }


    }
}
  1. 定义DataJsonHelper处理类
@Service
public class DataJsonHelper {

    @Autowired
    private MetaDataService metaDataService;
    private static ExpressionParser expressionParser = new SpelExpressionParser();

    @Autowired
    private NacosDataJsonProperties nacosDataJsonProperties;

    private Logger logger = LoggerFactory.getLogger(DataJsonHelper.class);

    private static ConcurrentHashMap<String,List> configDataMap = new ConcurrentHashMap<>();

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    /**
     * 配置json初始化
     * @throws Exception
     */
    public synchronized void init() throws Exception{
        ConfigService configService = getConfigService();
        metaDataService.scanNacosJSONEntity();
        List<NacosConfigBean> nacosConfigBeanList = metaDataService.getNacosConfigBeanList();
        Map<String,List> initConfigMap = new HashMap<>();
        //已有数据进行接入
        if(nacosDataJsonProperties.getUsecallback().equals("true")){
            ConfigCallBackService configCallBackService = (ConfigCallBackService)SpringContextUtil.getBean(ConfigCallBackService.class);
            initConfigMap = configCallBackService.getAllConfigList();
        }
        for (NacosConfigBean nacosConfigBean : nacosConfigBeanList){
            regist(nacosConfigBean,initConfigMap,configService);
            addListener(nacosConfigBean.getDataId(),nacosConfigBean.getGroupId(),configService);
        }
    }

    /**
     * 向nacos注册数据
     * @param nacosConfigBean
     * @param configMap
     * @throws NacosException
     */
    private void regist(NacosConfigBean nacosConfigBean, Map<String, List> configMap,ConfigService configService) throws NacosException {
        if(configMap == null || configMap.isEmpty()){
            configMap = new HashMap<>();
        }
        String dataId = nacosConfigBean.getDataId();
        String groupId = nacosConfigBean.getGroupId();
        logger.info("dataId = {},groupId = {}",dataId,groupId);



        String content = configService.getConfig(dataId, groupId, 3000);
        if(StringUtils.isEmpty(content)){
            String metaDataJsonStr = geteMetaDataJson(nacosConfigBean)+"\n";
            StringBuilder builder = new StringBuilder(metaDataJsonStr);
            List configList = configMap.get(nacosConfigBean.getClassSimpleName());
            if(CollectionUtils.isNotEmpty(configList)){
                for (Object obj : configList){
                    builder.append(JSON.toJSONString(obj)+"\n");
                }
            }
            //发布配置到nacos
            boolean b = configService.publishConfig(dataId,groupId,builder.toString());
            logger.info("发布配置到nacos结果:dataId={},groupId={},result={}",dataId,groupId,b);
            return;
        }
        //更新远程数据到本地
        updateConfig(content, nacosConfigBean);
        logger.info("获取nacos数据并存储本地,dataId={},groupId={},",dataId,groupId);

    }


    /**
     * 获取注解元数据-json
     * @param nacosConfigBean
     * @return
     */
    private String geteMetaDataJson(NacosConfigBean nacosConfigBean){
      return JSON.toJSONString(nacosConfigBean);
    }


    /**
     * 针对不同的dataid,groupid添加listener
     *
     * @param dataId
     * @param groupID
     * @throws NacosException
     */
    private void addListener(String dataId, final String groupID,ConfigService configService) throws NacosException {

        configService.addListener(dataId, groupID, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                logger.info("收到远程配置更新信息,dataId={},groupId={},准备更新",dataId,groupID);
                exeConvert(configInfo, dataId, groupID);
                logger.info("收到远程配置更新信息,dataId={},groupId={},更新完成",dataId,groupID);
            }

            @Override
            public Executor getExecutor() {
                return null;
            }
        });
    }

    /**
     * 进行远程数据更新
     * @param content
     * @param dataID
     * @param groupID
     */
    private void exeConvert(String content, String dataID, String groupID) {
        List<NacosConfigBean> nacosConfigBeanList = metaDataService.getNacosConfigBeanList();
        NacosConfigBean  nacosConfigBean = nacosConfigBeanList.stream()
                .filter(o->o.getDataId().equals(dataID) && o.getGroupId().equals(groupID))
                .collect(Collectors.toList()).get(0);

        updateConfig(content,nacosConfigBean);
    }

    /**
     * 更新远程配置到本地
     * @param content
     * @param nacosConfigBean
     */
    private void updateConfig(String content, NacosConfigBean nacosConfigBean){
        String[] array = content.split("\n");
        List configList = Lists.newArrayListWithCapacity(array.length);
        for (int i = 1;i < array.length;i ++ ){
            Object object = JSON.parseObject(array[i],nacosConfigBean.getClazz());
            configList.add(object);
        }
        configDataMap.put(nacosConfigBean.getClassSimpleName(),configList);
    }

    /**
     * 通过原生Java方式获取ConfigService
     * @return
     * @throws NacosException
     */
    private ConfigService getConfigService() throws NacosException {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
        return NacosFactory.createConfigService(properties);
    }

    /**
     * 通过entity类名获取对应配置数据列表,获取多条记录
     * @param simpleClassName
     * @return
     */
    public List getByClassName(String simpleClassName){
        return configDataMap.get(simpleClassName);
    }

    /**
     * 通过查询key获取对应的单条记录
     * @param o
     * @return
     */
    public Object getByQuery(Object o){
        logger.info("查询单条记录,queryObj={}",JSON.toJSONString(o));
        if(o == null){
            return null;
        }
        List<NacosConfigBean> nacosConfigBeanList = metaDataService.getNacosConfigBeanList();
        AtomicReference<NacosConfigBean> nacosConfigBeanReference = new AtomicReference<>();
        nacosConfigBeanList.forEach(configBean -> {
            configBean.getClassSimpleName().equals(o.getClass().getSimpleName());
            nacosConfigBeanReference.set(configBean);
        });
        if(nacosConfigBeanReference.get() == null){
            return null;
        }
        NacosConfigBean nacosConfigBean = nacosConfigBeanReference.get();
        List<ConfigFieldBean> configFieldBeanList = nacosConfigBean.getConfigFieldBeanList().stream().filter(configFieldBean -> configFieldBean.isQueryKey()==true).collect(Collectors.toList());

        List list = configDataMap.get(o.getClass().getSimpleName());
        AtomicReference<Object> result = new AtomicReference<>();
        list.forEach(obj->{
            AtomicBoolean b = new AtomicBoolean(true);
            configFieldBeanList.forEach(configFieldBean -> {
                Object value1 = expressionParser.parseExpression(configFieldBean.getFieldName()).getValue(obj);
                Object value2 = expressionParser.parseExpression(configFieldBean.getFieldName()).getValue(o);
                if(!value1.equals(value2)){
                    b.set(false);
                }
            });
            if(b.get()){
                result.set(obj);
            }
        });
        return result.get();
    }
}
  1. 定义DataJsonService接口
public interface DataJsonService<T> {

    /**
     * 根据配置entity获取配置数据列表
     * @param clazz
     * @return
     */
    List<T> getDataConfigList(Class<T> clazz);

    /**
     * 根据查询的字段判断是否存在指定配置数据
     * @param t
     * @return
     */
    T  getByQueryKey(T t);

}
  1. 定义回调接口
public interface ConfigCallBackService {
    /**
     * 获取项目工程中可以用来作为nacos配置服务的配置数据
     * @return
     */
    public Map<String, List> getAllConfigList();
}
  1. 定义源数据处理服务
/**
 * Description:
 * date: 2020/9/29 9:04 上午
 *
 * @author fanchunshuai
 * @version 1.0.0
 * @since JDK 1.8
 */
@Service
@Configuration
public class MetaDataService {
    /**
     * 指定扫描类路径减少扫描时间
     */
    @Value("{nacos.datajson.entitypath}")
    private String entitypath;

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;
    @Autowired
    private NacosDataJsonProperties nacosDataJsonProperties;

    private static List<NacosConfigBean> nacosConfigBeanList = new ArrayList<>();

    public List<NacosConfigBean> getNacosConfigBeanList() {
        return nacosConfigBeanList;
    }


    /**
     * 根据配置的类路径获取配置的entity元数据
     *
     * @return
     */
    public synchronized void scanNacosJSONEntity() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        List<Class<?>> classList = ClassUtils.getClass(nacosDataJsonProperties.getEntitypath(), true);
        List<NacosConfigBean> list = new ArrayList<>();
        for (Class clazz : classList) {
            NacosConfig nacosConfig = AnnotationUtils.findAnnotation( Class.forName(clazz.getName()),NacosConfig.class);
            if(nacosConfig == null){
                continue;
            }

            NacosConfigBean nacosConfigBean = getBean(clazz,nacosConfig);
            nacosConfigBean.setClazz(clazz);
            List<ConfigFieldBean> configFieldBeanList = getConfigFieldBeanList(clazz);
            nacosConfigBean.setConfigFieldBeanList(configFieldBeanList);
            list.add(nacosConfigBean);
        }
        if(CollectionUtils.isEmpty(nacosConfigBeanList)){
            this.getNacosConfigBeanList().addAll(list);
        }
    }

    /**
     * 获取NacosConfigBean
     * @param clazz
     * @return
     */
    private NacosConfigBean getBean(Class<?> clazz,NacosConfig nacosConfig){

        NacosConfigBean nacosConfigBean = new NacosConfigBean();
        String dataId = nacosConfig.dataId();
        //使用默认方式构造dataId
        if (StringUtils.isEmpty(dataId)) {
            dataId = clazz.getName();
        }
        nacosConfigBean.setDataId(dataId);

        String groupId = nacosConfig.groupId();
        //使用默认方式构造groupId
        if (StringUtils.isEmpty(groupId)) {
            groupId = nacosDiscoveryProperties.getService() + "-" +clazz.getSimpleName();
        }
        nacosConfigBean.setGroupId(groupId);

        String desc = nacosConfig.desc();
        if (StringUtils.isEmpty(desc)) {
            desc = "NacosJSON configService:"+clazz.getSimpleName();
        }
        nacosConfigBean.setDesc(desc);
        nacosConfigBean.setClassSimpleName(clazz.getSimpleName());
        return nacosConfigBean;
    }

    /**
     * 获取getConfigFieldBeanList
     * @param clazz
     * @return
     */
    private List<ConfigFieldBean> getConfigFieldBeanList(Class<?> clazz) throws ClassNotFoundException {
        Field [] fieldArr = Class.forName(clazz.getName()).getDeclaredFields();
        List<ConfigFieldBean> list = new ArrayList<>();

        for (Field field : fieldArr){
            if(!field.isAnnotationPresent(ConfigField.class)){
                continue;
            }
            ConfigField configField = field.getAnnotation(ConfigField.class);
            ConfigFieldBean configFieldBean = new ConfigFieldBean();
            configFieldBean.setFieldName(field.getName());
            configFieldBean.setDesc(configField.desc());
            configFieldBean.setQueryKey(configField.queryKey());
            list.add(configFieldBean);
        }
        return list;
    }

}
  1. 定义DataJsonService实现
@Service
public class DataJsonServiceImpl implements DataJsonService {
    @Autowired
    private DataJsonHelper dataJsonHelper;

    private static  ExpressionParser expressionParser = new SpelExpressionParser();

    @Override
    public List getDataConfigList(Class clazz) {
        return dataJsonHelper.getByClassName(clazz.getSimpleName());
    }

    @Override
    public Object getByQueryKey(Object o) {
        return dataJsonHelper.getByQuery(o);
    }
}
上一篇:umask


下一篇:Nacos-配置中心