T0.8: open-gateway 建仓——Spring Boot 2.7.18/JDK8 骨架 + EnvelopeWriter(裸码/信封两形态,信封与 SDK OpenApiEnvelope 字段对齐)+ CI/Dockerfile/.dockerignore
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package com.yunhai.platform.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 开放接口网关入口。
|
||||
* <p>
|
||||
* 环境值(datasource/redis/routes/observe)一律走平台配置层(application-platform.yml),
|
||||
* jar 内 application.yml 只留占位注释——环境值打进 jar 曾造成全站事故(§0 规则 8)。
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class GatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.yunhai.platform.gateway.envelope;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 网关统一错误写出组件(开放接口中心 T0.8)。
|
||||
* <p>
|
||||
* 两形态(§1.1/D3):
|
||||
* <ul>
|
||||
* <li><b>裸码</b>:网关自产错误一律裸 HTTP 状态码、无响应体——401 验签失败/缺失、403 可见性拒绝、
|
||||
* 404 target/路由未知、400 URI 非法、413 body 超限、429 限流(RAW)、503 熔断(RAW)、
|
||||
* 502 提供方连接失败、504 提供方超时(F5:鉴权错 = 裸 HTTP,不裹信封);</li>
|
||||
* <li><b>信封</b>:HTTP 200 + {@code {code, msg, data:null}}——字段结构与 SDK 侧
|
||||
* {@code OpenApiEnvelope}(yh-openapi-client core,自 yh-api-std 提炼)的解析形态逐字段对齐:
|
||||
* unwrap 读 {@code code}(非 0 抛业务异常)与 {@code msg},网关治理信封写同名同义字段,
|
||||
* 老 SDK 无需升级即可按 DException 收到 code/msg。</li>
|
||||
* </ul>
|
||||
* 提供方响应<b>原样透传</b>,不经本组件。
|
||||
*/
|
||||
public final class EnvelopeWriter {
|
||||
|
||||
/** 治理错误码(D3 预留;error-style 开关 T3.1 才消费,本阶段只预留常量) */
|
||||
public static final int RATE_LIMITED = 7;
|
||||
public static final int CIRCUIT_OPEN = 8;
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private EnvelopeWriter() {
|
||||
}
|
||||
|
||||
/** 网关自产错误:裸 HTTP 状态码,无响应体(不裹信封) */
|
||||
public static void writeBare(HttpServletResponse response, int status) throws IOException {
|
||||
response.reset();
|
||||
response.setStatus(status);
|
||||
response.setContentLength(0);
|
||||
}
|
||||
|
||||
/** 业务信封:HTTP 200 + {code, msg, data:null}(与 SDK OpenApiEnvelope 解析形态对齐) */
|
||||
public static void writeEnvelope(HttpServletResponse response, int code, String msg) throws IOException {
|
||||
response.reset();
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
ObjectNode node = MAPPER.createObjectNode();
|
||||
node.put("code", code);
|
||||
node.put("msg", msg);
|
||||
node.putNull("data");
|
||||
response.getWriter().write(MAPPER.writeValueAsString(node));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
server:
|
||||
# 网关默认端口(projman 注册档案:port=10010;compose 模板端口注入以盘点结论为准)
|
||||
port: 10010
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: open-gateway
|
||||
datasource:
|
||||
# 注册表/台账库(open_gateway)——连接串由平台配置层注入,jar 内不留环境值
|
||||
url: # 例(平台配置层):jdbc:mysql://<host>:<port>/open_gateway?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
username: #
|
||||
password: #
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
# 表结构由 docs/sql/01_schema.sql 交付,应用不自动建表
|
||||
ddl-auto: none
|
||||
redis:
|
||||
# 密钥链(sign:app:{appKey})走共享 Redis db0——由平台配置层注入
|
||||
host: #
|
||||
port: #
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
|
||||
yh:
|
||||
app:
|
||||
auth:
|
||||
# starter 验签配置(T0.11 起 GatewayKeyService 复用 SignKeyResolver)
|
||||
# include/exclude 路径策略由平台配置层下发;本文件不配任何真实值
|
||||
enabled: # 默认随 starter
|
||||
gateway:
|
||||
# 路由表(T0.12):target -> base-url,值 = 容器网内服务直连 URL,走平台配置层
|
||||
routes: #
|
||||
# userbase: http://userbase-service:8080
|
||||
# userclass: http://userclass-service:8080
|
||||
# cms: http://cms-service:8080
|
||||
# svc-hall: http://svc-hall-service:8080
|
||||
# flow-center: http://flow-center-service:8080
|
||||
# usermsg: http://usermsg-service:8080
|
||||
# notice: http://notice-service:8080
|
||||
# 观察模式(T1.6):true = 全判定只记不拦(R4:P1 只观察);生产初期保持 true
|
||||
observe: true
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.yunhai.platform.gateway.envelope;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* EnvelopeWriter 两形态写出契约(T0.8 TDD):
|
||||
* 裸码无信封 JSON;信封 HTTP 200 + code/msg/data 与 SDK OpenApiEnvelope 解析形态逐字段对齐。
|
||||
*/
|
||||
class EnvelopeWriterTest {
|
||||
|
||||
@Test
|
||||
void bare_status_written() throws IOException {
|
||||
MockHttpServletResponse res = new MockHttpServletResponse();
|
||||
EnvelopeWriter.writeBare(res, 401);
|
||||
assertThat(res.getStatus()).isEqualTo(401);
|
||||
assertThat(res.getContentAsString()).as("裸码无信封 JSON").isEmpty();
|
||||
|
||||
MockHttpServletResponse res404 = new MockHttpServletResponse();
|
||||
EnvelopeWriter.writeBare(res404, 404);
|
||||
assertThat(res404.getStatus()).isEqualTo(404);
|
||||
assertThat(res404.getContentAsString()).isEmpty();
|
||||
|
||||
MockHttpServletResponse res502 = new MockHttpServletResponse();
|
||||
EnvelopeWriter.writeBare(res502, 502);
|
||||
assertThat(res502.getStatus()).isEqualTo(502);
|
||||
assertThat(res502.getContentAsString()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void envelope_written_with_code_and_message() throws IOException {
|
||||
MockHttpServletResponse res = new MockHttpServletResponse();
|
||||
EnvelopeWriter.writeEnvelope(res, 7, "rate limited");
|
||||
assertThat(res.getStatus()).as("信封恒走 HTTP 200").isEqualTo(200);
|
||||
assertThat(res.getContentType()).contains("application/json");
|
||||
assertThat(res.getContentAsString())
|
||||
.isEqualTo("{\"code\":7,\"msg\":\"rate limited\",\"data\":null}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rate_limit_constants_reserved() {
|
||||
assertThat(EnvelopeWriter.RATE_LIMITED).isEqualTo(7);
|
||||
assertThat(EnvelopeWriter.CIRCUIT_OPEN).isEqualTo(8);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user