GatewayRegistry.java 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. package com.zsjz.gateway.registry;
  2. import com.zsjz.gateway.config.GatewayProperties;
  3. import jakarta.annotation.PostConstruct;
  4. import lombok.RequiredArgsConstructor;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
  7. import org.springframework.context.ApplicationEventPublisher;
  8. import org.springframework.scheduling.annotation.Scheduled;
  9. import org.springframework.stereotype.Component;
  10. import java.util.List;
  11. import java.util.Optional;
  12. /**
  13. * 服务/节点配置的内存快照(事实来源是 PostgreSQL,Redis 与内存都只是缓存/快照)。
  14. *
  15. * <ul>
  16. * <li>启动时:gateway_service 为空则用 YAML 播种,然后加载快照;</li>
  17. * <li>运行中:本实例管理接口写库后立即 {@link #reload()};其他实例每 10 秒对账一次,
  18. * 快照变化时发布 {@link RefreshRoutesEvent} 刷新路由缓存。</li>
  19. * </ul>
  20. */
  21. @Slf4j
  22. @Component
  23. @RequiredArgsConstructor
  24. public class GatewayRegistry {
  25. private final GatewayConfigRepository repository;
  26. private final GatewayProperties properties;
  27. private final ApplicationEventPublisher eventPublisher;
  28. private volatile List<ServiceInfo> services = List.of();
  29. /**
  30. * 首次加载(@PostConstruct 阶段)不能发布 RefreshRoutesEvent:启动期事件监听会
  31. * 拉起路由基础设施(CachingRouteLocator -> 路由定义仓库 -> 本类),形成循环依赖。
  32. * 初始路由定义由 CachingRouteLocator 自己创建时拉取,不需要事件;后续变更才需要。
  33. */
  34. private volatile boolean initialized = false;
  35. @PostConstruct
  36. void init() {
  37. seedFromYamlIfNeeded();
  38. reload();
  39. }
  40. /** 首次启动播种:表为空时把 gateway.services 写入数据库;之后 YAML 不再覆盖 */
  41. void seedFromYamlIfNeeded() {
  42. if (repository.countServices() > 0) {
  43. return;
  44. }
  45. List<GatewayProperties.ServiceDef> defs = properties.getServices();
  46. if (defs == null || defs.isEmpty()) {
  47. log.warn("gateway_service 表为空且 gateway.services 未配置,网关没有任何可转发服务");
  48. return;
  49. }
  50. for (GatewayProperties.ServiceDef def : defs) {
  51. GatewayConfigRepository.validateServiceId(def.getId());
  52. GatewayConfigRepository.validatePathPrefix(def.getPathPrefix());
  53. repository.seedService(def.getId(), def.getName(), def.getPathPrefix(),
  54. def.getLoginPath(), def.getHealthPath(), def.getDefaultNodeId());
  55. if (def.getNodes() != null) {
  56. for (GatewayProperties.NodeDef node : def.getNodes()) {
  57. GatewayConfigRepository.validateNodeId(node.getId());
  58. GatewayConfigRepository.validateBaseUrl(node.getBaseUrl());
  59. repository.seedNode(node.getId(), def.getId(), node.getName(), node.getBaseUrl());
  60. }
  61. }
  62. }
  63. log.info("已按 YAML 播种 {} 个网关服务", defs.size());
  64. }
  65. /**
  66. * 从数据库重载快照;有变化时发布路由刷新事件。
  67. *
  68. * @return true 表示快照发生了变化
  69. */
  70. public synchronized boolean reload() {
  71. List<ServiceInfo> fresh = repository.loadAll();
  72. boolean changed = !fresh.equals(services);
  73. if (changed) {
  74. services = fresh;
  75. if (initialized) {
  76. eventPublisher.publishEvent(new RefreshRoutesEvent(this));
  77. }
  78. log.info("网关配置已重载:{} 个服务 {}", fresh.size(),
  79. fresh.stream().map(ServiceInfo::id).toList());
  80. }
  81. initialized = true;
  82. return changed;
  83. }
  84. /** 其他网关实例写库后的对账(多实例共享 PG,写侧只刷新自己,读侧靠这里收敛) */
  85. @Scheduled(fixedDelayString = "${gateway.registry-poll-interval:10s}")
  86. void pollForChanges() {
  87. try {
  88. reload();
  89. } catch (Exception ex) {
  90. log.warn("网关配置对账失败,下一轮重试:{}", ex.toString());
  91. }
  92. }
  93. public List<ServiceInfo> getServices() {
  94. return services;
  95. }
  96. public Optional<ServiceInfo> getService(String serviceId) {
  97. return services.stream().filter(s -> s.id().equals(serviceId)).findFirst();
  98. }
  99. /** 按路径前缀找服务(快照已按前缀长度降序,第一个命中的即最长前缀) */
  100. public Optional<ServiceInfo> findServiceByPath(String rawPath) {
  101. return services.stream().filter(s -> s.matchesPath(rawPath)).findFirst();
  102. }
  103. }