SpringCloud网关

网关:网络关口,负责请求路由,转发,身份校验

一、配置网关的路由转发规则

整体流程 :

  1. 前端发起请求 http://127.0.0.1:8080/items/list,请求先到达网关。
  2. 网关从 Nacos 注册中心拉取微服务列表
  3. 网关读取路由配置,用断言 (predicates) 判断当前请求路径是否匹配规则。
  4. 如果匹配成功,网关通过lb://负载均衡,把请求转发给对应的微服务。
  5. 微服务处理完请求,结果原路返回给前端。

Nacos 两个作用:

  • 微服务启动 → 服务注册,把自己信息上报给 Nacos
  • 网关启动 → 服务拉取,拿到所有微服务实例地址,用来负载均衡

yaml代码

复制代码
spring:
  cloud:
    gateway:
      routes:  # 路由集合,可以配置多条路由规则
        # 第一条路由
        - id: item                 # 路由规则id,自定义,只要求唯一,用来区分不同路由
          uri: lb://item-service   # 路由目标微服务;lb = loadBalance负载均衡
                                   # 网关去Nacos找到名为item-service的服务,做负载均衡转发
          predicates:               # 路由断言:判断请求是否满足规则,满足才转发
            - Path=/items/**        # 路径断言:请求路径以 /items/ 开头,就匹配这条路由
                                   # `**` 代表匹配后面任意多级路径,例如 /items/list、/items/1

        # 第二条路由示例
        - id: xx
          uri: lb://xx-service
          predicates:
            - Path=/xx/**
  1. id:路由标识,自定义、唯一,只是给这条路由起名字,没有业务转发作用。
  2. uri :转发目标地址
    • lb://服务名:从 Nacos 获取服务实例,自动负载均衡(必须引入 nacos-discovery 依赖)
  3. predicates(断言)匹配条件 ,只有请求满足断言,这条路由才生效。
    • Path=/items/**:路径匹配。访问网关/items/xxx就会命中该路由。

举例:

前端请求地址:http://127.0.0.1:8080/search/list

  1. 请求访问网关 8080 端口
  2. 网关读取断言:Path=/items/**,/search/**,请求路径/search/list满足/search/**规则,命中这条item-service路由
  3. uri: lb://item-service → 网关去 Nacos 拉取item-service商品微服务实例
  4. 负载均衡转发请求给 item-service 微服务
  5. item-service 里SearchController/search/list接口接收请求,处理完数据原路返回给前端

①Path=/items/**,/search/**

逗号 , 代表 或 的关系,满足任意一个就匹配成功 ** 代表匹配后面任意多级子路径

可以匹配(会转发给 item-service)

  • /items/list
  • /items/1
  • /search/list(就是咱们前面 SearchController 的接口)
  • /search/xxx/abc

不会匹配

  • /user/list(前缀不是 items、也不是 search)

二、路由属性

网关路由对应的 Java 类型:RouteDefinition,4 个核心属性:id,uri,predicates,filters

  1. id :路由唯一标示 作用:给这条路由起一个唯一名字,用来区分多条路由,没有转发逻辑,自定义即可。
  2. uri:路由目标地址 作用:请求匹配成功后,转发的目标地址。 两种写法:
  • lb://服务名:去 Nacos 拉取实例,负载均衡转发(微服务项目用这个)
  • http://ip:端口:直接写死固定地址
  1. predicates(路由断言) 作用:判断请求是否符合当前路由。只有断言全部满足,这条路由才生效,请求才会转发。
名称 说明 YAML 示例
Path 请求路径匹配(最常用),判断请求 url 路径是否符合规则 - Path=/items/**,/search/**
Method 判断请求方式,限定 GET/POST 等 - Method=GET,POST
Header 判断请求头是否包含指定 header,支持正则 - Header=X-Request-Id, \d+
Query 判断请求参数,要求 url 带某个参数 - Query=name
Host 判断访问的域名 host - Host=**.hmall.com
RemoteAddr 判断客户端来源 IP - RemoteAddr=192.168.1.1/24

我们前面用的 Path=/items/**,/search/** 就是路径断言。

  1. filters(路由过滤器) 作用:对请求或者响应做特殊处理。 可以在请求转发给微服务之前,修改请求;微服务返回结果之后,修改响应。 举例:去掉请求路径前缀、添加请求头、限流。

StripPrefix

配置:- StripPrefix=1 前端访问网关:http://网关ip:8080/api/items/list

  • 原始路径:/api/items/list
  • 去掉 1 段前缀 /api
  • 转发给微服务的路径:/items/list

StripPrefix=1去掉路径最前面的 1 段路径前缀

http://127.0.0.1:8080/api/items/list 网关拿到的原始请求路径/api/items/list

拆分路径分段: 第 1 段:api 第 2 段:items 第 3 段:list

StripPrefix=1删掉1段 api/items/list

StripPrefix=2:删 2 段 /api/items/list/list

bash 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: item-service
          uri: lb://item-service
          predicates:
            - Path=/api/items/** # 匹配所有/api/items开头
          filters:
            - StripPrefix=1 # 删掉第一段/api

YAML 代码对照

bash 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: item-service                 # id:路由唯一标识
          uri: lb://item-service           # uri:目标转发地址
          predicates:                      # predicates:路由断言,匹配请求
            - Path=/items/**,/search/**
          filters:                         # filters:路由过滤器,处理请求/响应
            - StripPrefix=1

流程:前端请求进来 → predicates 断言判断是否匹配 → 匹配成功 → filters 过滤器处理请求 → 转发到 uri 对应的微服务 → 微服务返回响应 → filters 过滤器处理响应 → 返回前端

三、登录校验

网关过滤器分两大类:

  1. GatewayFilter(路由过滤器) 作用在你指定的某一条路由上 ,默认不生效,必须写在对应路由的 filters 里面配置才生效
  2. GlobalFilter(全局过滤器) 作用于网关全部路由,所有请求都会经过, 代码声明成 Bean 之后,自动生效,不需要在 yaml 路由里额外配置
bash 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: item-service
          uri: lb://item-service
          predicates:
            - Path=/items/**,/search/**
          filters:
            # 这条过滤器【只对item-service这条路由生效】GatewayFilter
            - AddRequestHeader=truth, anyone long-press like button will be rich
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/addresses/**,/users/**
      # default-filters 默认过滤器:所有路由都会执行
      default-filters:
        - AddRequestHeader=truth, anyone long-press like button will be rich
类型 生效范围 使用方式
GatewayFilter 路由过滤器 单个指定路由 在 routes 下对应路由的 filters 配置;只作用当前路由
GlobalFilter 全局过滤器 网关所有路由 Java 代码写 Bean,自动全局生效,不用写 yaml
default-filters 默认过滤器 网关所有路由 yaml 配置,作用全部路由,属于配置型 GatewayFilter

1.自定义GlobalFilter(用的最多)

java 复制代码
/**
 * 自定义全局过滤器 GlobalFilter
 * 实现Ordered接口,用来设置过滤器执行优先级
 */
