Spring Boot 3 整合 Spring Cloud Gateway 工程实践

引子

当前微服务架构已成为中大型系统的标配,但在享受拆分带来的敏捷性时,流量治理与安全管控的复杂度也呈指数级上升。因此,我们需要构建微服务网关来为系统“保驾护航”。本文将会通过一个项目(核心模块包含 鉴权服务、文件服务、主服务 共 3 个微服务),采用 Spring Cloud Alibaba 2023.0.0.0 版本技术栈(核心组件:Nacos 2.5.0 注册中心与配置中心),分享如何构建一个微服务网关。

为什么需要微服务网关

我们当前模拟的这个项目中包含了三个业务服务,如果部署到线上的话,每个服务都有自己的ip(或域名)以及端口号。因此,我们的业务入口是分散的且暴露在外的,我们无法统一拦截异常流量以及限制接口访问等。但有了微服务网关,我们就可以将所有的请求都先集中在网关这里(有点类似于一个房子的大门口),由网关对所有请求进行统一的管理。

在这里插入图片描述

实践

在知晓了网关的作用后,我们将实践如何在一个现成的微服务项目中整合gateway网关以及做功能开发。当然,在这之前,我们需要先完成整合。首先,我们需要建一个网关模块,如下:

在这里插入图片描述完成模块的创建后,导入gateway相关的依赖,如下:

    <dependencies><dependency><groupId>com.pitayafruits</groupId><artifactId>wechat-pojo</artifactId><version>1.0-SNAPSHOT</version><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency></dependencies>

说明一下:这里引入的pojo包含了项目中常用的方法、工具类等;web则是因为网关本身也是一个可以访问的服务,所以需要引入;gateway则是这里需要使用的网关的依赖。然后来对它进行基础的配置,如下:

server:port: 1000tomcat:uri-encoding: UTF-8max-swallow-size: -1 # 不限制请求体大小spring:application:name: gatewaycloud:nacos:config:server-addr: 127.0.0.1:8848username: nacospassword: naocs# 日志级别
logging:level:root: info

我们使用了nacos来管理服务,网关自然也是一个服务,因此也需要把它注册到nacos

1.统一路由

引入网关的首要作用是统一访问的入口,所有的服务访问都要先经过网关。因此,第一个要实现的功能就是统一路由。而它的实现也是非常简单,只需要在配置文件中做下简单配置即可:

spring:gateway:discovery:locator:enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由routes:           # 路由配置信息(数组/list)- id: authRoute # 每项路由规则都有一个唯一的id编号,可以自定义uri: lb://auth-service # lb=负载均衡,会动态寻址predicates:- Path=/a/**- id: fileRouteuri: lb://file-servicepredicates:- Path=/f/**- id: mainRouteuri: lb://main-servicepredicates:- Path=/m/**globalcors: # 允许跨域的相关配置cors-configurations:'[/**]':allowedOriginPatterns: "*"allowedHeaders: "*"allowedMethods: "*"allowCredentials: true

