1. 程式人生 > 其它 >基礎工具元件starter-datadict-nacos設計與實現

基礎工具元件starter-datadict-nacos設計與實現

技術標籤:輕蝸牛租房技術&業務架構系列模組提取專題分散式理論&實踐基礎工具元件spring starternacosdatadict

一、功能描述

基於nacos管理配置服務,這裡藉助starter機制與nacos一起配合管理k-v型別的資料,比如列舉資料,簡化應用接入k-v資料的複雜度,降低程式碼複雜度。

二、實現原理

通過配置dataid,groupid動態與nacos進行互動,並解析資料,通過介面訪問。

2.1 配置說明

nacos:
  #:資料字典型別的配置(k-v)
  datadict:
    dataIDArr: com.coderman.dict1,com.coderman.dict2,com.coderman.dict3
    groupIDArr: 0-g1,0-g2,0-g3,1-g4,1-g5,2-g1

這裡說明一下groupIDArr中的0-g1的資料結構的含義:0代表dataIDArr的第一個dataId,g1代表groupId.

2.2 程式碼解析

  1. 定義enable註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(NacosDataDictAutoConfiguration.class)
public @interface EnableNacosDataDict {
}
  1. 定義NacosDataProperties類
@Configuration
@ConfigurationProperties(prefix = "nacos.datadict")
public class NacosDataProperties {
    /**
     * nacos的dataID
     *
     */
    private String []  dataIDArr;
    /**
     * nacos的groupId
     * 與dataID進行對映時採用
     * 0:groupId0,
     * 1:groupId1格式,0,1則是nacos dataID 在dataIDArr
     * 的索引位置
     */
    private String []  groupIDArr;


    public String[] getDataIDArr() {
        return dataIDArr;
    }

    public void setDataIDArr(String[] dataIDArr) {
        this.dataIDArr = dataIDArr;
    }

    public String[] getGroupIDArr() {
        return groupIDArr;
    }

    public void setGroupIDArr(String[] groupIDArr) {
        this.groupIDArr = groupIDArr;
    }



    @Override
    public String toString() {
        return "NacosDataConfig{" +
                "dataIDArr=" + Arrays.toString(dataIDArr) +
                ", groupIDArr=" + Arrays.toString(groupIDArr) +
                '}';
    }
}
  1. 定義DataService介面
public interface DataService  {

    /**
     * 對配置的nacos dataID-group進行
     * 註冊變更監聽
     */
    void addListener() throws NacosException;



    /**
     * 根據nacos dataID groupid獲取 配置資料
     * @param dataID
     * @param groupID
     * @return map<k,v>
     */
    Map<String,StrictMap<String>> getDataMap(String dataID, String ... groupID);

    /**
     * 根據nacos dataID group id獲取 配置資料
     * @param dataID
     * @param groupID
     * @return
     */
    Map<String, StrictList<KVPair>> getDataKVList(String dataID, String ... groupID);


}
  1. 定義NacosDataDictAutoConfiguration類
@Configuration
@EnableConfigurationProperties(NacosDataProperties.class)
@ConditionalOnProperty(prefix = "nacos.datadict", havingValue = "true",name = "nacos.datadict")
public class NacosDataDictAutoConfiguration {
    @Autowired
    private  NacosDataProperties nacosDataProperties;

    /**
     * 對外暴露的資料服務
     * @return
     */
    @Bean(name = "nacosDataService")
    public NacosDataServiceImpl demoService(){
        return new NacosDataServiceImpl(nacosDataProperties.getDataIDArr(), nacosDataProperties.getGroupIDArr());
    }

}
  1. 定義DataService介面實現
@Service
public class NacosDataServiceImpl implements DataService{
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public NacosDataServiceImpl(){}

    public NacosDataServiceImpl(String [] dataIDArr, String [] groupIDArr){}
    /**
     * nacos的dataID
     *
     */
    private String []  dataIDArr;
    /**
     * nacos的groupId
     * 與dataID進行對映時採用
     * 0:groupId0,
     * 1:groupId1格式,0,1則是nacos dataID 在dataIDArr
     * 的索引位置
     */
    private String []  groupIDArr;


    @NacosInjected
    private ConfigService configService;

    private ConcurrentHashMap<String, Map<String,StrictMap<String>>> kvEnumMap = new ConcurrentHashMap();

    /**
     * k:nacos中的group_id
     * v:nacos中group_id 對應的資料ListKVPair
     */
    private ConcurrentHashMap<String, Map<String,StrictList<KVPair>>> kvPairMap = new ConcurrentHashMap();

    /**
     * k:nacos中的group_id
     * v:nacos中group_id 對應的資料ListKVPair
     */
    private ConcurrentHashMap<String, Map<String,List<KVParentPair<String,String,String>>>> kvParentPairMap = new ConcurrentHashMap();


    @Override
    public void addListener() throws NacosException {
        Map<String,String> dataIDMap = new HashMap<>();

        if(dataIDArr != null && dataIDArr.length > 0){
            for (int i = 0;i < dataIDArr.length; i++){
                dataIDMap.put(i+"", dataIDArr[i]);
            }
        }

        if(groupIDArr != null  && groupIDArr.length >0){
            logger.warn("nacos data not fund groupIdArr ,please check your config application-nacosdatadict.yml");
            return;
        }

        for (String str : groupIDArr){
            String [] strArr = str.split("-");
            String dataID = dataIDMap.get(strArr[0]);
            if(StringUtils.isNotEmpty(dataID)){
                convertNacosData(dataID,strArr[1]);
                addListener(dataID,strArr[1]);
            }
        }
    }