@Component
public class MyGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 获取请求头
        HttpHeaders headers = request.getHeaders();
        System.out.println("headers = " + headers);

        // 从请求头获取token
        String token = headers.getFirst("token");
        // 判断token是否为空
        if (token == null || token.isEmpty()) {
            // token不存在,设置响应状态码401 未授权
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            // 直接结束请求,不放行,拦截请求
            return exchange.getResponse().setComplete();
        }

        // token存在,放行,继续向后执行
        return chain.filter(exchange);

    /**
     * 设置过滤器执行顺序
     * 返回的值越小,优先级越高,过滤器越早执行
     */
    @Override
    public int getOrder() {
        // 过滤器执行顺序,值越小,优先级越高
        return 0;
    }
}

2.登录校验

application.yaml

bash 复制代码
hm:
  jwt:
    location: classpath:hmall.jks # RSA密钥库位置,与登录服务(hm-service)共用同一密钥
    alias: hmall
    password: hmall123
    tokenTTL: 30m
  auth:
    exclude-paths: # 登录校验白名单,命中直接放行
      - /user/login
      - /user/register
      - /doc.html
  1. location: classpath:hmall.jks
    • jks:Java 密钥库文件,存放 RSA 公私钥对
    • 放在 resources 下(classpath);登录服务拿私钥生成 token,网关拿公钥校验 token(非对称特点)
  2. alias: hmall:密钥在 jks 库里面的别名,用来定位密钥
  3. password: hmall123:jks 密钥库的访问密码,读取密钥库需要
  4. tokenTTL: 30m:token 过期时间 30 分钟

