target base-url 由 open_api_target 快照优先解析,无行/禁用行/空白地址行回落 yml yh.gateway.routes;/api/internal/routes/refresh 同时重载两表。
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
-- =====================================================================
|
||||||
|
-- 开放接口中心 target 地址表(E11,T-D1a)
|
||||||
|
-- 语义:target_code → 提供方 base-url(容器网内直连)。地址解析优先级 DB > yml(yh.gateway.routes);
|
||||||
|
-- DB 无行 / enabled=0 / base_url 空白 三者一律回落 yml,故本表可整表为空、可逐行灰度。
|
||||||
|
-- 写入方:projman Apply 派生(E12)+ 存量回填(E13);网关运行时只读,
|
||||||
|
-- 改行后 POST /api/internal/routes/refresh 重载即生效(不重启)。
|
||||||
|
-- 可重复执行:CREATE TABLE IF NOT EXISTS(不 DROP——生产回填数据不可丢)
|
||||||
|
-- =====================================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS open_api_target (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
target_code VARCHAR(32) NOT NULL COMMENT '目标应用 ctx(剥掉首斜杠,如 userbase/notice/flowcenter)',
|
||||||
|
base_url VARCHAR(256) NOT NULL COMMENT '提供方 base-url(如 http://notice-service:8080,不含路径)',
|
||||||
|
enabled TINYINT NOT NULL DEFAULT 1,
|
||||||
|
gmt_create DATETIME NOT NULL,
|
||||||
|
gmt_modified DATETIME NOT NULL,
|
||||||
|
UNIQUE KEY uk_target (target_code)
|
||||||
|
);
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.yunhai.platform.gateway.entity;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.PrePersist;
|
||||||
|
import javax.persistence.PreUpdate;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* target 地址表(E11 T-D1a;DDL 见 docs/sql/03_target.sql,字段与列逐一对齐)。
|
||||||
|
* <p>
|
||||||
|
* 与 yml {@code yh.gateway.routes} 同语义(target → base-url),解析优先级 <b>DB > yml</b>:
|
||||||
|
* 无行 / enabled=0 / base_url 空白一律回落 yml,故派生写入(projman Apply)可逐行灰度。
|
||||||
|
* 网关运行时只读,改行后走 {@code /api/internal/routes/refresh} 重载。
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "open_api_target", uniqueConstraints = @UniqueConstraint(
|
||||||
|
name = "uk_target", columnNames = {"target_code"}))
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class OpenApiTarget {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "target_code", nullable = false, length = 32)
|
||||||
|
private String targetCode;
|
||||||
|
|
||||||
|
/** 提供方 base-url,不含路径(转发 = base-url + 完整原始 URI,换主机不换路径) */
|
||||||
|
@Column(name = "base_url", nullable = false, length = 256)
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@Column(name = "enabled", nullable = false)
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
@Column(name = "gmt_create", nullable = false)
|
||||||
|
private LocalDateTime gmtCreate;
|
||||||
|
|
||||||
|
@Column(name = "gmt_modified", nullable = false)
|
||||||
|
private LocalDateTime gmtModified;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
void preCreate() {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
if (gmtCreate == null) {
|
||||||
|
gmtCreate = now;
|
||||||
|
}
|
||||||
|
if (gmtModified == null) {
|
||||||
|
gmtModified = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
void preUpdate() {
|
||||||
|
gmtModified = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.yunhai.platform.gateway.repository;
|
||||||
|
|
||||||
|
import com.yunhai.platform.gateway.entity.OpenApiTarget;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface OpenApiTargetRepository extends JpaRepository<OpenApiTarget, Long> {
|
||||||
|
|
||||||
|
List<OpenApiTarget> findByEnabledTrue();
|
||||||
|
}
|
||||||
@@ -19,12 +19,12 @@ public class RouteRefreshController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RouteRegistry routeRegistry;
|
private RouteRegistry routeRegistry;
|
||||||
|
|
||||||
/** 清缓存重 load,返回加载数 */
|
/** 清缓存重 load,返回加载数(routes = 路由条目,targets = DB 地址行,E11 同端点重载) */
|
||||||
@PostMapping("/routes/refresh")
|
@PostMapping("/routes/refresh")
|
||||||
public Map<String, Object> refresh() {
|
public Map<String, Object> refresh() {
|
||||||
int loaded = routeRegistry.reload();
|
|
||||||
Map<String, Object> body = new LinkedHashMap<>();
|
Map<String, Object> body = new LinkedHashMap<>();
|
||||||
body.put("loaded", loaded);
|
body.put("loaded", routeRegistry.reload());
|
||||||
|
body.put("targets", routeRegistry.reloadTargets());
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package com.yunhai.platform.gateway.route;
|
|||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.yunhai.platform.gateway.entity.OpenApiRoute;
|
import com.yunhai.platform.gateway.entity.OpenApiRoute;
|
||||||
|
import com.yunhai.platform.gateway.entity.OpenApiTarget;
|
||||||
import com.yunhai.platform.gateway.repository.OpenApiRouteRepository;
|
import com.yunhai.platform.gateway.repository.OpenApiRouteRepository;
|
||||||
|
import com.yunhai.platform.gateway.repository.OpenApiTargetRepository;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -40,12 +44,22 @@ public class RouteRegistry {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private OpenApiRouteRepository routeRepo;
|
private OpenApiRouteRepository routeRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OpenApiTargetRepository targetRepo;
|
||||||
|
|
||||||
/** key = targetCode;无 expire(条目 <100,靠手动刷新) */
|
/** key = targetCode;无 expire(条目 <100,靠手动刷新) */
|
||||||
private final Cache<String, List<Entry>> cache = Caffeine.newBuilder().build();
|
private final Cache<String, List<Entry>> cache = Caffeine.newBuilder().build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* target → base-url 的 DB 快照(E11;整体替换,读侧无锁)。
|
||||||
|
* 只收 enabled 且地址非空白的行——空白行等同无行,由 {@link TargetRouter} 回落 yml。
|
||||||
|
*/
|
||||||
|
private volatile Map<String, String> targetBaseUrls = Collections.emptyMap();
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
reload();
|
reload();
|
||||||
|
reloadTargets();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全量重 load(@PostConstruct 与 refresh 端点共用);返回加载数 */
|
/** 全量重 load(@PostConstruct 与 refresh 端点共用);返回加载数 */
|
||||||
@@ -64,6 +78,27 @@ public class RouteRegistry {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全量重 load target 地址表(与 {@link #reload()} 同由 refresh 端点触发);返回装载数。
|
||||||
|
* 表为空(E13 回填前的常态)= 全部 target 走 yml 兜底。
|
||||||
|
*/
|
||||||
|
public int reloadTargets() {
|
||||||
|
Map<String, String> fresh = new LinkedHashMap<>();
|
||||||
|
for (OpenApiTarget t : targetRepo.findByEnabledTrue()) {
|
||||||
|
if (!t.isEnabled() || t.getBaseUrl() == null || t.getBaseUrl().trim().isEmpty()) {
|
||||||
|
continue; // 与路由条目同口径双保险:禁用行/空白地址行不覆盖 yml
|
||||||
|
}
|
||||||
|
fresh.put(t.getTargetCode(), t.getBaseUrl().trim());
|
||||||
|
}
|
||||||
|
targetBaseUrls = fresh;
|
||||||
|
return fresh.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** target 的 DB 地址;无行(含被过滤的禁用/空白行)返回 null = 回落 yml */
|
||||||
|
public String targetBaseUrl(String targetCode) {
|
||||||
|
return targetBaseUrls.get(targetCode);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由判定。method 参与匹配(条目 * 或精确相等);精确 pattern 优先于 /** 通配。
|
* 路由判定。method 参与匹配(条目 * 或精确相等);精确 pattern 优先于 /** 通配。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import org.springframework.stereotype.Component;
|
|||||||
* 路由选择、注册表匹配与 canonical 剥离,不参与转发路径改写——提供方自带 context-path,
|
* 路由选择、注册表匹配与 canonical 剥离,不参与转发路径改写——提供方自带 context-path,
|
||||||
* URI 原样送达即天然对齐,签名 canonical 逐字节不变。<br>
|
* URI 原样送达即天然对齐,签名 canonical 逐字节不变。<br>
|
||||||
* 网关只做 oapi:未知 target / 单段 URI / 第二段非 oapi(如 /{target}/api/x)一律返回 null
|
* 网关只做 oapi:未知 target / 单段 URI / 第二段非 oapi(如 /{target}/api/x)一律返回 null
|
||||||
* = <b>裸 404</b>({@code /api} 与页面不属于网关职责)。
|
* = <b>裸 404</b>({@code /api} 与页面不属于网关职责)。<br>
|
||||||
|
* base-url 解析优先级 <b>DB(open_api_target)> yml(yh.gateway.routes)</b>(E11):
|
||||||
|
* DB 无行、禁用行、空白地址行都按「无行」处理,回落 yml,故 yml 恒为兜底不被绕过。
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class TargetRouter {
|
public class TargetRouter {
|
||||||
@@ -33,11 +35,20 @@ public class TargetRouter {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private GatewayProperties properties;
|
private GatewayProperties properties;
|
||||||
|
|
||||||
|
/** target 地址 DB 快照持有者(E11);解析优先级 DB > yml */
|
||||||
|
@Autowired
|
||||||
|
private RouteRegistry routeRegistry;
|
||||||
|
|
||||||
/** 测试/显式装配入口 */
|
/** 测试/显式装配入口 */
|
||||||
void setProperties(GatewayProperties properties) {
|
void setProperties(GatewayProperties properties) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 测试/显式装配入口 */
|
||||||
|
void setRouteRegistry(RouteRegistry routeRegistry) {
|
||||||
|
this.routeRegistry = routeRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param requestUri 原始请求 URI(未解码,如 /userbase/oapi/x)
|
* @param requestUri 原始请求 URI(未解码,如 /userbase/oapi/x)
|
||||||
* @param queryString 原始 query(无 '?' 前缀;null = 无)
|
* @param queryString 原始 query(无 '?' 前缀;null = 无)
|
||||||
@@ -64,7 +75,10 @@ public class TargetRouter {
|
|||||||
if (!rest.equals("/oapi") && !rest.startsWith("/oapi/")) {
|
if (!rest.equals("/oapi") && !rest.startsWith("/oapi/")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String baseUrl = properties.getRoutes().get(target);
|
String baseUrl = routeRegistry.targetBaseUrl(target);
|
||||||
|
if (baseUrl == null) {
|
||||||
|
baseUrl = properties.getRoutes().get(target); // DB 无行(含禁用/空白行)→ yml 兜底
|
||||||
|
}
|
||||||
if (baseUrl == null || baseUrl.trim().isEmpty()) {
|
if (baseUrl == null || baseUrl.trim().isEmpty()) {
|
||||||
return null; // 未知 target = 裸 404
|
return null; // 未知 target = 裸 404
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.yunhai.platform.gateway.route;
|
package com.yunhai.platform.gateway.route;
|
||||||
|
|
||||||
import com.yunhai.platform.gateway.entity.OpenApiRoute;
|
import com.yunhai.platform.gateway.entity.OpenApiRoute;
|
||||||
|
import com.yunhai.platform.gateway.entity.OpenApiTarget;
|
||||||
import com.yunhai.platform.gateway.repository.OpenApiRouteRepository;
|
import com.yunhai.platform.gateway.repository.OpenApiRouteRepository;
|
||||||
|
import com.yunhai.platform.gateway.repository.OpenApiTargetRepository;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
@@ -109,4 +111,27 @@ class RouteRegistryTest {
|
|||||||
assertThat(spec.authMode).as("refresh 后新增条目生效").isEqualTo("ANON");
|
assertThat(spec.authMode).as("refresh 后新增条目生效").isEqualTo("ANON");
|
||||||
assertThat(spec.cacheTtlSeconds).isEqualTo(120);
|
assertThat(spec.cacheTtlSeconds).isEqualTo(120);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reloadTargets_装载数只计有效行_空白地址不入快照() {
|
||||||
|
// E11:target 地址快照与路由条目同库不同表,装载口径一致(enabled 行 + 非空地址)
|
||||||
|
OpenApiTargetRepository targetRepo = Mockito.mock(OpenApiTargetRepository.class);
|
||||||
|
when(targetRepo.findByEnabledTrue()).thenReturn(Arrays.asList(
|
||||||
|
targetRow("userbase", "http://userbase-service:8080"),
|
||||||
|
targetRow("notice", " ")));
|
||||||
|
RouteRegistry reg = new RouteRegistry();
|
||||||
|
ReflectionTestUtils.setField(reg, "targetRepo", targetRepo);
|
||||||
|
assertThat(reg.reloadTargets()).as("空白地址行不装载").isEqualTo(1);
|
||||||
|
assertThat(reg.targetBaseUrl("userbase")).isEqualTo("http://userbase-service:8080");
|
||||||
|
assertThat(reg.targetBaseUrl("notice")).as("空白地址 = 无 DB 行语义,由 TargetRouter 回落 yml").isNull();
|
||||||
|
assertThat(reg.targetBaseUrl("nobody")).as("DB 无此 target").isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OpenApiTarget targetRow(String code, String baseUrl) {
|
||||||
|
OpenApiTarget t = new OpenApiTarget();
|
||||||
|
t.setTargetCode(code);
|
||||||
|
t.setBaseUrl(baseUrl);
|
||||||
|
t.setEnabled(true);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,33 @@
|
|||||||
package com.yunhai.platform.gateway.route;
|
package com.yunhai.platform.gateway.route;
|
||||||
|
|
||||||
|
import com.yunhai.platform.gateway.entity.OpenApiTarget;
|
||||||
|
import com.yunhai.platform.gateway.repository.OpenApiTargetRepository;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TargetRouter 用例(T0.12 TDD):已知 target forwardUrl 逐字符断言(换主机不换路径)、
|
* TargetRouter 用例(T0.12 TDD):已知 target forwardUrl 逐字符断言(换主机不换路径)、
|
||||||
* 未知 target / 单段 / 非 oapi 第二段一律裸 404(null)、base-url 尾斜杠归一化。
|
* 未知 target / 单段 / 非 oapi 第二段一律裸 404(null)、base-url 尾斜杠归一化。
|
||||||
|
* <p>
|
||||||
|
* E11 追加 DB>yml 四例:DB 有行覆盖 yml、DB 无行回落 yml、enabled=0 回落 yml、
|
||||||
|
* refresh 后新增行生效。target 快照经真实 {@link RouteRegistry}(mock repository)走,
|
||||||
|
* 不 stub 解析逻辑本身。
|
||||||
*/
|
*/
|
||||||
class TargetRouterTest {
|
class TargetRouterTest {
|
||||||
|
|
||||||
private TargetRouter router;
|
private TargetRouter router;
|
||||||
|
private RouteRegistry registry;
|
||||||
|
private List<OpenApiTarget> targetRows;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
@@ -25,6 +38,26 @@ class TargetRouterTest {
|
|||||||
routes.put("notice", "http://notice-service:8080/");
|
routes.put("notice", "http://notice-service:8080/");
|
||||||
props.setRoutes(routes);
|
props.setRoutes(routes);
|
||||||
router.setProperties(props);
|
router.setProperties(props);
|
||||||
|
|
||||||
|
targetRows = new ArrayList<>();
|
||||||
|
OpenApiTargetRepository targetRepo = Mockito.mock(OpenApiTargetRepository.class);
|
||||||
|
when(targetRepo.findByEnabledTrue()).thenReturn(targetRows);
|
||||||
|
registry = new RouteRegistry();
|
||||||
|
ReflectionTestUtils.setField(registry, "targetRepo", targetRepo);
|
||||||
|
router.setRouteRegistry(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OpenApiTarget target(String code, String baseUrl, boolean enabled) {
|
||||||
|
OpenApiTarget t = new OpenApiTarget();
|
||||||
|
t.setTargetCode(code);
|
||||||
|
t.setBaseUrl(baseUrl);
|
||||||
|
t.setEnabled(enabled);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 模拟 refresh 端点:重载 target 表快照 */
|
||||||
|
private void refreshTargets() {
|
||||||
|
registry.reloadTargets();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -76,4 +109,41 @@ class TargetRouterTest {
|
|||||||
assertThat(r.forwardUrl).isEqualTo("http://userbase-service:8080/userbase/oapi");
|
assertThat(r.forwardUrl).isEqualTo("http://userbase-service:8080/userbase/oapi");
|
||||||
assertThat(r.pathAfterStrip).isEqualTo("/oapi");
|
assertThat(r.pathAfterStrip).isEqualTo("/oapi");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void DB有行_用DB地址覆盖yml() {
|
||||||
|
targetRows.add(target("userbase", "http://10.0.0.9:9999", true));
|
||||||
|
refreshTargets();
|
||||||
|
TargetRouter.Routing r = router.route("/userbase/oapi/x", "a=1");
|
||||||
|
assertThat(r.forwardUrl).as("DB 行优先于 yml 同名 target")
|
||||||
|
.isEqualTo("http://10.0.0.9:9999/userbase/oapi/x?a=1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void DB无行_回落yml() {
|
||||||
|
refreshTargets();
|
||||||
|
TargetRouter.Routing r = router.route("/userbase/oapi/x", null);
|
||||||
|
assertThat(r.forwardUrl).isEqualTo("http://userbase-service:8080/userbase/oapi/x");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void DB禁用行_回落yml() {
|
||||||
|
// repo 契约按 enabled=true 过滤;注册表再滤一层(与 RouteRegistry 同口径双保险)
|
||||||
|
targetRows.add(target("userbase", "http://10.0.0.9:9999", false));
|
||||||
|
refreshTargets();
|
||||||
|
TargetRouter.Routing r = router.route("/userbase/oapi/x", null);
|
||||||
|
assertThat(r.forwardUrl).as("enabled=0 行不参与解析").isEqualTo("http://userbase-service:8080/userbase/oapi/x");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void DB行只在解析后新增时可见_未refresh不生效() {
|
||||||
|
refreshTargets();
|
||||||
|
targetRows.add(target("flowcenter", "http://flow-center-service:8080", true));
|
||||||
|
assertThat(router.route("/flowcenter/oapi/x", null))
|
||||||
|
.as("快照未 refresh 前 DB 新行不生效").isNull();
|
||||||
|
refreshTargets();
|
||||||
|
TargetRouter.Routing r = router.route("/flowcenter/oapi/x", null);
|
||||||
|
assertThat(r).as("refresh 后 DB 新增 target 生效(yml 无此 target)").isNotNull();
|
||||||
|
assertThat(r.forwardUrl).isEqualTo("http://flow-center-service:8080/flowcenter/oapi/x");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ spring:
|
|||||||
password:
|
password:
|
||||||
sql:
|
sql:
|
||||||
init:
|
init:
|
||||||
# 直接跑交付 DDL(docs/sql/01_schema.sql)——round-trip 钉死「DDL ↔ 实体」一致
|
# 直接跑交付 DDL(docs/sql/01_schema.sql + 03_target.sql)——round-trip 钉死「DDL ↔ 实体」一致
|
||||||
mode: always
|
mode: always
|
||||||
schema-locations: file:docs/sql/01_schema.sql
|
schema-locations:
|
||||||
|
- file:docs/sql/01_schema.sql
|
||||||
|
- file:docs/sql/03_target.sql
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: none
|
ddl-auto: none
|
||||||
|
|||||||
Reference in New Issue
Block a user