1
This commit is contained in:
139
pom.xml
Normal file
139
pom.xml
Normal file
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.aida</groupId>
|
||||
<artifactId>aida-gateway</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>aida-gateway</name>
|
||||
<description>Gateway module for unified authentication</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<spring-cloud.version>2023.0.1</spring-cloud.version>
|
||||
<spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Cloud Gateway -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Nacos Discovery -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Nacos Config -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.12.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.12.3</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.12.3</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Hutool (aligned with aida_seller 5.8.26) -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.26</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis (for token blacklist) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Actuator (health check) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
|
||||
<version>${spring-cloud-alibaba.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
14
src/main/java/com/aida/gateway/AidaGatewayApplication.java
Normal file
14
src/main/java/com/aida/gateway/AidaGatewayApplication.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.aida.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
public class AidaGatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AidaGatewayApplication.class, args);
|
||||
System.out.println("AidaGatewayApplication 启动完成!");
|
||||
}
|
||||
}
|
||||
36
src/main/java/com/aida/gateway/common/AuthConstants.java
Normal file
36
src/main/java/com/aida/gateway/common/AuthConstants.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.aida.gateway.common;
|
||||
|
||||
/**
|
||||
* Auth-related constants shared between Gateway and downstream services.
|
||||
*/
|
||||
public final class AuthConstants {
|
||||
|
||||
private AuthConstants() {}
|
||||
|
||||
/** 请求头:用户 ID(Gateway 验证后写入) */
|
||||
public static final String USER_ID_HEADER = "X-User-Id";
|
||||
|
||||
/** 请求头:用户信息的 JSON 字符串(Gateway 验证后写入) */
|
||||
public static final String USER_INFO_HEADER = "X-User-Info";
|
||||
|
||||
/** 客户端传来的原始 JWT token 头 */
|
||||
public static final String TOKEN_HEADER = "Authorization";
|
||||
|
||||
/** JWT token 前缀(带连字符) */
|
||||
public static final String TOKEN_PREFIX = "Bearer-";
|
||||
|
||||
/** Redis 黑名单 key 前缀 */
|
||||
public static final String BLACKLIST_PREFIX = "jwt:blacklist:";
|
||||
|
||||
/** 响应码:未认证 */
|
||||
public static final int STATUS_UNAUTHORIZED = 401;
|
||||
|
||||
/** 响应码:禁止访问 */
|
||||
public static final int STATUS_FORBIDDEN = 403;
|
||||
|
||||
/** 响应消息 */
|
||||
public static final String MSG_MISSING_TOKEN = "请传入token";
|
||||
public static final String MSG_INVALID_TOKEN = "Token无效";
|
||||
public static final String MSG_TOKEN_EXPIRED = "Token已过期,请重新登录";
|
||||
public static final String MSG_TOKEN_BLACKLISTED = "Token已失效,请重新登录";
|
||||
}
|
||||
25
src/main/java/com/aida/gateway/common/AuthPrincipalVo.java
Normal file
25
src/main/java/com/aida/gateway/common/AuthPrincipalVo.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.aida.gateway.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户主体信息(从 JWT subject 解析)。
|
||||
* 与 aida_back_001 的 AuthPrincipalVo 结构保持一致。
|
||||
*/
|
||||
@Data
|
||||
public class AuthPrincipalVo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String username;
|
||||
private String avatarUrl;
|
||||
private Boolean isAdmin;
|
||||
private String source;
|
||||
private Integer status;
|
||||
private String language;
|
||||
private String country;
|
||||
private List<String> authorities;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.aida.gateway.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "gateway.auth")
|
||||
public class GatewayAuthProperties {
|
||||
|
||||
private String jwtSecret = "JWTSECRET";
|
||||
|
||||
private String jwtTokenHeader = "Authorization";
|
||||
|
||||
private String jwtTokenPrefix = "Bearer-";
|
||||
|
||||
private List<String> ignorePaths;
|
||||
|
||||
private boolean blacklistEnabled = true;
|
||||
}
|
||||
28
src/main/java/com/aida/gateway/config/RedisConfig.java
Normal file
28
src/main/java/com/aida/gateway/config/RedisConfig.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package com.aida.gateway.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public ReactiveRedisTemplate<String, String> reactiveRedisTemplate(
|
||||
ReactiveRedisConnectionFactory factory) {
|
||||
StringRedisSerializer serializer = new StringRedisSerializer();
|
||||
RedisSerializationContext<String, String> context = RedisSerializationContext
|
||||
.<String, String>newSerializationContext()
|
||||
.key(serializer)
|
||||
.value(serializer)
|
||||
.hashKey(serializer)
|
||||
.hashValue(serializer)
|
||||
.build();
|
||||
return new ReactiveRedisTemplate<>(factory, context);
|
||||
}
|
||||
}
|
||||
170
src/main/java/com/aida/gateway/filter/GlobalAuthWebFilter.java
Normal file
170
src/main/java/com/aida/gateway/filter/GlobalAuthWebFilter.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package com.aida.gateway.filter;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import com.aida.gateway.common.AuthConstants;
|
||||
import com.aida.gateway.common.AuthPrincipalVo;
|
||||
import com.aida.gateway.config.GatewayAuthProperties;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Gateway 全局鉴权过滤器。
|
||||
* <p>
|
||||
* 流程:
|
||||
* 1. 白名单路径直接放行
|
||||
* 2. 从请求头读取 JWT,验证签名和有效期
|
||||
* 3. 检查 Redis 黑名单(logout 后 token 被拉黑)
|
||||
* 4. 将用户 ID 和用户信息 JSON 写入下游请求头
|
||||
* 5. 失败返回 401 JSON
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class GlobalAuthWebFilter implements WebFilter {
|
||||
|
||||
private final GatewayAuthProperties authProperties;
|
||||
@Qualifier("reactiveRedisTemplate")
|
||||
private final ReactiveRedisTemplate<String, String> redisTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
String path = exchange.getRequest().getURI().getPath();
|
||||
|
||||
// 1. 白名单直接放行
|
||||
if (isIgnoredPath(path)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 2. 提取 token
|
||||
String rawHeader = exchange.getRequest().getHeaders()
|
||||
.getFirst(authProperties.getJwtTokenHeader());
|
||||
if (StrUtil.isBlank(rawHeader)) {
|
||||
return writeUnauthorized(exchange, AuthConstants.MSG_MISSING_TOKEN);
|
||||
}
|
||||
|
||||
String token = rawHeader;
|
||||
if (rawHeader.startsWith(authProperties.getJwtTokenPrefix())) {
|
||||
token = rawHeader.substring(authProperties.getJwtTokenPrefix().length());
|
||||
}
|
||||
|
||||
// 3. JWT 签名验证
|
||||
Claims claims;
|
||||
try {
|
||||
claims = parseToken(token);
|
||||
} catch (Exception e) {
|
||||
log.warn("JWT signature invalid or expired: {}", e.getMessage());
|
||||
return writeUnauthorized(exchange, AuthConstants.MSG_TOKEN_EXPIRED);
|
||||
}
|
||||
|
||||
// 4. 解析用户信息
|
||||
AuthPrincipalVo principal;
|
||||
try {
|
||||
principal = objectMapper.readValue(claims.getSubject(), AuthPrincipalVo.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse AuthPrincipalVo from JWT subject: {}", e.getMessage());
|
||||
return writeUnauthorized(exchange, AuthConstants.MSG_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
if (principal == null || principal.getId() == null) {
|
||||
return writeUnauthorized(exchange, AuthConstants.MSG_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
// 5. 黑名单检查(仅当启用时)
|
||||
if (authProperties.isBlacklistEnabled()) {
|
||||
String blacklistKey = AuthConstants.BLACKLIST_PREFIX + principal.getId();
|
||||
Boolean isBlacklisted = redisTemplate.hasKey(blacklistKey).block();
|
||||
if (Boolean.TRUE.equals(isBlacklisted)) {
|
||||
return writeUnauthorized(exchange, AuthConstants.MSG_TOKEN_BLACKLISTED);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 写入下游请求头
|
||||
String userInfoJson;
|
||||
try {
|
||||
userInfoJson = objectMapper.writeValueAsString(principal);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to serialize AuthPrincipalVo", e);
|
||||
return writeUnauthorized(exchange, AuthConstants.MSG_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
|
||||
.header(AuthConstants.USER_ID_HEADER, String.valueOf(principal.getId()))
|
||||
.header(AuthConstants.USER_INFO_HEADER, userInfoJson)
|
||||
.build();
|
||||
|
||||
return chain.filter(exchange.mutate().request(mutatedRequest).build());
|
||||
}
|
||||
|
||||
private boolean isIgnoredPath(String requestUri) {
|
||||
if (authProperties.getIgnorePaths() == null) {
|
||||
return false;
|
||||
}
|
||||
for (String pattern : authProperties.getIgnorePaths()) {
|
||||
if (matches(pattern, requestUri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matches(String pattern, String uri) {
|
||||
if (pattern.endsWith("/**")) {
|
||||
String prefix = pattern.substring(0, pattern.length() - 3);
|
||||
return uri.startsWith(prefix);
|
||||
}
|
||||
if (pattern.endsWith("/*")) {
|
||||
String prefix = pattern.substring(0, pattern.length() - 2);
|
||||
if (!uri.startsWith(prefix)) return false;
|
||||
String suffix = uri.substring(prefix.length());
|
||||
return !suffix.contains("/");
|
||||
}
|
||||
return uri.contains(pattern);
|
||||
}
|
||||
|
||||
private Claims parseToken(String token) {
|
||||
SecretKey key = buildSigningKey();
|
||||
return Jwts.parser()
|
||||
.verifyWith(key)
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
private SecretKey buildSigningKey() {
|
||||
byte[] raw = authProperties.getJwtSecret().getBytes(StandardCharsets.UTF_8);
|
||||
if (raw.length < 32) {
|
||||
raw = DigestUtil.sha256(raw);
|
||||
}
|
||||
return Keys.hmacShaKeyFor(raw);
|
||||
}
|
||||
|
||||
private Mono<Void> writeUnauthorized(ServerWebExchange exchange, String message) {
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
String body = String.format("{\"code\":401,\"message\":\"%s\"}", message);
|
||||
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
|
||||
return response.writeWith(Mono.just(buffer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.aida.gateway.filter;
|
||||
|
||||
import com.aida.gateway.common.AuthConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Logout 黑名单接口。
|
||||
* 服务调用此端点将指定用户的 token 加入 Redis 黑名单,
|
||||
* Gateway 会在下次请求时拒绝已拉黑的 token。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class LogoutBlacklistWebFilter implements WebFilter {
|
||||
|
||||
private final
|
||||
@Qualifier("reactiveRedisTemplate")
|
||||
ReactiveRedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
// 仅处理 /internal/logout 路径
|
||||
if (!exchange.getRequest().getURI().getPath().equals("/internal/logout")) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
if (!"POST".equalsIgnoreCase(exchange.getRequest().getMethod().name())) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 从请求头读取 X-User-Id(内部调用,不需要鉴权)
|
||||
String userId = exchange.getRequest().getHeaders().getFirst(AuthConstants.USER_ID_HEADER);
|
||||
if (userId == null || userId.isBlank()) {
|
||||
return writeResponse(exchange, HttpStatus.BAD_REQUEST, "{\"code\":400,\"message\":\"userId required\"}");
|
||||
}
|
||||
|
||||
String blacklistKey = AuthConstants.BLACKLIST_PREFIX + userId;
|
||||
|
||||
// 黑名单 TTL 设为 7 天(与 JWT 有效期保持一致)
|
||||
return redisTemplate.opsForValue()
|
||||
.set(blacklistKey, "1")
|
||||
.then(redisTemplate.expire(blacklistKey, Duration.ofDays(7)))
|
||||
.then(writeResponse(exchange, HttpStatus.OK, "{\"code\":200,\"message\":\"ok\"}"))
|
||||
.onErrorResume(e -> {
|
||||
log.error("Failed to add token to blacklist, userId={}", userId, e);
|
||||
return writeResponse(exchange, HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"{\"code\":500,\"message\":\"internal error\"}");
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Void> writeResponse(ServerWebExchange exchange, HttpStatus status, String body) {
|
||||
exchange.getResponse().setStatusCode(status);
|
||||
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
return exchange.getResponse().writeWith(
|
||||
Mono.just(exchange.getResponse().bufferFactory()
|
||||
.wrap(body.getBytes(StandardCharsets.UTF_8))));
|
||||
}
|
||||
}
|
||||
37
src/main/java/com/aida/gateway/route/RouteConfig.java
Normal file
37
src/main/java/com/aida/gateway/route/RouteConfig.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.aida.gateway.route;
|
||||
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 路由配置。
|
||||
* <p>
|
||||
* 注意:实际生产环境中建议将路由配置放在 Nacos 配置中心。
|
||||
* StripPrefix=1 将 /seller 前缀剥离,例如:
|
||||
* /seller/designer/check -> /designer/check (发到 aida-seller 的 /api/designer/check)
|
||||
*/
|
||||
@Configuration
|
||||
public class RouteConfig {
|
||||
|
||||
@Bean
|
||||
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
// /internal/** 用于内部服务调用(如 logout 黑名单),不需要 stripPrefix
|
||||
.route("aida-gateway-internal", r -> r
|
||||
.path("/internal/**")
|
||||
.uri("forward:/internal"))
|
||||
// aida-seller 服务
|
||||
.route("aida-seller", r -> r
|
||||
.path("/seller/**")
|
||||
.filters(f -> f.stripPrefix(1))
|
||||
.uri("lb://aida-seller"))
|
||||
// aida-back_001 服务
|
||||
.route("aida-back", r -> r
|
||||
.path("/api/**")
|
||||
.filters(f -> f.stripPrefix(1))
|
||||
.uri("lb://aida-back"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
60
src/main/resources/application.yml
Normal file
60
src/main/resources/application.yml
Normal file
@@ -0,0 +1,60 @@
|
||||
# ============================================================
|
||||
# aida-gateway - 本地配置(不区分环境)
|
||||
# 公共配置(DB、Redis、RabbitMQ、MinIO、API Keys 等)由 Nacos 统一管理
|
||||
# ============================================================
|
||||
|
||||
server:
|
||||
port: 5569
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: aida-gateway
|
||||
|
||||
# ---------- Gateway JWT 认证(gateway 独有) ----------
|
||||
gateway:
|
||||
auth:
|
||||
jwt-secret: ${BACK_JWT_SECRET:JWTSECRET}
|
||||
jwt-token-header: Authorization
|
||||
jwt-token-prefix: Bearer-
|
||||
blacklist-enabled: true
|
||||
ignore-paths:
|
||||
- /favicon.ico
|
||||
- /doc.html
|
||||
- /swagger-ui.html
|
||||
- /swagger-ui/**
|
||||
- /swagger-resources/**
|
||||
- /v2/api-docs
|
||||
- /v3/api-docs/**
|
||||
- /webjars/**
|
||||
- /api/account/login
|
||||
- /api/account/preLogin
|
||||
- /api/designer/check
|
||||
- /actuator/**
|
||||
- /internal/**
|
||||
- /api/account/sendEmail
|
||||
- /api/account/noLoginRequired
|
||||
- /api/account/resetPwd
|
||||
- /api/account/designWorksRegister
|
||||
- /api/account/questionnaire
|
||||
- /api/account/schoolLogin
|
||||
- /api/account/enterpriseLogin
|
||||
- /api/account/organizationNameSearch
|
||||
- /api/account/activateNewEmail
|
||||
- /api/python/saveGeneratePicture
|
||||
- /api/python/getLibraryByUserId
|
||||
- /api/python/flush
|
||||
- /api/account/healthy
|
||||
- /api/third/party/**
|
||||
- /api/element/initDefaultSysFile
|
||||
- /api/ali-pay/trade/notify
|
||||
- /api/paypal/ipn/back
|
||||
- /api/alipay-hk/trade/notify
|
||||
- /api/stripe/trade/notify
|
||||
- /api/portfolio/**
|
||||
- /api/global-award/**
|
||||
- /api/llm/stream
|
||||
- /notification/**
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.aida.gateway: debug
|
||||
20
src/main/resources/bootstrap.yml
Normal file
20
src/main/resources/bootstrap.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
# ============================================================
|
||||
# aida-gateway - Bootstrap
|
||||
# 通过 NACOS_NAMESPACE 环境变量切换命名空间(dev / test / prod)
|
||||
# 示例:docker run -e NACOS_NAMESPACE=prod ...
|
||||
# ============================================================
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: aida-gateway
|
||||
config:
|
||||
import: optional:nacos:aida-public-${NACOS_NAMESPACE:dev}.yml
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: ${NACOS_HOST:127.0.0.1:8848}
|
||||
namespace: ${NACOS_NAMESPACE:dev}
|
||||
config:
|
||||
server-addr: ${NACOS_HOST:127.0.0.1:8848}
|
||||
namespace: ${NACOS_NAMESPACE:dev}
|
||||
file-extension: yaml
|
||||
Reference in New Issue
Block a user