exclude-paths不需要校验 token 的白名单接口

  • /user/login:登录接口,还没有 token,必须放行
  • /user/register:注册接口
  • /doc.html:接口文档页面(Knife4j)

AuthProperties.java自定义配置属性类

java 复制代码
@Data
@ConfigurationProperties(prefix = "hm.auth")
public class AuthProperties {
    private List<String> excludePaths;
}
  1. @ConfigurationProperties(prefix = "hm.auth"):读取 yaml 中hm.auth开头的配置,自动绑定到类的成员变量。
  2. private List<String> excludePaths:接收 yaml 里的白名单路径集合。
  3. 必须在启动类 添加注解@EnableConfigurationProperties(AuthProperties.class)开启配置属性扫描。
  4. @Data lombok 注解,自动生成 get/set 方法。

自定义异常 UnauthorizedException

java 复制代码
/**
 * 未授权异常:JWT校验失败时抛出
 * 网关不依赖 hm-common(其knife4j会传递引入spring-webmvc,与Gateway的WebFlux冲突),
 * 因此在网关内部定义该异常
 */
public class UnauthorizedException extends RuntimeException {
    public UnauthorizedException(String msg) {
        super(msg);
    }

    public UnauthorizedException(String msg, Throwable e) {
        super(msg, e);
    }
}
  • JWT 校验失败(无 token、过期、篡改)抛出此运行时异常
  • 在 GlobalFilter 捕获,返回 401 状态码

JwtTool.java 工具类核心

java 复制代码
/**
 * JWT工具类(从 hm-service 复制,与登录服务共用同一套RSA密钥)
 */
@Component
public class JwtTool {
    private final JWTSigner jwtSigner;

    public JwtTool(KeyPair keyPair) {
        this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);
    }

    public String createToken(Long userId, Duration ttl) {
        // 1.生成jws
        return JWT.create()
                .setPayload("user", userId)
                .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis()))
                .setSigner(jwtSigner)
                .sign();
    }

    public Long parseToken(String token) {
        // 1.校验token是否为空
        if (token == null) {
            throw new UnauthorizedException("未登录");
        }
        // 2.校验并解析jwt
        JWT jwt;
        try {
            jwt = JWT.of(token).setSigner(jwtSigner);
        } catch (Exception e) {
            throw new UnauthorizedException("无效的token", e);
        }
        // 2.校验jwt是否有效
        if (!jwt.verify()) {
            // 验证失败
            throw new UnauthorizedException("无效的token");
        }
        // 3.校验是否过期
        try {
            JWTValidator.of(jwt).validateDate();
        } catch (ValidateException e) {
            throw new UnauthorizedException("token已经过期");
        }
        // 4.数据格式校验
        Object userPayload = jwt.getPayload("user");
        if (userPayload == null) {
            // 数据为空
            throw new UnauthorizedException("无效的token");
        }

        // 5.数据解析
        try {
            return Long.valueOf(userPayload.toString());
        } catch (RuntimeException e) {
            // 数据格式有误
            throw new UnauthorizedException("无效的token");
        }
    }
}
  1. Algorithm.HMAC256对称加密,网关和登录服务秘钥必须一致
  2. verifier.verify(token):一次性校验签名、token 格式、过期时间
  3. 核心:校验失败抛异常,不是返回 null,过滤器要用 try-catch 捕获
  4. getClaim("user_id"):读取 JWT 载荷里存放的用户 ID

AuthGlobalFilter.java核心过滤器

