Chat2Excel 项目网关服务开发 一、编写 bootstrap 配置文件spring: application: name: gateway-service main: web-application-type: reactive # 设置为响应式应用类型避免与Spring MVC冲突 # Jackson配置 jackson: date-format: HH:mm:ss time-zone: UTC serialization: write-dates-as-timestamps: false fail-on-empty-beans: false deserialization: fail-on-unknown-properties: false fail-on-null-for-primitives: false accept-empty-string-as-null-object: true accept-single-value-as-array: true # Redis配置 - Gateway 服务使用更长的超时时间 data: redis: host: *** port: 6379 password: *** database: 0 timeout: 10000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: -1ms # Nacos服务发现配置 cloud: nacos: discovery: server-addr: ***:8848 username: nacos password: *** namespace: public enabled: true # 启用 Nacos 服务发现 gateway: # 默认过滤器 default-filters: - DedupeResponseHeaderAccess-Control-Allow-Credentials Access-Control-Allow-Origin # - JwtAuth # 全局跨域配置 globalcors: cors-configurations: [/**]: # 允许跨域的源使用 allowedOriginPatterns 支持通配符和凭证 allowedOriginPatterns: * # 允许跨域的HTTP方法 allowedMethods: - GET - POST - PUT - DELETE - OPTIONS # 允许跨域的请求头 allowedHeaders: * # 允许携带凭证 allowCredentials: true # 跨域预检的有效期单位为秒 maxAge: 3600 # 路由配置 routes: # 用户服务路由 - id: user-service uri: lb://user-service predicates: - Path/api/v1/users/** filters: - StripPrefix2 # 去掉 /api/v1保留后续路径 # 文件服务路由 - id: file-service uri: lb://file-service predicates: - Path/api/v1/files/** filters: - StripPrefix2 # 去掉 /api/v1保留后续路径 # AI服务路由 - llm接口 - id: ai-service-llm uri: lb://ai-service predicates: - Path/api/v1/llm/** filters: - StripPrefix2 # 去掉 /api/v1保留后续路径 # AI服务路由 - ai接口 - id: ai-service-ai uri: lb://ai-service predicates: - Path/api/v1/ai/** filters: - StripPrefix2 # 去掉 /api/v1保留后续路径 # MyBatis Plus公共配置 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto table-underline: true # 安全配置 - 网关令牌所有服务共享 security: gateway: token: internal-gateway-secret-token-2024 enabled: true # 设置为 false 可以禁用网关检查开发环境 whitelist: - /users/auth - /users/verification-code # 自定义日志配置 logging: pattern: console: %clr(%d{HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){cyan} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx二、创建配置类package com.linzhixin.gateway.config; 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; /** * 网关配置类(空路由在bootstrap里面已经配置类路由规则其实这个配置类可以省略) */ Configuration public class GateWayConfig { /** * 自定义路由配置 * param builder RouteLocatorBuilder构造器 * return RouteLocator实例对象 */ Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes().build(); } }package com.linzhixin.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.data.redis.core.ReactiveStringRedisTemplate; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * Redis 配置类 * Spring 确实会自动配置但默认序列化方式不友好。这个配置类主要是为了把序列化方式改成 String确保 Redis 数据可读、可共享。 */ Configuration public class RedisConfig { /** * 构建 ReactiveRedisTemplate -》 用于在响应式编程环境中如 Gateway/WebFlux异步操作 Redis。 * param connectionFactory 连接工厂 * return ReactiveRedisTemplate示例对象 */ Bean public ReactiveRedisTemplateString, String reactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory) { // 1、序列化 // StringRedisSerializer 是 Spring Data Redis 提供的一个序列化器负责 Java 对象 ↔ Redis 存储格式 的转换。 StringRedisSerializer serializer new StringRedisSerializer(); // 2、创建 builder RedisSerializationContext.RedisSerializationContextBuilderString, String builder RedisSerializationContext.newSerializationContext(); RedisSerializationContextString, String context builder .key(serializer) .value(serializer) .hashKey(serializer) .hashValue(serializer) .build(); return new ReactiveRedisTemplate(connectionFactory, context); } }三、实现令牌过滤器写在 common-service 中package com.linzhixin.common.filter; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.io.IOException; /** * 网关令牌验证过滤器 * GatewayTokenFilter 会作用于所有依赖 common-service 的服务只要这些服务启动时被 Spring 扫描到。 */ Component Slf4j Order(1) public class GatewayTokenFilter implements Filter { /** * 网关令牌 */ Value(${security.gateway.token:}) private String gatewayToken; /** * 是否启用网关令牌(网关控制器) */ Value(${security.gateway.enabled:true}) private boolean gatewayCheckEnable; Override public void init(FilterConfig filterConfig) throws ServletException { if (gatewayCheckEnable) { log.info(网关令牌过滤器已经启动); } else { log.info(允许直接访问后端); } } Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 1、需要请求request HttpServletRequest httpServletRequest (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse (HttpServletResponse) servletResponse; String path httpServletRequest.getRequestURI(); String method httpServletRequest.getMethod(); // 2、网关控制器做判断 if(!gatewayCheckEnable) { //将请求和响应传递给过滤器链中的下一个过滤器如果已是最后一个过滤器则传递给目标 Servlet/Controller。 filterChain.doFilter(servletRequest, servletResponse); return; } // 3、获取网关令牌 String gatewayToke httpServletRequest.getHeader(X-Gateway-Token); if(gatewayToke null || gatewayToke.isEmpty()) { log.warn(网关令牌为空); unAuthorized(httpServletResponse, 禁止访问); return; } // 4、验证网关令牌 if(!gatewayToke.equals(this.gatewayToken)) { log.warn(网关令牌错误); unAuthorized(httpServletResponse, 禁止访问); return; } log.info(网关令牌验证通过:{} {}, method, path); filterChain.doFilter(httpServletRequest, httpServletResponse); } Override public void destroy() { Filter.super.destroy(); } private void unAuthorized(HttpServletResponse response, String message) { response.setStatus(403); //设置 HTTP 响应的内容类型和字符编码告诉浏览器/客户端我返回的是 JSON 数据用 UTF-8 解码。 response.setContentType(application/json;charsetUTF-8); //用 String.format 把错误码和错误信息动态拼成 JSON 字符串。 // { : JSON 对象开始 // \code\ : 转义双引号 → 表示 JSON 的 key 是 code //:%d 占位符填入整数 String json String.format( {\code\:%d,\message\:\%s\}, 403, message ); try { //获取响应的字符输出流将 JSON 字符串写入响应体发送给客户端。 response.getWriter().write(json); } catch (IOException e) { throw new RuntimeException(e); } } }