diff --git a/src/main/java/com/yunhai/platform/gateway/route/GatewayProperties.java b/src/main/java/com/yunhai/platform/gateway/route/GatewayProperties.java new file mode 100644 index 0000000..4bb871a --- /dev/null +++ b/src/main/java/com/yunhai/platform/gateway/route/GatewayProperties.java @@ -0,0 +1,42 @@ +package com.yunhai.platform.gateway.route; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 网关配置(T0.12 起;环境值走平台配置层,jar 内 yml 只留占位)。 + *
+ * 换主机不换路径:forwardUrl = base-url + 完整原始 URI(含 query)。target 首段仅用于
+ * 路由选择、注册表匹配与 canonical 剥离,不参与转发路径改写——提供方自带 context-path,
+ * URI 原样送达即天然对齐,签名 canonical 逐字节不变。
+ * 网关只做 oapi:未知 target / 单段 URI / 第二段非 oapi(如 /{target}/api/x)一律返回 null
+ * = 裸 404({@code /api} 与页面不属于网关职责)。
+ */
+@Component
+public class TargetRouter {
+
+ /** 路由产物:判定字段 + 转发 URL */
+ public static class Routing {
+ public final String target;
+ /** 剥掉 target 首段后的路径(如 /oapi/message/list)——RouteRegistry 与签名 canonical 同一坐标系 */
+ public final String pathAfterStrip;
+ /** base-url + 完整原始 URI(含 query),逐字符不改写 */
+ public final String forwardUrl;
+
+ Routing(String target, String pathAfterStrip, String forwardUrl) {
+ this.target = target;
+ this.pathAfterStrip = pathAfterStrip;
+ this.forwardUrl = forwardUrl;
+ }
+ }
+
+ @Autowired
+ private GatewayProperties properties;
+
+ /** 测试/显式装配入口 */
+ void setProperties(GatewayProperties properties) {
+ this.properties = properties;
+ }
+
+ /**
+ * @param requestUri 原始请求 URI(未解码,如 /userbase/oapi/x)
+ * @param queryString 原始 query(无 '?' 前缀;null = 无)
+ * @return 路由产物;不匹配(未知 target / 单段 / 非 oapi 第二段)返回 null = 裸 404
+ */
+ public Routing route(String requestUri, String queryString) {
+ if (requestUri == null || requestUri.isEmpty() || requestUri.equals("/")) {
+ return null;
+ }
+ // 剥首段 target:/userbase/oapi/x → target=userbase, rest=/oapi/x
+ int secondSlash = requestUri.indexOf('/', 1);
+ String target;
+ String rest;
+ if (secondSlash < 0) {
+ return null; // 单段 URI(/foo)——无 oapi 第二段
+ } else {
+ target = requestUri.substring(1, secondSlash);
+ rest = requestUri.substring(secondSlash);
+ }
+ if (target.isEmpty()) {
+ return null; // 双斜杠开头等畸形
+ }
+ // 网关只做 oapi:第二段必须恰为 oapi(/oapi、/oapi/xxx 均可)
+ if (!rest.equals("/oapi") && !rest.startsWith("/oapi/")) {
+ return null;
+ }
+ String baseUrl = properties.getRoutes().get(target);
+ if (baseUrl == null || baseUrl.trim().isEmpty()) {
+ return null; // 未知 target = 裸 404
+ }
+ String normalizedBase = baseUrl.endsWith("/")
+ ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
+ String url = normalizedBase + requestUri
+ + (queryString == null || queryString.isEmpty() ? "" : "?" + queryString);
+ return new Routing(target, rest, url);
+ }
+}
diff --git a/src/test/java/com/yunhai/platform/gateway/route/TargetRouterTest.java b/src/test/java/com/yunhai/platform/gateway/route/TargetRouterTest.java
new file mode 100644
index 0000000..cad395f
--- /dev/null
+++ b/src/test/java/com/yunhai/platform/gateway/route/TargetRouterTest.java
@@ -0,0 +1,79 @@
+package com.yunhai.platform.gateway.route;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * TargetRouter 用例(T0.12 TDD):已知 target forwardUrl 逐字符断言(换主机不换路径)、
+ * 未知 target / 单段 / 非 oapi 第二段一律裸 404(null)、base-url 尾斜杠归一化。
+ */
+class TargetRouterTest {
+
+ private TargetRouter router;
+
+ @BeforeEach
+ void setUp() {
+ router = new TargetRouter();
+ GatewayProperties props = new GatewayProperties();
+ Map