java 复制代码
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;
    private final JwtTool jwtTool;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 1.获取请求路径
        String path = request.getPath().toString();
        // 2.判断是否需要做登录拦截
        if (isExclude(path)) {
            // 白名单,放行
            return chain.filter(exchange);
        }

        // 3.获取token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (headers != null && !headers.isEmpty()) {
            token = headers.get(0);
        }

        // 4.校验并解析token(JwtTool内部失败抛UnauthorizedException)
        Long userId;
        try {
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            // 捕获鉴权异常,返回401
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 5.传递用户信息:把userId放入请求头传给下游微服务
        ServerHttpRequest newRequest = request.mutate()
                .header("user-id", userId.toString())
                .build();
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();

        // 6.放行
        return chain.filter(newExchange);
    }

    private boolean isExclude(String path) {
        // 循环配置里的白名单路径
        for (String excludePath : authProperties.getExcludePaths()) {
            if (path.contains(excludePath)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        // 鉴权过滤器优先执行
        return 0;
    }
}

执行流程:

1.先写:application.yaml(配置文件)

  1. 再写:AuthProperties.java 配置属性类

  2. 自定义异常 UnauthorizedException

  3. JwtTool.java JWT 工具

  4. 最后写:AuthGlobalFilter.java 全局过滤器

3.网关传递到微服务

网关鉴权成功后,把 userId 放入请求头user-info 转发给下游微服务;下游微服务通过MVC 拦截器 读取请求头里的 user-info,存入ThreadLocal,在 controller/service 中随时获取当前登录用户。分层:Gateway 网关层(WebFlux) → 下游微服务(SpringMVC)

  1. 网关 AuthGlobalFilter:token 校验成功,mutate 新增user-info请求头
  2. 写 UserContext 工具类(ThreadLocal)
  3. 写 UserInfoInterceptor 拦截器,preHandle 读取 header 存入 TL,afterCompletion 清理
  4. MvcConfig 配置类,注册拦截器,让拦截器生效

网关模块:AuthGlobalFilter.java

java 复制代码
// 5.传递用户信息:校验成功,修改exchange,添加user-info请求头
exchange = exchange.mutate()
        .request(builder -> builder.header("user-info", userId.toString()))
        .build();

// 6.放行,注意:放行要用修改后的exchange
return chain.filter(exchange);

下游微服务:UserInfoInterceptor.java(MVC 拦截器)

java 复制代码
/**
 * 用户信息拦截器:读取网关切片后通过 user-info 请求头透传的userId,
 * 存入UserContext(ThreadLocal),供同线程内controller/service直接获取
 */
public class UserInfoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.从请求头拿到网关传过来的user-info
        String userInfo = request.getHeader("user-info");
        // 2.判断不为空,转Long,存入ThreadLocal
        if (StrUtil.isNotBlank(userInfo)) {
            UserContext.setUser(Long.valueOf(userInfo));
        }
        // 3.放行
        return true;
    }

    // 请求结束后,清理ThreadLocal,防止线程池复用导致串号、内存泄漏
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        UserContext.removeUser();
    }
}

UserContext.java 工具类(ThreadLocal 封装)

java 复制代码
public class UserContext {
    // ThreadLocal,保存当前线程的userId
    private static final ThreadLocal<Long> TL = new ThreadLocal<>();

    // 存入用户id
    public static void setUser(Long userId){
        TL.set(userId);
    }
    // 获取用户id
    public static Long getUser(){
        return TL.get();
    }
    // 删除,清理
    public static void removeUser(){
        TL.remove();
    }
}

MvcConfig.java 注册拦截器(让拦截器生效)

java 复制代码
/**
 * MVC配置:注册UserInfoInterceptor
 * 通过META-INF/spring.factories自动装配,
 * 使包名为com.qcby.*的微服务(cart-service/item-service)也能自动加载,无需逐个配置
 */
@Configuration
@ConditionalOnClass(DispatcherServlet.class) // 仅在SpringMVC环境下生效
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册用户信息拦截器,对所有请求生效
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

4.OpenFeign传递用户(微服务之间调用)

前面网关把user-info放到请求头,同一个请求内,A 微服务调用 B 微服务(OpenFeign 远程调用)

问题:默认 Feign 发起远程调用时,不会自动携带当前请求的请求头 ,B 微服务拿不到user-info,UserContext 为空。

解决方案:Feign 请求拦截器 RequestInterceptor

写一个 Feign 拦截器,在 Feign 发起远程调用之前 ,从当前线程UserContext取出 userId,手动放到 Feign 新请求的请求头user-info里。

注意:不是从 HttpServletRequest 拿 header!直接从 ThreadLocal 拿 userId