这里对routes下的相关配置说明下:id是给每个服务的路由一个唯一编号,保证唯一即可,通常我们采用的写法是服务名+route;uri则是服务名称,如果写成ip或者域名,那么地址发生变化,我们还需要重新修改配置,但是服务名称是可以固定不变的;接下来是predicates,它可以配置多个值,我们一个服务里会有多个controller,把每个controller的路由配置在这里即可,/**表示指定的controller下的所有方法。

另外,如果负载均衡这个写法无法被识别,说明你当前使用的spring-cloud版本中默认并不包含相关依赖,我们需要手动引入它。

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

完成上述配置后,我们此时其他服务的API将无法直接访问,而统一通过网关来访问。例如原本main-service中的127.0.0.1:88/m/hello 变成了 127.0.0.1:1000/m/hello

2.限流防刷

提到网关,一个绕不开的话题就是限流。如果有人恶意刷我们的接口,我们就需要对某些IP进行访问限制,比如在XX秒内访问同一接口超过XX次,就需要限制访问。它的实现非常简单,声明一个处理类继承gateway的相关过滤接口即可。代码如下:

@Component
public class IPLimitFilter implements GlobalFilter {private static final Integer continueCounts = 3;private static final Integer timeInterval = 20;private static final Integer limitTimes = 30;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return doLimit(exchange, chain);}/*** 限制ip请求次数的判断** @param exchange 请求交换器* @param chain     过滤器链* @return 返回值*/public Mono<Void> doLimit(ServerWebExchange exchange,GatewayFilterChain chain) {// 获取ipServerHttpRequest request = exchange.getRequest();String ip = IPUtil.getIP(request);// 正常ip定义final String ipRedisKey = "gateway-ip" + ip;// 被拦截的黑名单,如果在redis中存在,那么就不允许访问final String ipRedisLimitKey = "gateway-ip:limit" + ip;// 判断当前ip的剩余时间,如果大于0,则表示还处于黑名单long limitLeftTimes = redis.ttl(ipRedisLimitKey);if ( limitLeftTimes > 0 ) {return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);}// 在redis中更新次数long requestCounts = redis.increment(ipRedisKey, 1);// 如果第一次访问,就需要设置间隔时间if (requestCounts == 1) {redis.expire(ipRedisKey, timeInterval);}// 如果还能获得正常请求次数,说明用户的正常请求落在正常时间内,超过则限制if (requestCounts > continueCounts) {redis.set(ipRedisLimitKey, ipRedisLimitKey, limitTimes);return renderErrorMsg(exchange, ResponseStatusEnum.SYSTEM_ERROR_BLACK_IP);}// 放行请求return chain.filter(exchange);}//过滤器的顺序,数字越小优先级越大.@Overridepublic int getOrder() {return 1;}

我们需要借助redis来实现根据时间对指定ip的控制,这里的逻辑是:如果某个ip在30秒访问超过三次,就限制访问,如果限制了,则20秒后再恢复。

3.登录鉴权

关于登录鉴权,我们目前通常会采用无状态的做法:即用户登录后,后端返回token给前端,前端后续所有的请求都在headers中携带token,后端服务不存储token,只对前端发来的token进行校验和解析。而网关作为所有服务的入口,自然而然地也就可以承担起这个职责了。

import com.google.gson.Gson;
import com.pitayafruits.base.BaseInfoProperties;
import com.pitayafruits.grace.result.GraceJSONResult;
import com.pitayafruits.grace.result.ResponseStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.context.config.annotation.RefreshScope;
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.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;
import java.util.List;@Component
@Slf4j
@RefreshScope
public class SecurityFilterToken extends BaseInfoProperties implements GlobalFilter, Ordered {@Resourceprivate ExcludeUrlProperties excludeUrlProperties;private AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取用户请求路径String url = exchange.getRequest().getURI().getPath();// 获取所有需要排除校验的urlList<String> excludeList = excludeUrlProperties.getUrls();// 校验并排除urlif (excludeList != null && !excludeList.isEmpty()) {for (String excludeUrl : excludeList) {if (antPathMatcher.matchStart(excludeUrl, url)) {return chain.filter(exchange);}}}// 从header中获得用户id和tokenString userId = exchange.getRequest().getHeaders().getFirst(HEADER_USER_ID);String userToken = exchange.getRequest().getHeaders().getFirst(HEADER_USER_TOKEN);// 校验header中的tokenif (StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userToken)) {String redisToken = redis.get(REDIS_USER_TOKEN + ":" + userId);if (redisToken.equals(userToken)) {return chain.filter(exchange);}}// 默认不放行return renderErrorMsg(exchange, ResponseStatusEnum.UN_LOGIN);}//过滤器的顺序,数字越小优先级越大.@Overridepublic int getOrder() {return 0;}/*** 异常信息包装** @param exchange   交换器* @param statusEnum 状态枚举* @return 返回值*/public Mono<Void> renderErrorMsg(ServerWebExchange exchange,ResponseStatusEnum statusEnum) {//1.获得responseServerHttpResponse response = exchange.getResponse();//2.构建jsonResultGraceJSONResult jsonResult = GraceJSONResult.exception(statusEnum);//3.设置header类型if (!response.getHeaders().containsKey("Content-Type")) {response.getHeaders().add("Content-Type",MimeTypeUtils.APPLICATION_JSON_VALUE);}//4.设置状态码response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);//5.转换json并向response写数据String resultJson = new Gson().toJson(jsonResult);DataBuffer buffer = response.bufferFactory().wrap(resultJson.getBytes(StandardCharsets.UTF_8));//6.返回return response.writeWith(Mono.just(buffer));}
}

在我这个示例中,我做的校验逻辑很简单:只是用户登录的时候会在redis里存放生成的token,然后其他接口访问的时候比对下传来的tokenredis里存放的token是否一致。这里需要关注下过滤器的顺序,目前的案例中我们已经编写了两个过滤器-限流防刷和登录鉴权。所以可以把登录鉴权过滤器的执行顺序改为0,限流防抖改为1。

另外,我们需要对部分接口放行不拦截,比如登录接口。而我这里的做法则是将放行接口写在配置文件里,并声明配置类进行读取。

exclude.urls[0] = /passport/getSMSCode
exclude.urls[1] = /passport/regist
exclude.urls[2] = /passport/login
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;import java.util.List;@Component
@Data
@PropertySource("classpath:excludeUrlPath.properties")
@ConfigurationProperties(prefix = "exclude")
public class ExcludeUrlProperties {private List<String> urls;}

特别说明下:这里制定好过滤器的执行顺序后,内部的验证逻辑根据自己实际情况填写,我这里没用鉴权框架只是方便讲解,要用也很简单,引入之后把相关的鉴权逻辑写进对应的过滤器就行。

小结

在本文中,我们完成了Spring Cloud Gateway微服务网关的整合,并完成了三个最基础常见的实践场景。如果你的项目有更多的业务需求,只需要加相应的过滤器并制定好过滤器的执行顺序即可,希望对大家有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/23388.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flutter项目构建常见问题

最近在研究一个验证码转发的app&#xff0c;原理是尝试读取手机中对应应用的验证码进行自动转发。本次尝试用flutter开发&#xff0c;因为之前没有flutter开发的经验&#xff0c;遇到了诸多环境方面的问题&#xff0c;汇总一些常见的问题如下。希望帮助到入门的flutter开发者&a…

Classic Control Theory | 12 Real Poles or Zeros (第12课笔记-中文版)

笔记链接&#xff1a;https://m.tb.cn/h.Tt876SW?tkQaITejKxnFLhttps://m.tb.cn/h.Tt876SW?tkQaITejKxnFL

图解感知机(Perceptron)

目录 1.感知机&#xff08;Perceptron&#xff09;介绍 2.网络结构与工作原理 3.模型工作示例 4.总结 1.感知机&#xff08;Perceptron&#xff09;介绍 感知机&#xff08;Perceptron&#xff09;是最早的人工神经网络模型之一&#xff0c;由弗兰克罗森布拉特&#xff08;…

多旋翼+航模+直升机:多型号无人机飞行表演技术详解

多旋翼、航模、直升机等多种型号的无人机飞行表演技术&#xff0c;是现代科技与艺术的完美结合&#xff0c;它们通过精密的编程、高效的通信、先进的定位与导航技术&#xff0c;以及复杂的编队控制算法&#xff0c;共同呈现出令人震撼的视觉效果。以下是对这些无人机飞行表演技…

deepseek 导出导入模型(docker)

前言 实现导出导入deepseek 模型。deepseek 安装docker下参考 docker 导出模型 实际生产环境建议使用docker-compose.yml进行布局&#xff0c;然后持久化ollama模型数据到本地参考 echo "start ollama" docker start ollama#压缩容器内文件夹&#xff0c;然后拷贝…

【MySQL】表的增删查改(CRUD)(上)

个人主页&#xff1a;♡喜欢做梦 欢迎 &#x1f44d;点赞 ➕关注 ❤️收藏 &#x1f4ac;评论 CRUD&#xff1a;Create&#xff08;新增数据&#xff09;、Retrieve&#xff08;查询数据&#xff09;、Update&#xff08;修改数据&#xff09;、Delete&#xff08;修改数据…

Win11作为宿主机,运行VMware 总没有网络

问题&#xff1a; 移动了VMware到新宿主机上后&#xff0c;虚拟机无法连接网络&#xff0c;其实会显示一个圆圈的图标&#xff0c;这是连接上的图标。 造成这个错误的原因是多种多样的。 用下面的方法来查排查错误。 1.控制面板-> 网络连接 安装好虚拟机后&#xff0c;会…

edge浏览器将书签栏顶部显示

追求效果&#xff0c;感觉有点丑&#xff0c;但总归方便多了 操作路径&#xff1a;设置-外观-显示收藏夹栏-始终

快速入门——第三方组件element-ui

学习自哔哩哔哩上的“刘老师教编程”&#xff0c;具体学习的网站为&#xff1a;10.第三方组件element-ui_哔哩哔哩_bilibili&#xff0c;以下是看课后做的笔记&#xff0c;仅供参考。 第一节 组件间的传值 组件可以有内部Data提供数据&#xff0c;也可由父组件通过prop方式传…

代码审计入门学习之sql注入

路由规则 入口文件&#xff1a;index.php <?php // ---------------------------------------------------------------------- // | wuzhicms [ 五指互联网站内容管理系统 ] // | Copyright (c) 2014-2015 http://www.wuzhicms.com All rights reserved. // | Licensed …

基于vue和微信小程序的校园自助打印系统(springboot论文源码调试讲解)

第3章 系统设计 3.1系统功能结构设计 本系统的结构分为管理员和用户、店长。本系统的功能结构图如下图3.1所示&#xff1a; 图3.1系统功能结构图 3.2数据库设计 本系统为小程序类的预约平台&#xff0c;所以对信息的安全和稳定要求非常高。为了解决本问题&#xff0c;采用前端…

QQ登录测试用例报告

QQ登录测试用例思维导图 一、安全性测试用例 1. 加密传输与存储验证 测试场景&#xff1a;输入账号密码并提交登录请求。预期结果&#xff1a;账号密码通过加密传输&#xff08;如HTTPS&#xff09;与存储&#xff08;如哈希加盐&#xff09;&#xff0c;无明文暴露。 2. 二…

【AI算法岗面试八股面经【超全整理】——机器学习】

AI算法岗面试八股面经【超全整理】 概率论【AI算法岗面试八股面经【超全整理】——概率论】信息论【AI算法岗面试八股面经【超全整理】——信息论】机器学习【AI算法岗面试八股面经【超全整理】——机器学习】深度学习【AI算法岗面试八股面经【超全整理】——深度学习】NLP【A…

luci界面开发中的MVC架构——LuCI介绍(二)

想要给openwrt开发应用&#xff0c;虽然直接可执行程序也可以运行&#xff0c;但是没有UI会很不方便&#xff0c;想要开发UI就要用openwrt的那一套&#xff0c;自然就是LuCI&#xff0c;LuCI又用了一套MVC框架&#xff0c;今天就讲讲这是个什么东西。 OpenWrt LuCI 界面开发中…

网络安全监测探针安装位置 网络安全监测系统

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 软件简介&#xff1a; SockMon(SocketMonitor)网络安全监控系统是一款为电脑专业人员打造的一款出色的安防监控软件。在如今这个恶意软件&#xff0c;攻击&#…

车载DoIP协议 --- TCP详细解析

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

DeepSeek-R1本地部署保姆级教程

一、DeepSeek-R1本地部署配置要求 &#xff08;一&#xff09;轻量级模型 ▌DeepSeek-R1-1.5B 内存容量&#xff1a;≥8GB 显卡需求&#xff1a;支持CPU推理&#xff08;无需独立GPU&#xff09; 适用场景&#xff1a;本地环境验证测试/Ollama集成调试 &#xff08;二&a…

2025年SCI一区智能优化算法:真菌生长优化算法(Fungal Growth Optimizer,FGO),提供MATLAB代码

一. 真菌生长优化算法&#xff08;FGO&#xff09; 真菌生长优化算法&#xff08;Fungal Growth Optimizer&#xff0c;FGO&#xff09;是一种新型的自然启发式元启发式算法&#xff0c;其灵感来源于自然界中真菌的生长行为。该算法通过模拟真菌的菌丝尖端生长、分支和孢子萌发…

【人工智能】蓝耘智算平台盛大发布DeepSeek满血版:开创AI推理体验新纪元

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ 蓝耘智算平台 蓝耘智算平台核心技术与突破元生代推理引擎快速入门&#xff1a;三步调用大模型接口&#xff0c;OpenAI SDK无缝兼容实战用例文…

基于AVue的二次封装:快速构建后台管理系统的CRUD方案

基于AVue的二次封装&#xff1a;快速构建后台管理系统的CRUD方案 在开发后台管理系统时&#xff0c;表格是常见的组件之一。然而&#xff0c;使用原生的Element Plus实现CRUD&#xff08;增删改查&#xff09;功能往往需要编写大量重复代码&#xff0c;过程繁琐。即使借助类似…