最近工作上遇到了一類需求,就是要對接大量外部系統,而需要對接的外部系統又存在各種環境,比如開發,測試,正式環境,他們的配置不盡相同。
一般情況下,我們可以通過配置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; }