一種在【微服務體系】下的【引數配置神器】

可少發表於2020-11-20

最近工作上遇到了一類需求,就是要對接大量外部系統,而需要對接的外部系統又存在各種環境,比如開發,測試,正式環境,他們的配置不盡相同。

一般情況下,我們可以通過配置application-dev.yml、application-test.yml、application-prod.yml等springboot配置檔案來配置這些引數。

但是由於我們對接外部系統數量極多,而且不停的在增加,所以如果考慮將外部系統的地址,賬戶密碼配置進yml檔案,會導致yml檔案爆炸且不太好管控,所以需要思考一個更快捷的辦法。

中間我們考慮將這樣的配置寫進資料庫,但是幾個環境的資料庫都要分別配置,也挺麻煩的。

經過思考,我們想出了一個程式碼配置的辦法,這種辦法可以讓程式設計師在程式碼級別自由發揮,不受制於運維,僅供大家參考。

1.首先增加一個介面,定義若干環境

/**
 * 程式碼配置引數神器
 *
 * @author xiaokek
 * @since 2020年5月13日 下午4:01:47
 */
public interface CoreConfig {
    default Map<String, String> getLocal(){ return Collections.EMPTY_MAP;};
    default Map<String, String> getTest(){ return getLocal();};
    default Map<String, String> getProd(){ return getLocal();};
}

2.增加一個配置獲取器

/**
 * 各環境配置引數
 *
 * @author xiaokek
 * @since 2020年5月13日 下午4:02:01
 */
public class CoreConfigs {
    private final Map<String, String> c;
    
    public CoreConfigs(Map<String, String> c) {
        this.c = c;
    }
    public String getConfig(String key) {
        if(!c.containsKey(key)) {
            throw BizException.error("配置項不存在:"+key);
        }
        return c.get(key);
    }

}

3.增加一個自動配置

/**
 * 基於不同環境的配置項
 *
 * @author xiaokek
 * @since 2020年2月27日 下午12:35:27
 */
@Configuration
@ConditionalOnBean(CoreConfig.class)
public class EnvAutoConfiguration implements InitializingBean{

    @Bean
    @Profile({
        "local", "my", "dev"
    })
    public CoreConfigs localConfig(List<CoreConfig> cs) {
        Map<String, String> r = new LinkedCaseInsensitiveMap<>();
        for(CoreConfig cc : cs) {
            r.putAll(cc.getLocal());
        }
        return new CoreConfigs(r);
    }

    @Bean
    @Profile({
        "test"
    })
    public CoreConfigs testConfig(List<CoreConfig> cs) {
        Map<String, String> r = new LinkedCaseInsensitiveMap<>();
        for(CoreConfig cc : cs) {
            r.putAll(cc.getTest());
        }
        return new CoreConfigs(r);
    }

    @Bean
    @Profile({
        "prod"
    })
    public CoreConfigs prodConfig(List<CoreConfig> cs) {
        Map<String, String> r = new LinkedCaseInsensitiveMap<>();
        for(CoreConfig cc : cs) {
            r.putAll(cc.getProd());
        }
        return new CoreConfigs(r);
    }

    
    @Autowired(required=false)List<CoreConfig> cs;
    @Override
    public void afterPropertiesSet() throws Exception {
        if(CollectionUtils.isEmpty(cs)) {
            return;
        }
        Set<String> all = null;
        for(CoreConfig cc : cs) {
            check(cc);
            all = check(all, cc.getLocal().keySet(),"local", cc);
        }
        all = null;
        for(CoreConfig cc : cs) {
            all = check(all, cc.getTest().keySet(),"test", cc);
        }
        all = null;
        for(CoreConfig cc : cs) {
            all = check(all, cc.getProd().keySet(),"prod", cc);
        }
        
    }

  /**
   以開發環境為準,防止引數漏配檢查
  */
private void check(CoreConfig cc) { int size = (cc.getLocal() == null ? 0: cc.getLocal().size()); if(size != (cc.getSit() == null ? 0: cc.getSit().size())) { throw BizException.error(cc.getClass().getName()+" -test的配置引數缺失, 請檢查"); }if(size != (cc.getProd() == null ? 0: cc.getProd().size())) { throw BizException.error(cc.getClass().getName()+" -prod的配置引數缺失, 請檢查"); } } private Set<String> check(Set<String> all, Set<String> next, String env, CoreConfig cc) { if(all == null) { all = Sets.newHashSet(); } SetView<String> in = Sets.intersection(all, next); if(in != null && in.size() > 0) { throw BizException.error("發現" +cc.getClass().getName() +" - " +env+"的重複配置鍵: "+in); } all.addAll(next); return all; } }

4.上面3個是放在一個配置基礎jar包,其他微服務小組可以依賴上述jar包,在自己的工程做自己的配置

關鍵的一點來了,這樣的配置類,可以有無限個,適合拆分在各自的包裡

@Component
public class JobParamConfig implements CoreConfig{
    @Override
    public Map<String, String> getLocal() {
        Map<String, String> c = Maps.newHashMap();
        //YY介面
        c.put("xxx.webservice.url", "http://xxxx:9040/xxx.asmx?WSDL");
        
        //XX介面
        c.put("350100.url", "http:/xxxx:4321/ylxwjk/");
        c.put("350100.username", "1_1");
        c.put("350100.password", "2");
        
        return c;
    }
    @Override
    public Map<String, String> getProd() {
        Map<String, String> c = Maps.newHashMap();
        //YY介面
        c.put("xxx.webservice.url", "http://220.xx.xx.xx:9040/xxx.asmx?WSDL");
        
        //XX介面
        c.put("350100.url", "http://10.xx.xxx.xx:8089/ylxwjk/");
        c.put("350100.username", "222");
        c.put("350100.password", "222");

        return c;
    }
}

5.接下來可以快樂使用引數拉!

@Resource CoreConfigs c;    
private String initToken() {
        String body = "{\"username\":\""+c.getConfig("350100.username")+"\",\"password\":\""+c.getConfig("350100.password")+"\"}";
        String token = null;
        String json = "";
        try {
            ResponseEntity<String> en = rt.exchange( RequestEntity.post(URI.create(c.getConfig("350100.url")+"/user/login")).contentType(MediaType.APPLICATION_JSON).body(body), String.class);
            json = en.getBody();
            Result<String> r = JsonUtil.fromJson(json, Result.class);
            if(StringUtils.equals(r.getCode(),"200")) {
                token = r.getData();
            }else {
                throw BizException.error("身份驗證獲取token失敗!:"+r.getMessage());
            }
        }catch (BizException e) {
            throw e;
        }return token;
    }

 

相關文章