java 复制代码
@Configuration
public class FeignConfig {
    @Bean
    public RequestInterceptor userInfoRequestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate template) {
                // 1.从当前线程的ThreadLocal获取userId
                Long userId = UserContext.getUser();
                // 2.判断不为空,添加到feign请求头 user-info
                if(userId != null){
                    template.header("user-info", userId.toString());
}

5.总结

网关统一鉴权,网关把用户 ID 传给下游微服务;微服务之间 OpenFeign 远程调用时,继续把用户 ID 往下传递,全程用user-info请求头 + ThreadLocal。

前端 → Gateway 网关(校验 JWT,添加user-info请求头) → 微服务 A(MVC 拦截器读 header → ThreadLocal) → A 通过 OpenFeign 调用 B(Feign 拦截器从 ThreadLocal 取出 userId,放入 Feign 请求头user-info) → 微服务 B(MVC 拦截器读 header → ThreadLocal)

前端请求携带 token → Gateway 网关

  1. 执行AuthGlobalFilter全局过滤器,网关里所有请求都会先进这个过滤器
  2. 过滤器做登录校验:白名单判断 → 获取 Authorization 的 token → JWT 校验解析得到 userId,失败返回 401
  3. 校验成功:使用exchange.mutate()新增user-info请求头,转发请求到下游微服务 A
  4. 微服务 A:hm-commonMVC 拦截器读取user-info,userId 存入 ThreadLocal(UserContext)
  5. A 服务OpenFeign调用B:hm-api:触发 Feign 的RequestInterceptor,从 UserContext 拿 userId,添加到 Feign 请求头user-info
  6. 请求到达 B 微服务:B 的 MVC 拦截器读取user-info存入 B 的 ThreadLocal
  7. A、B 服务的 Controller/Service 都可以通过 UserContext.getUser () 拿到登录用户 ID
  8. 请求结束,MVC 拦截器清理 ThreadLocal四、配置管理

四、结构目录

浏览器/前端(nginx:80)

hm-gateway:8080 ── JWT统一鉴权、路由、user-info透传

│ lb://(Nacos服务发现 + LoadBalancer)

┌────┴────────────┐

cart-service:8081 item-service:8082 (未来还有 user/order-service)

└──── Feign ──────┘ (hm-api 定义客户端接口)

MySQL(hmall 库) + Nacos 注册中心

html 复制代码
hmall项目根目录
├─ hmall-nginx          # Nginx静态资源服务器,托管前端页面,反向代理请求转发到网关hm-gateway
├─ hm-gateway           # 网关模块(SpringCloud Gateway)【登录校验就在这里】
├─ hm-common            # 公共通用模块,**被所有微服务依赖**
├─ hm-api               # OpenFeign接口定义模块,存放远程调用客户端接口
├─ hm-service           # 基础公共服务(部分项目版本)
├─ cart-service         # 购物车微服务
├─ item-service         # 商品微服务
            

1.hm-gateway 网关模块(核心:登录校验 GlobalFilter)

端口统一入口,所有前端请求先进网关,统一鉴权、路由转发

复制代码
hm-gateway
└─ src/main/java/com/hmall/gateway
   ├─ config
   │   ├─ AuthProperties.java    # 读取配置文件:白名单路径(不需要登录的接口)
   │   ├─ JwtProperties.java     # JWT配置:密钥、有效期
   │   └─ SecurityConfig.java    # 安全相关配置
   ├─ filters
   │   └─ AuthGlobalFilter.java  # ✅【重点】自定义GlobalFilter全局过滤器,登录校验逻辑在这里
   ├─ exception                  # 网关全局异常
   ├─ util                       # JWT工具类,解析token
   └─ GatewayApplication.java    # 网关启动类
└─ resources
   ├─ application.yaml           # 网关路由配置、nacos地址
   └─ hmall.jks                  # JWT签名证书(RSA非对称加密)

核心工作

  1. AuthGlobalFilter:实现GlobalFilter,拦截请求,校验 JWT
  2. 校验成功后,新增user-info请求头携带 userId,转发请求给下游微服务
  3. 白名单放行登录、注册接口,其余接口强制校验 token

2.hm-common 公共模块(所有微服务都要引入这个依赖!)

公共抽取,避免重复代码,利用 spring.factories 实现自动装配

html 复制代码
hm-common
└─ src/main/java/com/hmall/common
   ├─ advice                     # 全局异常处理器 @RestControllerAdvice
   ├─ config                     # 通用配置类
   ├─ domain                     # 通用返回结果类 Result
   ├─ exception                  # 自定义业务异常
   ├─ interceptor
   │   └─ UserInfoInterceptor.java # MVC拦截器:读取user-info请求头,userId存入ThreadLocal(UserContext)
   └─ utils
       └─ UserContext.java        # ThreadLocal工具类,存/取/清除当前登录用户userId
└─ resources
   └─ META-INF
       └─ spring.factories        # 自动装配文件:让SpringBoot自动加载拦截器、配置类,业务微服务不用写@Configuration

关键:cart-service、item-service 引入 hm-common 依赖后,自动注入UserInfoInterceptor,不用自己写拦截器代码

3. hm-api 模块(OpenFeign 接口包)

Feign 接口统一存放,微服务之间远程调用的接口定义,供各个微服务引入依赖

html 复制代码
hm-api
└─ src/main/java/com/hmall/api
   ├─ client        # Feign客户端接口(@FeignClient),例如ItemClient、CartClient
   ├─ config        # Feign配置类:RequestInterceptor Feign拦截器(传递user-info请求头!)
   └─ dto           # Feign远程调用用到的DTO传输对象

重点:RequestInterceptor写在这里,引入 hm-api 的微服务自动获得 Feign 拦截能力,远程调用自动携带 userId

4.cart-service 购物车微服务(业务微服务示例)

复制代码
cart-service
└─ src/main/java/com/qcby/cartservice
   ├─ client         # 引入hm-api里的FeignClient,远程调用其他服务(如商品服务)
   ├─ controller     # 购物车接口,Controller直接从UserContext获取userId
   ├─ domain         # 实体类、DTO
   ├─ mapper         # Mybatis Mapper接口,操作购物车表
   ├─ service        # 业务逻辑层
   └─ CartServiceApplication # 启动类
└─ resources
   ├─ application.yaml        # 基础配置
   ├─ application-dev.yaml    # 开发环境配置
   └─ application-local.yaml  # 本地环境配置
  • 依赖:hm-common + hm-api
  • 自动拥有:MVC 用户拦截器 + Feign 用户传递拦截器,不用重复写代码

6. hm-service(基础服务模块)

复制代码
hm-service
└─ src/main/java/com/hmall
   ├─ config
   ├─ controller
   ├─ domain
   ├─ interceptor
   ├─ mapper
   ├─ service
   ├─ utils
   └─ HMallApplication

7. hmall-nginx

Nginx①托管前端页面, 直接返回 HTML、JS、CSS、图片,不需要经过 Java 后端 。②反向代理 前端页面里调用接口(登录、查购物车),请求路径是/api/**,Nginx 识别到这类动态接口请求,转发给 hm-gateway 网关,再由网关路由到各个微服务。

相关推荐
敲代码的嘎仔2 小时前
互动问答系统实战:两级评论模型、ES 搜索集成、Caffeine 多级缓存全记录
java·开发语言·数据库·elasticsearch·缓存·mybatis·高并发
QQ_21696290962 小时前
基于微服务架构的店铺管理系统的设计与实现
大数据·spring boot·后端·spring·微服务·小程序·架构
CoderYanger2 小时前
Java EE 进阶:2.3 JavaScript 综合案例
java·前端·javascript·css·职场和发展·java-ee·html
starzy19902 小时前
Flink TimeWindow 详解及代码实现:从三种时间语义到滚动与滑动窗口
java·服务器·flink
重生之小比特2 小时前
【Java SE】类和对象(一)
java·ide·intellij-idea
captain3762 小时前
网络原理(4)-TCP
java·服务器·网络·网络协议·tcp/ip·java-ee
青山木2 小时前
枚举类DP进阶:完全平方数与零钱兑换
java·数据结构·算法·leetcode·动态规划
小七在进步2 小时前
类和对象(二)
java·开发语言
clz13145213 小时前
风险特征平台架构设计与实现
java·flink