feat(gateway): Implement gateway module foundation
This commit is contained in:
@@ -24,6 +24,12 @@
|
|||||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba Nacos Config -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Cloud Alibaba Sentinel -->
|
<!-- Spring Cloud Alibaba Sentinel -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
@@ -41,5 +47,55 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Commons IO -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.15.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JJWT (JSON Web Token) -->
|
||||||
|
<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>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<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>
|
</project>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.nuaa.video.gateway.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.reactive.CorsWebFilter;
|
||||||
|
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.util.pattern.PathPatternParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨域配置
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class CorsConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsWebFilter corsFilter() {
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.addAllowedOriginPattern("*"); // 允许所有来源
|
||||||
|
config.addAllowedHeader("*"); // 允许所有请求头
|
||||||
|
config.addAllowedMethod("*"); // 允许所有方法
|
||||||
|
config.setAllowCredentials(true); // 允许携带凭证
|
||||||
|
config.setMaxAge(3600L); // 预检请求的有效期,单位为秒
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
|
||||||
|
return new CorsWebFilter(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.nuaa.video.gateway.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||||
|
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||||
|
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
|
||||||
|
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.ApplicationEventPublisherAware;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态路由配置
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class DynamicRouteConfig implements ApplicationEventPublisherAware {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RouteDefinitionWriter routeDefinitionWriter;
|
||||||
|
|
||||||
|
private ApplicationEventPublisher publisher;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||||
|
this.publisher = applicationEventPublisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加路由
|
||||||
|
*/
|
||||||
|
public void addRoute(RouteDefinition definition) {
|
||||||
|
try {
|
||||||
|
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
|
||||||
|
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||||
|
log.info("成功添加路由: {}", definition.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("添加路由失败: {}", definition.getId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新路由
|
||||||
|
*/
|
||||||
|
public void updateRoute(RouteDefinition definition) {
|
||||||
|
try {
|
||||||
|
this.deleteRoute(definition.getId());
|
||||||
|
this.addRoute(definition);
|
||||||
|
log.info("成功更新路由: {}", definition.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新路由失败: {}", definition.getId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除路由
|
||||||
|
*/
|
||||||
|
public void deleteRoute(String id) {
|
||||||
|
try {
|
||||||
|
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
|
||||||
|
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||||
|
log.info("成功删除路由: {}", id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除路由失败: {}", id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.nuaa.video.gateway.config;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
|
||||||
|
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
|
||||||
|
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
|
||||||
|
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关降级配置
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class GatewayFallbackConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化自定义限流响应和规则
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void doInit() {
|
||||||
|
// 自定义限流响应
|
||||||
|
initBlockHandlers();
|
||||||
|
// 初始化QPS限流规则
|
||||||
|
initQpsLimitRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义限流响应
|
||||||
|
*/
|
||||||
|
private void initBlockHandlers() {
|
||||||
|
BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("code", HttpStatus.TOO_MANY_REQUESTS.value());
|
||||||
|
response.put("message", "请求过于频繁,请稍后再试");
|
||||||
|
|
||||||
|
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.body(BodyInserters.fromValue(response));
|
||||||
|
};
|
||||||
|
|
||||||
|
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化QPS限流规则
|
||||||
|
*/
|
||||||
|
private void initQpsLimitRule() {
|
||||||
|
Set<GatewayFlowRule> rules = new HashSet<>();
|
||||||
|
|
||||||
|
// 对每个API路径设置限流
|
||||||
|
// 用户服务限流
|
||||||
|
rules.add(new GatewayFlowRule("video-user")
|
||||||
|
.setCount(100)
|
||||||
|
.setIntervalSec(1));
|
||||||
|
|
||||||
|
// 内容服务限流
|
||||||
|
rules.add(new GatewayFlowRule("video-content")
|
||||||
|
.setCount(200)
|
||||||
|
.setIntervalSec(1));
|
||||||
|
|
||||||
|
// 认证服务限流
|
||||||
|
rules.add(new GatewayFlowRule("video-auth")
|
||||||
|
.setCount(50)
|
||||||
|
.setIntervalSec(1));
|
||||||
|
|
||||||
|
// 加载限流规则
|
||||||
|
GatewayRuleManager.loadRules(rules);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.nuaa.video.gateway.filter;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
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.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT认证过滤器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
|
@Value("${jwt.secret:video-platform-secret-key-2024-nuaa}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@Value("${jwt.token-header:Authorization}")
|
||||||
|
private String tokenHeader;
|
||||||
|
|
||||||
|
@Value("${gateway.ignore.urls:/auth/login,/auth/register,/auth/captcha}")
|
||||||
|
private List<String> ignoreUrls;
|
||||||
|
|
||||||
|
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
|
String path = request.getURI().getPath();
|
||||||
|
|
||||||
|
// 跳过不需要验证的路径
|
||||||
|
if (isIgnorePath(path)) {
|
||||||
|
return chain.filter(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取token
|
||||||
|
String token = getToken(request);
|
||||||
|
if (!StringUtils.hasText(token)) {
|
||||||
|
return unauthorized(exchange, "缺少token");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 验证token
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
|
||||||
|
// 将用户信息传递到下游服务
|
||||||
|
ServerHttpRequest.Builder mutate = request.mutate();
|
||||||
|
mutate.header("userId", claims.getSubject());
|
||||||
|
mutate.header("username", claims.get("username", String.class));
|
||||||
|
return chain.filter(exchange.mutate().request(mutate.build()).build());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Token验证失败", e);
|
||||||
|
return unauthorized(exchange, "Token验证失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return Ordered.HIGHEST_PRECEDENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为忽略路径
|
||||||
|
*/
|
||||||
|
private boolean isIgnorePath(String path) {
|
||||||
|
return ignoreUrls.stream().anyMatch(ignore -> pathMatcher.match(ignore, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求中获取token
|
||||||
|
*/
|
||||||
|
private String getToken(ServerHttpRequest request) {
|
||||||
|
String token = request.getHeaders().getFirst(tokenHeader);
|
||||||
|
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
|
||||||
|
return token.substring(7);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析token
|
||||||
|
*/
|
||||||
|
private Claims parseToken(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.verifyWith(getSigningKey())
|
||||||
|
.build()
|
||||||
|
.parseSignedClaims(token)
|
||||||
|
.getPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取签名密钥
|
||||||
|
*/
|
||||||
|
private SecretKey getSigningKey() {
|
||||||
|
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||||
|
return Keys.hmacShaKeyFor(keyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回未授权响应
|
||||||
|
*/
|
||||||
|
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
|
||||||
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
|
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("code", HttpStatus.UNAUTHORIZED.value());
|
||||||
|
result.put("message", message);
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] bytes = objectMapper.writeValueAsBytes(result);
|
||||||
|
DataBuffer buffer = response.bufferFactory().wrap(bytes);
|
||||||
|
return response.writeWith(Mono.just(buffer));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("响应JSON转换异常", e);
|
||||||
|
return response.setComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.nuaa.video.gateway.filter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求日志过滤器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class RequestLogFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
|
URI uri = request.getURI();
|
||||||
|
String method = request.getMethod().name();
|
||||||
|
String host = request.getRemoteAddress().getHostString();
|
||||||
|
|
||||||
|
// 记录请求日志
|
||||||
|
log.info("Request: {} {} from {}", method, uri, host);
|
||||||
|
|
||||||
|
// 记录请求头信息(生产环境中可以根据需要调整日志级别)
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
Map<String, List<String>> headers = request.getHeaders();
|
||||||
|
headers.forEach((name, values) ->
|
||||||
|
log.debug("Header: {} = {}", name, String.join(",", values)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行后续过滤器
|
||||||
|
return chain.filter(exchange).then(Mono.fromRunnable(() ->
|
||||||
|
log.info("Response for: {} {}", method, uri)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return Ordered.HIGHEST_PRECEDENCE + 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package com.nuaa.video.gateway.handler;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.HttpStatusCode; // 导入HttpStatusCode
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局异常处理器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Order(-1)
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||||
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
|
|
||||||
|
// 设置响应的状态码
|
||||||
|
if (response.isCommitted()) {
|
||||||
|
return Mono.error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpStatusCode status; // 修改为HttpStatusCode类型
|
||||||
|
String message;
|
||||||
|
int statusCode; // 添加一个int类型的状态码变量
|
||||||
|
|
||||||
|
// 根据异常类型设置响应状态和消息
|
||||||
|
if (ex instanceof ResponseStatusException) {
|
||||||
|
ResponseStatusException responseStatusException = (ResponseStatusException) ex;
|
||||||
|
status = responseStatusException.getStatusCode();
|
||||||
|
message = responseStatusException.getReason();
|
||||||
|
statusCode = status.value(); // 获取状态码的数值
|
||||||
|
} else {
|
||||||
|
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
message = "服务器内部错误";
|
||||||
|
statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.error("网关异常: {}", message, ex);
|
||||||
|
|
||||||
|
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
response.setStatusCode(status);
|
||||||
|
|
||||||
|
// 构建响应体
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("code", statusCode); // 使用状态码数值
|
||||||
|
result.put("message", message);
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] bytes = objectMapper.writeValueAsBytes(result);
|
||||||
|
DataBuffer buffer = response.bufferFactory().wrap(bytes);
|
||||||
|
return response.writeWith(Mono.just(buffer));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("响应JSON转换异常", e);
|
||||||
|
return response.setComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
sentinel:
|
||||||
|
log:
|
||||||
|
dir: ./logs/sentinel/
|
||||||
|
switch-pid: true
|
||||||
|
gateway:
|
||||||
|
httpclient:
|
||||||
|
connect-timeout: 60000
|
||||||
|
response-timeout: 60000
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.springframework.cloud.gateway: debug
|
||||||
|
reactor.netty.http.client: debug
|
||||||
@@ -4,12 +4,26 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: video-gateway
|
name: video-gateway
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- optional:nacos:${spring.application.name}.yaml
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
discovery:
|
discovery:
|
||||||
server-addr: 5.180.253.115:8848
|
server-addr: ${NACOS_HOST:5.180.253.115}:${NACOS_PORT:8848}
|
||||||
namespace: public
|
namespace: ${NACOS_NAMESPACE:public}
|
||||||
group: DEFAULT_GROUP
|
group: ${NACOS_GROUP:DEFAULT_GROUP}
|
||||||
|
config:
|
||||||
|
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||||
|
namespace: ${spring.cloud.nacos.discovery.namespace}
|
||||||
|
group: ${spring.cloud.nacos.discovery.group}
|
||||||
|
file-extension: yaml
|
||||||
|
extension-configs:
|
||||||
|
- data-id: video-gateway.yaml
|
||||||
|
group: ${NACOS_GROUP:DEFAULT_GROUP}
|
||||||
|
refresh: true
|
||||||
gateway:
|
gateway:
|
||||||
discovery:
|
discovery:
|
||||||
locator:
|
locator:
|
||||||
@@ -54,7 +68,7 @@ spring:
|
|||||||
- StripPrefix=1
|
- StripPrefix=1
|
||||||
sentinel:
|
sentinel:
|
||||||
transport:
|
transport:
|
||||||
dashboard: 5.180.253.115:8080
|
dashboard: ${SENTINEL_DASHBOARD_HOST:5.180.253.115}:${SENTINEL_DASHBOARD_PORT:8080}
|
||||||
port: 8719
|
port: 8719
|
||||||
eager: true
|
eager: true
|
||||||
scg:
|
scg:
|
||||||
@@ -67,3 +81,21 @@ management:
|
|||||||
web:
|
web:
|
||||||
exposure:
|
exposure:
|
||||||
include: '*'
|
include: '*'
|
||||||
|
endpoint:
|
||||||
|
health:
|
||||||
|
show-details: always
|
||||||
|
gateway:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# 不需要鉴权的URL列表
|
||||||
|
gateway:
|
||||||
|
ignore:
|
||||||
|
urls:
|
||||||
|
- /auth/login
|
||||||
|
- /auth/register
|
||||||
|
- /auth/captcha
|
||||||
|
- /*/v3/api-docs
|
||||||
|
- /swagger-resources/**
|
||||||
|
- /swagger-ui/**
|
||||||
|
- /actuator/**
|
||||||
|
- /favicon.ico
|
||||||
Reference in New Issue
Block a user