| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- package com.zsjz.gateway.registry;
- import com.zsjz.gateway.config.GatewayProperties;
- import jakarta.annotation.PostConstruct;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
- import org.springframework.context.ApplicationEventPublisher;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.springframework.stereotype.Component;
- import java.util.List;
- import java.util.Optional;
- /**
- * 服务/节点配置的内存快照(事实来源是 PostgreSQL,Redis 与内存都只是缓存/快照)。
- *
- * <ul>
- * <li>启动时:gateway_service 为空则用 YAML 播种,然后加载快照;</li>
- * <li>运行中:本实例管理接口写库后立即 {@link #reload()};其他实例每 10 秒对账一次,
- * 快照变化时发布 {@link RefreshRoutesEvent} 刷新路由缓存。</li>
- * </ul>
- */
- @Slf4j
- @Component
- @RequiredArgsConstructor
- public class GatewayRegistry {
- private final GatewayConfigRepository repository;
- private final GatewayProperties properties;
- private final ApplicationEventPublisher eventPublisher;
- private volatile List<ServiceInfo> services = List.of();
- /**
- * 首次加载(@PostConstruct 阶段)不能发布 RefreshRoutesEvent:启动期事件监听会
- * 拉起路由基础设施(CachingRouteLocator -> 路由定义仓库 -> 本类),形成循环依赖。
- * 初始路由定义由 CachingRouteLocator 自己创建时拉取,不需要事件;后续变更才需要。
- */
- private volatile boolean initialized = false;
- @PostConstruct
- void init() {
- seedFromYamlIfNeeded();
- reload();
- }
- /** 首次启动播种:表为空时把 gateway.services 写入数据库;之后 YAML 不再覆盖 */
- void seedFromYamlIfNeeded() {
- if (repository.countServices() > 0) {
- return;
- }
- List<GatewayProperties.ServiceDef> defs = properties.getServices();
- if (defs == null || defs.isEmpty()) {
- log.warn("gateway_service 表为空且 gateway.services 未配置,网关没有任何可转发服务");
- return;
- }
- for (GatewayProperties.ServiceDef def : defs) {
- GatewayConfigRepository.validateServiceId(def.getId());
- GatewayConfigRepository.validatePathPrefix(def.getPathPrefix());
- repository.seedService(def.getId(), def.getName(), def.getPathPrefix(),
- def.getLoginPath(), def.getHealthPath(), def.getDefaultNodeId());
- if (def.getNodes() != null) {
- for (GatewayProperties.NodeDef node : def.getNodes()) {
- GatewayConfigRepository.validateNodeId(node.getId());
- GatewayConfigRepository.validateBaseUrl(node.getBaseUrl());
- repository.seedNode(node.getId(), def.getId(), node.getName(), node.getBaseUrl());
- }
- }
- }
- log.info("已按 YAML 播种 {} 个网关服务", defs.size());
- }
- /**
- * 从数据库重载快照;有变化时发布路由刷新事件。
- *
- * @return true 表示快照发生了变化
- */
- public synchronized boolean reload() {
- List<ServiceInfo> fresh = repository.loadAll();
- boolean changed = !fresh.equals(services);
- if (changed) {
- services = fresh;
- if (initialized) {
- eventPublisher.publishEvent(new RefreshRoutesEvent(this));
- }
- log.info("网关配置已重载:{} 个服务 {}", fresh.size(),
- fresh.stream().map(ServiceInfo::id).toList());
- }
- initialized = true;
- return changed;
- }
- /** 其他网关实例写库后的对账(多实例共享 PG,写侧只刷新自己,读侧靠这里收敛) */
- @Scheduled(fixedDelayString = "${gateway.registry-poll-interval:10s}")
- void pollForChanges() {
- try {
- reload();
- } catch (Exception ex) {
- log.warn("网关配置对账失败,下一轮重试:{}", ex.toString());
- }
- }
- public List<ServiceInfo> getServices() {
- return services;
- }
- public Optional<ServiceInfo> getService(String serviceId) {
- return services.stream().filter(s -> s.id().equals(serviceId)).findFirst();
- }
- /** 按路径前缀找服务(快照已按前缀长度降序,第一个命中的即最长前缀) */
- public Optional<ServiceInfo> findServiceByPath(String rawPath) {
- return services.stream().filter(s -> s.matchesPath(rawPath)).findFirst();
- }
- }
|