## 新增服务模块 ### 1. ERP服务 (hzhub-erp) - 新增独立的ERP数据适配服务 - 支持SQL Server 2008 R2数据源 - 提供动态API配置管理系统 - 包含客户管理、销售数据等业务接口 ### 2. 系统服务 (hzhub-system) - 新增独立的系统管理服务 - 用户、角色、权限、部门、菜单管理 - 租户管理、操作日志、在线用户监控 - 工作流引擎(warm-flow)集成 - 企业微信审批同步功能 ### 3. API网关 (hzhub-gateway) - 新增Spring Cloud Gateway网关服务 - JWT认证、路由分发、限流熔断 - XSS防护、请求日志记录 - 统一入口端口8080 ## 后台管理功能增强 ### ERP动态API管理 - 新增动态API配置管理界面 - API测试、文档预览、统计监控 - 错误日志查看、缓存管理 - 从数据库表自动导入API配置 ### 系统管理增强 - 企业微信配置管理 - 企业微信审批同步配置 - 部门和用户管理优化 ## 员工门户功能完善 ### 业务页面 - 审批中心:工作流审批、待办任务 - CRM管理:客户关系管理 - 经销商管理:经销商数据展示 - 供应链管理:采购、库存、销售 - BI报表:数据可视化分析 - ERP数据探索:SQL Server数据查询 ### 个人中心 - 基本设置:个人信息管理 - 安全设置:密码修改、登录日志 - 锁屏功能:自动锁屏、手动锁屏 ### 其他功能 - 标签页管理:多标签页导航 - 页面缓存:keepAlive缓存机制 - 会话超时:自动检测并提示 ## 经销商门户 ### 页面路由 - 新增经销商管理页面路由 - AI聊天界面完善 ## 文档更新 - ERP API数据库初始化指南 - ERP API前端完整实现文档 - ERP API测试和验证指南 - Gateway路由迁移计划 - 项目配置文档更新 ## 部署脚本 - 统一启动/停止/重启脚本 - Docker Compose配置优化 - Nginx配置文件更新 ## 技术栈 - 后端: Spring Boot 3.5.8, Java 17 - 前端: Vue 3, TypeScript, Element Plus, Vben Admin - 工作流: warm-flow 1.8.2 - 网关: Spring Cloud Gateway - 数据库: MySQL 8.0, SQL Server 2008 R2 - 缓存: Redis 7 - 向量库: Weaviate 1.25.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
544 lines
16 KiB
Markdown
544 lines
16 KiB
Markdown
# HZHub-Gateway 迁移方案
|
||
|
||
## 一、现状分析
|
||
|
||
### 当前架构(去中心化)
|
||
|
||
```
|
||
前端门户 → Nginx/Vite代理 → 直连后端服务
|
||
admin → localhost:6039 (hzhub-ai)
|
||
employee → localhost:6039 (hzhub-ai) + localhost:8082 (hzhub-erp)
|
||
dealer → localhost:6039 (hzhub-ai) + localhost:8082 (hzhub-erp)
|
||
```
|
||
|
||
hzhub-gateway 已存在但未部署,仅有基础路由配置(3条路由 + CORS)。
|
||
|
||
### hzhub-ai 中可迁移的网关级功能
|
||
|
||
| 功能 | 位置 | 是否适合迁移 |
|
||
|------|------|:---:|
|
||
| Sa-Token JWT 验证 | `SaTokenConfig` + `SecurityConfig` | ✅ 是 — 全局统一鉴权 |
|
||
| XSS 过滤 | `XssFilter` (javax.servlet Filter) | ✅ 是 — WebFilter 可实现 |
|
||
| 请求/响应加密 | `CryptoFilter` (RSA 加解密) | ⚠️ 部分 — 需适配 WebFlux |
|
||
| 接口限流 | `@RateLimiter` + Redisson | ✅ 是 — 可用 Redis + RequestRateLimiter |
|
||
| 幂等性校验 | `@Idempotent` | ❌ 否 — 业务级,保留后端 |
|
||
| SSE 流式响应 | ChatController | ❌ 否 — 业务级,保留后端 |
|
||
| WebSocket 握手鉴权 | `PlusWebSocketInterceptor` | ❌ 否 — 协议不同,保留后端 |
|
||
| 数据权限拦截 | MyBatis 拦截器 | ❌ 否 — ORM 层,保留后端 |
|
||
|
||
### 核心挑战
|
||
|
||
1. **Servlet vs WebFlux**:Gateway 基于 Spring WebFlux(响应式),hzhub-ai 的 Filter 是 Servlet 规范,不能直接复用,需改写为 `WebFilter`
|
||
2. **JWT 解析**:Gateway 只需验证 JWT 合法性并透传用户信息,无需完整 Sa-Token 登录态
|
||
3. **加密适配**:CryptoFilter 依赖 `ServletInputStream`,需改写为 `ServerHttpRequestDecorator`
|
||
|
||
---
|
||
|
||
## 二、目标架构
|
||
|
||
```
|
||
前端门户 → Gateway(:8080) → 后端服务
|
||
/ai/** → hzhub-ai(:8081)
|
||
/erp/** → hzhub-erp(:8082)
|
||
/system/** → hzhub-ai(:8081)
|
||
```
|
||
|
||
Gateway 承担:
|
||
- **统一鉴权**:验证 JWT Token,解析用户信息注入请求头
|
||
- **XSS 防护**:对 POST/PUT 请求体进行 XSS 清洗
|
||
- **接口限流**:基于 Redis 的令牌桶限流
|
||
- **请求/响应加密**:按需解密请求、加密响应
|
||
- **路由转发**:按路径分发到对应服务
|
||
- **跨域处理**:全局 CORS
|
||
|
||
后端服务(hzhub-ai / hzhub-erp)承担:
|
||
- **业务逻辑**:CRUD、AI 对话、工作流等
|
||
- **数据权限**:MyBatis 层的多租户数据隔离
|
||
- **幂等性**:防重复提交
|
||
- **SSE/WebSocket**:流式推送
|
||
|
||
### 服务间信任模型
|
||
|
||
```
|
||
外部请求 → Gateway → 后端服务
|
||
│ │
|
||
验证JWT 信任Gateway
|
||
透传的用户头
|
||
```
|
||
|
||
- Gateway 验证 JWT 通过后,将用户信息注入请求头:`X-User-Id`、`X-User-Name`、`X-Client-Id`
|
||
- 后端服务在 `SecurityConfig` 中配置:**来自 Gateway 的请求跳过 JWT 验证**
|
||
- 判断依据:检查请求头 `X-Gateway-Verified: true`(或共享内网 IP 白名单)
|
||
- 开发环境:保留直连后端的鉴权能力(双重模式)
|
||
|
||
---
|
||
|
||
## 三、实施步骤
|
||
|
||
### Phase 1: Gateway 基础增强(认证 + 路由)
|
||
|
||
**目标**:让 Gateway 能正常转发请求并验证 JWT
|
||
|
||
#### 3.1 修改 pom.xml
|
||
|
||
```xml
|
||
<!-- 已有:spring-cloud-starter-gateway, spring-cloud-starter-loadbalancer -->
|
||
|
||
<!-- 新增 -->
|
||
<dependency>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>cn.dev33</groupId>
|
||
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
|
||
<version>1.39.0</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>cn.dev33</groupId>
|
||
<artifactId>sa-token-jwt</artifactId>
|
||
<version>1.39.0</version>
|
||
</dependency>
|
||
```
|
||
|
||
#### 3.2 JWT 认证全局过滤器
|
||
|
||
```java
|
||
// src/main/java/org/hzhub/gateway/filter/AuthGlobalFilter.java
|
||
@Component
|
||
@RequiredArgsConstructor
|
||
public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
||
|
||
private final SaTokenConfig saTokenConfig;
|
||
|
||
// 放行路径
|
||
private static final List<String> WHITE_LIST = List.of(
|
||
"/erp/test/", "/erp/customer/", "/actuator/"
|
||
);
|
||
|
||
@Override
|
||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||
String path = exchange.getRequest().getURI().getPath();
|
||
|
||
// 白名单放行
|
||
if (isWhiteList(path)) {
|
||
return chain.filter(exchange);
|
||
}
|
||
|
||
// 从 Header 获取 Token
|
||
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
|
||
if (token == null || !token.startsWith("Bearer ")) {
|
||
return unauthorized(exchange, "未登录或登录已过期");
|
||
}
|
||
|
||
// 验证 JWT(使用 sa-token-jwt)
|
||
String jwtToken = token.substring(7);
|
||
try {
|
||
SaTokenInfo info = SaManager.getTokenInfo(jwtToken);
|
||
if (info == null || !info.isLogin()) {
|
||
return unauthorized(exchange, "登录已过期");
|
||
}
|
||
// 透传用户信息到后端
|
||
ServerHttpRequest mutated = exchange.getRequest().mutate()
|
||
.header("X-User-Id", info.getLoginId())
|
||
.header("X-Client-Id", exchange.getRequest().getHeaders().getFirst("ClientID"))
|
||
.header("X-Gateway-Verified", "true")
|
||
.build();
|
||
return chain.filter(exchange.mutate().request(mutated).build());
|
||
} catch (Exception e) {
|
||
return unauthorized(exchange, "Token 无效");
|
||
}
|
||
}
|
||
|
||
private boolean isWhiteList(String path) {
|
||
return WHITE_LIST.stream().anyMatch(path::startsWith);
|
||
}
|
||
|
||
private Mono<Void> unauthorized(ServerWebExchange exchange, String msg) {
|
||
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||
byte[] bytes = ("{\"code\":401,\"msg\":\"" + msg + "\"}").getBytes(StandardCharsets.UTF_8);
|
||
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
|
||
return exchange.getResponse().writeWith(Mono.just(buffer));
|
||
}
|
||
|
||
@Override
|
||
public int getOrder() { return -100; } // 最高优先级
|
||
}
|
||
```
|
||
|
||
#### 3.3 更新 application.yml
|
||
|
||
```yaml
|
||
server:
|
||
port: 8080
|
||
|
||
spring:
|
||
application:
|
||
name: hzhub-gateway
|
||
|
||
# Redis 配置(限流 + Token 验证共享)
|
||
data:
|
||
redis:
|
||
host: ${REDIS_HOST:localhost}
|
||
port: ${REDIS_PORT:6379}
|
||
password: ${REDIS_PASSWORD:}
|
||
|
||
cloud:
|
||
gateway:
|
||
routes:
|
||
- id: hzhub-ai
|
||
uri: http://${AI_HOST:localhost}:${AI_PORT:8081}
|
||
predicates:
|
||
- Path=/ai/**,/system/**
|
||
filters:
|
||
- StripPrefix=1
|
||
|
||
- id: hzhub-erp
|
||
uri: http://${ERP_HOST:localhost}:${ERP_PORT:8082}
|
||
predicates:
|
||
- Path=/erp/**
|
||
filters:
|
||
- StripPrefix=1
|
||
|
||
globalcors:
|
||
cors-configurations:
|
||
'[/**]':
|
||
allowedOriginPatterns: "*"
|
||
allowedMethods: "*"
|
||
allowedHeaders: "*"
|
||
allowCredentials: true
|
||
maxAge: 3600
|
||
|
||
# Sa-Token(JWT 验证用,与后端服务共享 secret)
|
||
sa-token:
|
||
jwt-secret-key: ${JWT_SECRET:abcdefghijklmnopqrstuvwxyz}
|
||
token-name: Authorization
|
||
|
||
# 日志
|
||
logging:
|
||
level:
|
||
org.hzhub.gateway: debug
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 2: XSS 过滤 + 限流
|
||
|
||
#### 3.4 XSS 全局过滤器(WebFlux 版)
|
||
|
||
```java
|
||
// src/main/java/org/hzhub/gateway/filter/XssGlobalFilter.java
|
||
@Component
|
||
public class XssGlobalFilter implements GlobalFilter, Ordered {
|
||
|
||
private static final List<String> EXCLUDE_PATHS = List.of("/ai/upload");
|
||
|
||
@Override
|
||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||
String path = exchange.getRequest().getURI().getPath();
|
||
HttpMethod method = exchange.getRequest().getMethod();
|
||
|
||
// GET/DELETE 不处理;排除白名单
|
||
if (method == HttpMethod.GET || method == HttpMethod.DELETE
|
||
|| EXCLUDE_PATHS.stream().anyMatch(path::startsWith)) {
|
||
return chain.filter(exchange);
|
||
}
|
||
|
||
// 包装请求,对 Body 进行 XSS 清洗
|
||
ServerHttpRequest decorated = new ServerHttpRequestDecorator(exchange.getRequest()) {
|
||
@Override
|
||
public Flux<DataBuffer> getBody() {
|
||
return super.getBody().map(buffer -> {
|
||
byte[] bytes = new byte[buffer.readableByteCount()];
|
||
buffer.read(bytes);
|
||
String body = new String(bytes, StandardCharsets.UTF_8);
|
||
String cleaned = cleanXss(body);
|
||
byte[] cleanedBytes = cleaned.getBytes(StandardCharsets.UTF_8);
|
||
DataBufferFactory factory = buffer.factory();
|
||
DataBuffer newBuffer = factory.allocateBuffer(cleanedBytes.length);
|
||
newBuffer.write(cleanedBytes);
|
||
return newBuffer;
|
||
});
|
||
}
|
||
};
|
||
|
||
return chain.filter(exchange.mutate().request(decorated).build());
|
||
}
|
||
|
||
private String cleanXss(String value) {
|
||
if (value == null) return null;
|
||
return value
|
||
.replaceAll("<script>", "<script>")
|
||
.replaceAll("</script>", "</script>")
|
||
.replaceAll("javascript:", "")
|
||
.replaceAll("on\\w+\\s*=", "");
|
||
}
|
||
|
||
@Override
|
||
public int getOrder() { return -50; }
|
||
}
|
||
```
|
||
|
||
#### 3.5 限流配置(基于 Redis)
|
||
|
||
```yaml
|
||
# 在 application.yml 的 gateway 配置中添加
|
||
spring.cloud.gateway.default-filters:
|
||
- name: RequestRateLimiter
|
||
args:
|
||
redis-rate-limiter.replenishRate: 10 # 每秒补充令牌数
|
||
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
|
||
key-resolver: "#{@ipKeyResolver}" # 按 IP 限流
|
||
```
|
||
|
||
```java
|
||
// src/main/java/org/hzhub/gateway/config/RateLimiterConfig.java
|
||
@Configuration
|
||
public class RateLimiterConfig {
|
||
|
||
/**
|
||
* 按 IP 地址限流
|
||
*/
|
||
@Bean
|
||
public KeyResolver ipKeyResolver() {
|
||
return exchange -> {
|
||
String ip = exchange.getRequest().getRemoteAddress() != null
|
||
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
|
||
: "unknown";
|
||
return Mono.just("gateway:ratelimit:" + ip);
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 按路径+IP 限流(更细粒度)
|
||
*/
|
||
@Bean
|
||
public KeyResolver pathIpKeyResolver() {
|
||
return exchange -> {
|
||
String path = exchange.getRequest().getURI().getPath();
|
||
String ip = exchange.getRequest().getRemoteAddress() != null
|
||
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
|
||
: "unknown";
|
||
return Mono.just("gateway:ratelimit:" + path + ":" + ip);
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 3: 后端服务适配
|
||
|
||
#### 3.6 hzhub-ai 的 SecurityConfig 改造
|
||
|
||
在 hzhub-ai 的 `SecurityConfig` 中新增:当请求来自 Gateway 时,跳过 JWT 验证。
|
||
|
||
```java
|
||
// hzhub-ai 的 SecurityConfig.java 中修改
|
||
private boolean isFromGateway(ServerHttpRequest request) {
|
||
String verified = request.getHeaders().getFirst("X-Gateway-Verified");
|
||
return "true".equals(verified);
|
||
}
|
||
```
|
||
|
||
或者更简单:在 Sa-Token 拦截器中添加检查:
|
||
```java
|
||
// 如果 Gateway 已验证,直接放行
|
||
if ("true".equals(request.getHeader("X-Gateway-Verified"))) {
|
||
return true;
|
||
}
|
||
```
|
||
|
||
#### 3.7 hzhub-erp 同理
|
||
|
||
在 hzhub-erp 的 `SecurityConfig.java` 中添加同样的 Gateway 信任逻辑。
|
||
|
||
---
|
||
|
||
### Phase 4: 前端路由切换
|
||
|
||
#### 3.8 hzhub-admin(管理后台)
|
||
|
||
修改 `.env.development`:
|
||
```env
|
||
VITE_GLOB_API_URL=http://localhost:8080
|
||
```
|
||
|
||
修改 `vite.config.ts` 代理:
|
||
```typescript
|
||
'/api': {
|
||
target: 'http://localhost:8080', // 改为 Gateway
|
||
changeOrigin: true,
|
||
rewrite: path => path.replace(/^\/api/, ''),
|
||
}
|
||
```
|
||
|
||
#### 3.9 hzhub-portal-employee(员工门户)
|
||
|
||
修改 `.env.development`:
|
||
```env
|
||
VITE_API_URL=http://localhost:8080
|
||
```
|
||
|
||
`request.ts` 中已自动使用此 baseURL,无需改动。
|
||
|
||
注意:原来直连 ERP 的请求(`/erp/customer/**`)也需经过 Gateway,Gateway 路由已覆盖 `/erp/**`。
|
||
|
||
#### 3.10 hzhub-portal-dealer(经销商门户)
|
||
|
||
同 employee,统一指向 Gateway。
|
||
|
||
---
|
||
|
||
### Phase 5: Docker Compose 集成
|
||
|
||
#### 3.11 新增 hzhub-gateway 服务
|
||
|
||
```yaml
|
||
# docker-compose.yml 新增
|
||
hzhub-gateway:
|
||
build:
|
||
context: ../hzhub-gateway
|
||
dockerfile: Dockerfile
|
||
container_name: hzhub-gateway
|
||
ports:
|
||
- "8080:8080"
|
||
environment:
|
||
SPRING_PROFILES_ACTIVE: prod
|
||
REDIS_HOST: hzhub-redis
|
||
REDIS_PORT: 6379
|
||
AI_HOST: hzhub-ai
|
||
AI_PORT: 6039
|
||
ERP_HOST: hzhub-erp
|
||
ERP_PORT: 8082
|
||
JWT_SECRET: ${JWT_SECRET:-abcdefghijklmnopqrstuvwxyz}
|
||
depends_on:
|
||
redis:
|
||
condition: service_healthy
|
||
hzhub-ai:
|
||
condition: service_started
|
||
hzhub-erp:
|
||
condition: service_started
|
||
networks:
|
||
- hzhub-network
|
||
```
|
||
|
||
#### 3.12 前端 Nginx 代理改为 Gateway
|
||
|
||
```nginx
|
||
# hzhub-portal-employee 的 Nginx 配置
|
||
# 原来: proxy_pass http://hzhub-ai:6039;
|
||
# 改为:
|
||
location /api/ {
|
||
proxy_pass http://hzhub-gateway:8080;
|
||
}
|
||
location /erp/ {
|
||
proxy_pass http://hzhub-gateway:8080;
|
||
}
|
||
```
|
||
|
||
#### 3.13 后端服务关闭外部端口暴露
|
||
|
||
```yaml
|
||
# docker-compose.yml 修改
|
||
hzhub-ai:
|
||
# ports:
|
||
# - "6039:6039" # 不再对外暴露,仅内网访问
|
||
hzhub-erp:
|
||
# ports:
|
||
# - "8082:8082" # 不再对外暴露,仅内网访问
|
||
```
|
||
|
||
---
|
||
|
||
## 四、文件变更清单
|
||
|
||
### 新增文件
|
||
|
||
| 文件 | 说明 |
|
||
|------|------|
|
||
| `hzhub-gateway/src/main/java/org/hzhub/gateway/filter/AuthGlobalFilter.java` | JWT 认证过滤器 |
|
||
| `hzhub-gateway/src/main/java/org/hzhub/gateway/filter/XssGlobalFilter.java` | XSS 过滤 |
|
||
| `hzhub-gateway/src/main/java/org/hzhub/gateway/config/RateLimiterConfig.java` | 限流 Key 解析器 |
|
||
| `hzhub-gateway/Dockerfile` | Gateway 容器构建 |
|
||
|
||
### 修改文件
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `hzhub-gateway/pom.xml` | 新增 sa-token-reactor、redis-reactive 依赖 |
|
||
| `hzhub-gateway/src/main/resources/application.yml` | 重写路由、Redis、Sa-Token 配置 |
|
||
| `hzhub-gateway/src/main/java/org/hzhub/HzhubGatewayApplication.java` | 无需改动 |
|
||
| `hzhub-ai/.../SecurityConfig.java` | 添加 Gateway 信任逻辑 |
|
||
| `hzhub-erp/.../SecurityConfig.java` | 添加 Gateway 信任逻辑 |
|
||
| `hzhub-admin/apps/web-antd/.env.development` | API 地址改为 Gateway |
|
||
| `hzhub-admin/apps/web-antd/vite.config.ts` | 代理目标改为 Gateway |
|
||
| `hzhub-portal-employee/.env.development` | VITE_API_URL 指向 Gateway |
|
||
| `hzhub-portal-dealer/.env.development` | 新增 API URL |
|
||
| `hzhub-deploy/docker-compose.yml` | 新增 Gateway 服务,调整网络 |
|
||
|
||
---
|
||
|
||
## 五、分阶段验证
|
||
|
||
### Phase 1 验证
|
||
```bash
|
||
# 1. 启动 Gateway
|
||
cd hzhub-gateway && mvn spring-boot:run
|
||
|
||
# 2. 测试路由转发
|
||
curl http://localhost:8080/erp/test/connection
|
||
# 应返回 SQL Server 连接信息
|
||
|
||
# 3. 测试鉴权(未登录)
|
||
curl http://localhost:8080/ai/chat/message
|
||
# 应返回 401
|
||
|
||
# 4. 测试鉴权(带 Token)
|
||
curl -H "Authorization: Bearer <token>" http://localhost:8080/ai/chat/message
|
||
# 应正常返回结果
|
||
```
|
||
|
||
### Phase 2 验证
|
||
```bash
|
||
# 测试 XSS 过滤
|
||
curl -X POST http://localhost:8080/ai/xxx \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"content": "<script>alert(1)</script>"}'
|
||
# 请求体应被清洗
|
||
|
||
# 测试限流(快速连续请求)
|
||
for i in {1..30}; do curl -s http://localhost:8080/ai/xxx; done
|
||
# 超过阈值应返回 429
|
||
```
|
||
|
||
### Phase 3 验证
|
||
```bash
|
||
# 前端登录后,所有请求走 Gateway
|
||
# 确认 AI 接口、ERP 接口均正常工作
|
||
# 确认 Token 过期后自动跳转登录页
|
||
```
|
||
|
||
---
|
||
|
||
## 六、风险与注意事项
|
||
|
||
| 风险 | 应对 |
|
||
|------|------|
|
||
| Sa-Token JWT 验证方式与后端不一致 | 统一使用 `StpLogicJwtForSimple`,共享 `jwt-secret-key` |
|
||
| Gateway 成为单点故障 | 后续可部署多实例 + Nginx 负载均衡 |
|
||
| SSE 流式响应经过 Gateway 可能超时 | 需在 Gateway 配置路由超时时间(默认无超时) |
|
||
| CORS 配置冲突 | Gateway 统一处理 CORS,后端服务关闭 CORS |
|
||
| 开发环境直连 vs 生产走 Gateway | 后端服务保留双重模式:有 `X-Gateway-Verified` 跳过验证,否则自行验证 |
|
||
|
||
---
|
||
|
||
## 七、后续优化方向
|
||
|
||
1. **服务注册与发现**:引入 Nacos,替代硬编码的服务地址
|
||
2. **熔断降级**:引入 Resilience4j,对下游服务做熔断保护
|
||
3. **链路追踪**:引入 SkyWalking 或 Micrometer Trace
|
||
4. **灰度发布**:基于 Header 的蓝绿部署路由
|
||
5. **API 文档聚合**:Gateway 聚合 Swagger/Knife4j 文档
|