DynamicRouteLoader.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package org.jeecg.loader;
  2. import cn.hutool.core.util.ArrayUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import com.alibaba.fastjson.JSON;
  5. import com.alibaba.fastjson.JSONArray;
  6. import com.alibaba.fastjson.JSONObject;
  7. import com.alibaba.nacos.api.NacosFactory;
  8. import com.alibaba.nacos.api.config.ConfigService;
  9. import com.alibaba.nacos.api.config.listener.Listener;
  10. import com.alibaba.nacos.api.exception.NacosException;
  11. import com.google.common.collect.Lists;
  12. import lombok.extern.slf4j.Slf4j;
  13. import org.apache.commons.lang3.ObjectUtils;
  14. import org.apache.commons.lang3.StringUtils;
  15. import org.jeecg.common.base.BaseMap;
  16. import org.jeecg.common.constant.CacheConstant;
  17. import org.jeecg.common.util.RedisUtil;
  18. import org.jeecg.config.GatewayRoutersConfig;
  19. import org.jeecg.config.RouterDataType;
  20. import org.jeecg.loader.repository.DynamicRouteService;
  21. import org.jeecg.loader.repository.MyInMemoryRouteDefinitionRepository;
  22. import org.jeecg.loader.vo.MyRouteDefinition;
  23. import org.springframework.beans.factory.annotation.Autowired;
  24. import org.springframework.cloud.context.config.annotation.RefreshScope;
  25. import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
  26. import org.springframework.cloud.gateway.filter.FilterDefinition;
  27. import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
  28. import org.springframework.cloud.gateway.route.RouteDefinition;
  29. import org.springframework.context.ApplicationEventPublisher;
  30. import org.springframework.context.ApplicationEventPublisherAware;
  31. import org.springframework.context.annotation.DependsOn;
  32. import org.springframework.stereotype.Component;
  33. import reactor.core.publisher.Mono;
  34. import java.net.URI;
  35. import java.net.URISyntaxException;
  36. import java.util.ArrayList;
  37. import java.util.List;
  38. import java.util.Map;
  39. import java.util.Properties;
  40. import java.util.concurrent.Executor;
  41. /**
  42. * 动态路由加载器
  43. *
  44. * @author : zyf
  45. * @date :2020-11-10
  46. */
  47. @Slf4j
  48. @Component
  49. @RefreshScope
  50. @DependsOn({"gatewayRoutersConfig"})
  51. public class DynamicRouteLoader implements ApplicationEventPublisherAware {
  52. public static final long DEFAULT_TIMEOUT = 30000;
  53. @Autowired
  54. private GatewayRoutersConfig gatewayRoutersConfig;
  55. private MyInMemoryRouteDefinitionRepository repository;
  56. private ApplicationEventPublisher publisher;
  57. private DynamicRouteService dynamicRouteService;
  58. private ConfigService configService;
  59. private RedisUtil redisUtil;
  60. /**
  61. * 需要拼接key的路由条件
  62. */
  63. private static String[] GEN_KEY_ROUTERS = new String[]{"Path", "Host", "Method", "After", "Before", "Between", "RemoteAddr"};
  64. public DynamicRouteLoader(MyInMemoryRouteDefinitionRepository repository, DynamicRouteService dynamicRouteService, RedisUtil redisUtil) {
  65. this.repository = repository;
  66. this.dynamicRouteService = dynamicRouteService;
  67. this.redisUtil = redisUtil;
  68. }
  69. // @PostConstruct
  70. // public void init() {
  71. // init(null);
  72. // }
  73. public void init(BaseMap baseMap) {
  74. log.info("初始化路由模式,dataType:"+ gatewayRoutersConfig.getDataType());
  75. if (RouterDataType.nacos.toString().endsWith(gatewayRoutersConfig.getDataType())) {
  76. loadRoutesByNacos();
  77. }
  78. //从数据库加载路由
  79. if (RouterDataType.database.toString().endsWith(gatewayRoutersConfig.getDataType())) {
  80. loadRoutesByRedis(baseMap);
  81. }
  82. }
  83. /**
  84. * 刷新路由
  85. *
  86. * @return
  87. */
  88. public Mono<Void> refresh(BaseMap baseMap) {
  89. log.info("初始化路由模式,dataType:"+ gatewayRoutersConfig.getDataType());
  90. if (!RouterDataType.yml.toString().endsWith(gatewayRoutersConfig.getDataType())) {
  91. this.init(baseMap);
  92. }
  93. return Mono.empty();
  94. }
  95. /**
  96. * 从nacos中读取路由配置
  97. *
  98. * @return
  99. */
  100. private void loadRoutesByNacos() {
  101. List<RouteDefinition> routes = Lists.newArrayList();
  102. configService = createConfigService();
  103. if (configService == null) {
  104. log.warn("initConfigService fail");
  105. }
  106. try {
  107. String configInfo = configService.getConfig(gatewayRoutersConfig.getDataId(), gatewayRoutersConfig.getRouteGroup(), DEFAULT_TIMEOUT);
  108. if (StringUtils.isNotBlank(configInfo)) {
  109. log.info("获取网关当前配置:\r\n{}", configInfo);
  110. routes = JSON.parseArray(configInfo, RouteDefinition.class);
  111. }else{
  112. log.warn("ERROR: 从Nacos获取网关配置为空,请确认Nacos配置是否正确!");
  113. }
  114. } catch (NacosException e) {
  115. log.error("初始化网关路由时发生错误", e);
  116. e.printStackTrace();
  117. }
  118. for (RouteDefinition definition : routes) {
  119. log.info("update route : {}", definition.toString());
  120. dynamicRouteService.add(definition);
  121. }
  122. this.publisher.publishEvent(new RefreshRoutesEvent(this));
  123. dynamicRouteByNacosListener(gatewayRoutersConfig.getDataId(), gatewayRoutersConfig.getRouteGroup());
  124. }
  125. /**
  126. * 从redis中读取路由配置
  127. *
  128. * @return
  129. */
  130. private void loadRoutesByRedis(BaseMap baseMap) {
  131. List<MyRouteDefinition> routes = Lists.newArrayList();
  132. configService = createConfigService();
  133. if (configService == null) {
  134. log.warn("initConfigService fail");
  135. }
  136. Object configInfo = redisUtil.get(CacheConstant.GATEWAY_ROUTES);
  137. if (ObjectUtil.isNotEmpty(configInfo)) {
  138. log.info("获取网关当前配置:\r\n{}", configInfo);
  139. JSONArray array = JSON.parseArray(configInfo.toString());
  140. try {
  141. routes = getRoutesByJson(array);
  142. } catch (URISyntaxException e) {
  143. e.printStackTrace();
  144. }
  145. }else{
  146. log.warn("ERROR: 从Redis获取网关配置为空,请确认system服务是否启动成功!");
  147. }
  148. for (MyRouteDefinition definition : routes) {
  149. log.info("update route : {}", definition.toString());
  150. Integer status=definition.getStatus();
  151. if(status.equals(0)){
  152. dynamicRouteService.delete(definition.getId());
  153. }else{
  154. dynamicRouteService.add(definition);
  155. }
  156. }
  157. if(ObjectUtils.isNotEmpty(baseMap)){
  158. String delRouterId = baseMap.get("delRouterId");
  159. if (ObjectUtils.isNotEmpty(delRouterId)) {
  160. dynamicRouteService.delete(delRouterId);
  161. }
  162. }
  163. this.publisher.publishEvent(new RefreshRoutesEvent(this));
  164. }
  165. /**
  166. * redis中的信息需要处理下 转成RouteDefinition对象
  167. * - id: login
  168. * uri: lb://cloud-jeecg-system
  169. * predicates:
  170. * - Path=/jeecg-boot/sys/**,
  171. *
  172. * @param array
  173. * @return
  174. */
  175. public static List<MyRouteDefinition> getRoutesByJson(JSONArray array) throws URISyntaxException {
  176. List<MyRouteDefinition> ls = new ArrayList<>();
  177. for (int i = 0; i < array.size(); i++) {
  178. JSONObject obj = array.getJSONObject(i);
  179. MyRouteDefinition route = new MyRouteDefinition();
  180. route.setId(obj.getString("routerId"));
  181. route.setStatus(obj.getInteger("status"));
  182. Object uri = obj.get("uri");
  183. if (uri == null) {
  184. route.setUri(new URI("lb://" + obj.getString("name")));
  185. } else {
  186. route.setUri(new URI(obj.getString("uri")));
  187. }
  188. Object predicates = obj.get("predicates");
  189. if (predicates != null) {
  190. JSONArray list = JSON.parseArray(predicates.toString());
  191. List<PredicateDefinition> predicateDefinitionList = new ArrayList<>();
  192. for (Object map : list) {
  193. JSONObject json = (JSONObject) map;
  194. PredicateDefinition predicateDefinition = new PredicateDefinition();
  195. //update-begin-author:zyf date:20220419 for:【VUEN-762】路由条件添加异常问题,原因是部分路由条件参数需要设置固定key
  196. String name=json.getString("name");
  197. predicateDefinition.setName(name);
  198. //路由条件是否拼接Key
  199. if(ArrayUtil.contains(GEN_KEY_ROUTERS,name)) {
  200. JSONArray jsonArray = json.getJSONArray("args");
  201. for (int j = 0; j < jsonArray.size(); j++) {
  202. predicateDefinition.addArg("_genkey" + j, jsonArray.get(j).toString());
  203. }
  204. }else{
  205. JSONObject jsonObject = json.getJSONObject("args");
  206. if(ObjectUtil.isNotEmpty(jsonObject)){
  207. for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
  208. Object valueObj=entry.getValue();
  209. if(ObjectUtil.isNotEmpty(valueObj)) {
  210. predicateDefinition.addArg(entry.getKey(), valueObj.toString());
  211. }
  212. }
  213. }
  214. }
  215. //update-end-author:zyf date:20220419 for:【VUEN-762】路由条件添加异常问题,原因是部分路由条件参数需要设置固定key
  216. predicateDefinitionList.add(predicateDefinition);
  217. }
  218. route.setPredicates(predicateDefinitionList);
  219. }
  220. Object filters = obj.get("filters");
  221. if (filters != null) {
  222. JSONArray list = JSON.parseArray(filters.toString());
  223. List<FilterDefinition> filterDefinitionList = new ArrayList<>();
  224. if (ObjectUtil.isNotEmpty(list)) {
  225. for (Object map : list) {
  226. JSONObject json = (JSONObject) map;
  227. JSONArray jsonArray = json.getJSONArray("args");
  228. String name = json.getString("name");
  229. FilterDefinition filterDefinition = new FilterDefinition();
  230. for (Object o : jsonArray) {
  231. JSONObject params = (JSONObject) o;
  232. filterDefinition.addArg(params.getString("key"), params.get("value").toString());
  233. }
  234. filterDefinition.setName(name);
  235. filterDefinitionList.add(filterDefinition);
  236. }
  237. route.setFilters(filterDefinitionList);
  238. }
  239. }
  240. ls.add(route);
  241. }
  242. return ls;
  243. }
  244. // private void loadRoutesByDataBase() {
  245. // List<GatewayRouteVo> routeList = jdbcTemplate.query(SELECT_ROUTES, new RowMapper<GatewayRouteVo>() {
  246. // @Override
  247. // public GatewayRouteVo mapRow(ResultSet rs, int i) throws SQLException {
  248. // GatewayRouteVo result = new GatewayRouteVo();
  249. // result.setId(rs.getString("id"));
  250. // result.setName(rs.getString("name"));
  251. // result.setUri(rs.getString("uri"));
  252. // result.setStatus(rs.getInt("status"));
  253. // result.setRetryable(rs.getInt("retryable"));
  254. // result.setPredicates(rs.getString("predicates"));
  255. // result.setStripPrefix(rs.getInt("strip_prefix"));
  256. // result.setPersist(rs.getInt("persist"));
  257. // return result;
  258. // }
  259. // });
  260. // if (ObjectUtil.isNotEmpty(routeList)) {
  261. // // 加载路由
  262. // routeList.forEach(route -> {
  263. // RouteDefinition definition = new RouteDefinition();
  264. // List<PredicateDefinition> predicatesList = Lists.newArrayList();
  265. // List<FilterDefinition> filtersList = Lists.newArrayList();
  266. // definition.setId(route.getId());
  267. // String predicates = route.getPredicates();
  268. // String filters = route.getFilters();
  269. // if (StringUtils.isNotEmpty(predicates)) {
  270. // predicatesList = JSON.parseArray(predicates, PredicateDefinition.class);
  271. // definition.setPredicates(predicatesList);
  272. // }
  273. // if (StringUtils.isNotEmpty(filters)) {
  274. // filtersList = JSON.parseArray(filters, FilterDefinition.class);
  275. // definition.setFilters(filtersList);
  276. // }
  277. // URI uri = UriComponentsBuilder.fromUriString(route.getUri()).build().toUri();
  278. // definition.setUri(uri);
  279. // this.repository.save(Mono.just(definition)).subscribe();
  280. // });
  281. // log.info("加载路由:{}==============", routeList.size());
  282. // Mono.empty();
  283. // }
  284. // }
  285. /**
  286. * 监听Nacos下发的动态路由配置
  287. *
  288. * @param dataId
  289. * @param group
  290. */
  291. public void dynamicRouteByNacosListener(String dataId, String group) {
  292. try {
  293. configService.addListener(dataId, group, new Listener() {
  294. @Override
  295. public void receiveConfigInfo(String configInfo) {
  296. log.info("进行网关更新:\n\r{}", configInfo);
  297. List<MyRouteDefinition> definitionList = JSON.parseArray(configInfo, MyRouteDefinition.class);
  298. for (MyRouteDefinition definition : definitionList) {
  299. log.info("update route : {}", definition.toString());
  300. dynamicRouteService.update(definition);
  301. }
  302. }
  303. @Override
  304. public Executor getExecutor() {
  305. log.info("getExecutor\n\r");
  306. return null;
  307. }
  308. });
  309. } catch (Exception e) {
  310. log.error("从nacos接收动态路由配置出错!!!", e);
  311. }
  312. }
  313. /**
  314. * 创建ConfigService
  315. *
  316. * @return
  317. */
  318. private ConfigService createConfigService() {
  319. try {
  320. Properties properties = new Properties();
  321. properties.setProperty("serverAddr", gatewayRoutersConfig.getServerAddr());
  322. if(StringUtils.isNotBlank(gatewayRoutersConfig.getNamespace())){
  323. properties.setProperty("namespace", gatewayRoutersConfig.getNamespace());
  324. }
  325. if(StringUtils.isNotBlank( gatewayRoutersConfig.getUsername())){
  326. properties.setProperty("username", gatewayRoutersConfig.getUsername());
  327. }
  328. if(StringUtils.isNotBlank(gatewayRoutersConfig.getPassword())){
  329. properties.setProperty("password", gatewayRoutersConfig.getPassword());
  330. }
  331. return configService = NacosFactory.createConfigService(properties);
  332. } catch (Exception e) {
  333. log.error("创建ConfigService异常", e);
  334. return null;
  335. }
  336. }
  337. @Override
  338. public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
  339. this.publisher = applicationEventPublisher;
  340. }
  341. }