    /**
     * 針對不同的dataid,groupid新增listener
     * @param dataId
     * @param groupID
     * @throws NacosException
     */
    private void addListener(String dataId, final String groupID) throws NacosException {

        configService.addListener(dataId, groupID, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                exeConvert(configInfo,dataId,groupID);
            }

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

    @Override
    public Map<String,StrictMap<String>> getDataMap(String dataID, String ... groupIDArr) {
        Map<String,StrictMap<String>> dataMap = new HashMap<>();
        Map<String,StrictMap<String>> AllDataMap = kvEnumMap.get(dataID);
        for (String groupId : groupIDArr){
            StrictMap<String> strictMap = AllDataMap.get(groupId);
            if(strictMap != null){
                dataMap.put(groupId,strictMap);
            }
        }
        return dataMap;
    }

    @Override
    public Map<String,StrictList<KVPair>> getDataKVList(String dataID, String ... groupID) {
        Map<String,StrictList<KVPair>> map = new HashMap<>();
        Map<String,StrictList<KVPair>> AllDataMap = kvPairMap.get(dataID);
        for (String groupId : groupIDArr){
            StrictList<KVPair> list = AllDataMap.get(groupId);
            if(list != null){
                map.put(groupId,list);
            }
        }
        return map;
    }

    /**
     * 解析配置內容
     * @param dataID
     * @param groupID
     * @throws NacosException nacos異常
     */
    private void convertNacosData(String dataID, String groupID) throws NacosException {
        String content = configService.getConfig(dataID, groupID, 3000);
        exeConvert(content,dataID,groupID);
    }

    private void exeConvert(String content,String dataID,String groupID){
        String[] array = content.split("\n");
        StrictMap dataStrictMap = new StrictMap<>(array[0]);
        StrictList dataStrictList = new StrictList<>(array[0]);
        StrictList dataStrictKVPList = new StrictList<>(array[0]);

        for (int i = 1; i < array.length; i++) {
            String[] kvArr = array[i].replace(" ", "").replace("\r", "").split(",");
            if(kvArr.length == 3){
                dataStrictKVPList.add(new KVParentPair<>(kvArr[1], kvArr[0], kvArr[2]));
            }
            else if(kvArr.length == 2){
                dataStrictMap.put(kvArr[1], kvArr[0]);
                dataStrictList.add(new KVPair<>(kvArr[1], kvArr[0]));
            }
        }
        if(!dataStrictMap.isEmpty()){
            Map<String,StrictMap<String>> mapMap = new HashMap<>();
            mapMap.put(groupID, dataStrictMap);
            kvEnumMap.put(dataID,mapMap);
        }
        if(!dataStrictList.isEmpty()){
            Map<String,StrictList<KVPair>> map = new HashMap<>();
            map.put(dataID, dataStrictList);
            kvPairMap.put(groupID,map);
        }
        if(!dataStrictKVPList.isEmpty()){
            Map<String,List<KVParentPair<String,String,String>>> map = new HashMap<>();
            map.put(dataID, dataStrictKVPList);
            kvParentPairMap.put(groupID, map);
        }
        logger.info(array[0] + " has data changed,dataId={},groupID={}......................................",dataID,groupID);
    }
}
  1. 自定義list資料結構
/**
 * 自定義list
 * @author fcs
 * @param <E>
 */
public class StrictList<E> extends ArrayList {
    private final String name;

    public StrictList(String name, int initialCapacity) {
        super(initialCapacity);
        this.name = name;
    }

    public StrictList(String name) {
        super();
        this.name = name;
    }


    public StrictList(String name, Collection<? extends E> m) {
        super(m);
        this.name = name;
    }


    @Override
    public boolean add(Object value) {
        if (super.contains(value)) {
            throw new IllegalArgumentException(name + " already contains value for " + value);
        }
        return super.add(value);
    }
}
  1. 自定義map資料結構
/**
 * 自定義map
 * @param <V>  用一個name表示這個map存的是什麼資料
 */
public class StrictMap <V> extends HashMap<String, V> {
    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;

    public StrictMap(String name, int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        this.name = name;
    }

    public StrictMap(String name, int initialCapacity) {
        super(initialCapacity);
        this.name = name;
    }

    public StrictMap(String name) {
        super();
        this.name = name;
    }

    public StrictMap(String name, Map<String, ? extends V> m) {
        super(m);
        this.name = name;
    }

    @SuppressWarnings("unchecked")
    @Override
    public V put(String key, V value) {
        if (containsKey(key)) {
            throw new IllegalArgumentException(name + " already contains value for " + key);
        }

        return super.put(key, value);
    }

    @Override
    public V get(Object key) {
        V value = super.get(key);
        if (value == null) {
            //throw new IllegalArgumentException(name + " does not contain value for " + key);
            return null;
        }
        return value;
    }

    @Override
    public String toString(){
       return "";
    }

}