1.什么是微服务
微服务是由以单一应用程序构成的小服务,自己拥有自己的行程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通信。同时服务会使用最小的规模的集中管理能力,服务可以用不同的编程语言与数据库等组件实现。
简单来说,微服务就是将一个大型项目的各个业务代码,拆分成多个互不相干的小项目,而这些小项目专心的完成自己的功能,而且可以调用别的小项目的方法,从而完成整体功能
2.为什么需要微服务
2.1单体项目的特点
一旦服务器忙,所有业务都无法快速响应
即使添加了服务器,也不能很好的解决这个问题
不能很好的实现"高并发,高可用,高性能"
但是因为服务器数量少,所以成本低,适合并发访问少的项目
2.2微服务项目的特点
每个业务专门一批人来负责,业务之间互不影响
在某个模块性能不足时,针对这个模块添加服务器改善性能
万一一个服务器发生异常,并不会影响整体功能
但是完成部署的服务器数量多,成本高,需要较多投资,能够满足"高并发,高可用,高性能"的项目
3.如何搭建微服务项目
在微服务概念提出之前(2014年),每个厂商都有自己的解决方案
但是Martin Fowler(马丁·福勒)提出了微服务的标准之后,为了技术统一和兼容性,很多企业开始支持这个标准
现在我们开发的微服务项目,大多数都是在马丁·福勒标准下的
如果我们自己编写支持这个标准的代码是不现实的,必须通过现成的框架或组件完成满足这个微服务标准的项目结构和格式
当今程序员要想快速开发满足上面微服务标准的项目结构,首选SpringCloud
4.什么是SpringCloud
SpringCloud是由Spring提供的一套能够快速搭建微服务架构程序的框架集
框架集表示SpringCloud不是一个框架,而是很多框架的统称
SpringCloud就是为了搭建微服务架构项目出现的
有人将SpringCloud称之为"Spring全家桶",广义上指代Spring的所有产品
5.Spring Cloud的内容
功能上分类
- 微服务的注册中心
- 微服务间的调用
- 微服务的分布式事务
- 微服务的限流
- 微服务的网关
…
6.微服务的注册中心 (Nacos、ZooKeeper、Eureka、Consul )
6.1什么是Nacos
Nacos是Spring Cloud Alibaba提供的一个软件
这个软件主要具有注册中心和配置中心的功能
微服务中所有项目都必须注册到注册中心才能成为微服务的一部分
6.2将模块注册到Nacos
如果某一个模块作为生产者,那么该模块会存在service(对外被调用的接口)和webapi(实际业务模块)这两个子模块,我们只需要将webapi注册到Nacos即可
- 在pom文件中添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 在application-dev.yml中编写对nacos注册的配置信息
spring:
application:
# 为当前项目命名,这个名称会在nacos注册时使用
name: nacos-business
cloud:
nacos:
discovery:
# 定义Nacos所在的位置,方便当前项目提交信息
server-addr: localhost:8848
# ephemeral设置当前项目启动时注册到nacos的类型 true(默认):临时实例 false:永久实例
ephemeral: false
持久化实例启动时向nacos注册,nacos会对这个实例进行持久化处理
心跳包的规则和临时实例一致,只是不会将该服务从列表中剔除
一般情况下,我们创建的服务都是临时实例
只有项目的主干业务才会设置为永久实例
6.3启动Nacos
- 方式一:
找到nacos安装目录的bin目录
输入cmd进入dos窗口
输入以下命令启动nacos
startup.cmd -m standalone
- 方式二:
利用idea快捷启动nacos
7.微服务间的调用(Dubbo,Ribbon+Feign)
7.1什么是RPC
RPC是Remote Procedure Call的缩写 翻译为:远程过程调用
目标是为了实现两台(多台)计算机\服务器,相互调用方法\通信的解决方案
RPC只是实现远程调用的一套标准
该标准主要规定了两部分内容:
1.序列化协议
2.通信协议
7.2什么是Dubbo
Dubbo是一套RPC框架。既然是框架,我们可以在框架结构高度,定义Dubbo中使用的通信协议,使用的序列化框架技术,而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。
可以说Dubbo就是RPC概念的实现
Dubbo是SpringCloudAlibaba提供的框架
能够实现微服务相互调用的功能!
7.3如何使用Dubbo
- 添加依赖
<!-- Dubbo依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
- 在dev.yml文件中添加配置
dubbo:
protocol:
# port设置-1是Dubbo框架支持的特殊写法
# 会自动动态生成可用的端口号,规则是从20880开始,如果被占用就递增使用
port: -1
# 设置连接名称,一般固定就叫dubbo
name: dubbo
registry:
# 声明注册中心的软件类型和ip端口号
address: nacos://localhost:8848
consumer:
# 当前项目启动时,作为消费者,是否要检查所有远程服务可用,false表示不检查
check: false
- 在被调用模块的service实现类上添加
@DubboService
注解
// @DubboService注解标记的业务逻辑层实现类,其中所有方法都会注册到Nacos
// 其它服务在订阅时会发现当前项目提供的业务逻辑层方法,以备Dubbo调用
@DubboService
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
@Autowired
private StockMapper stockMapper;
@Override
public void reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO) {
//方法略
}
}
- 如果当前项目是服务的提供者(生产者),还需要在SpringBoot启动类上添加@EnableDubbo的注解,才能真正让Dubbo功能生效
@SpringBootApplication
// 如果当前项目是Dubbo的生产者,需要添加这个注解否则无法调用该项目的方法
@EnableDubbo
public class CsmallStockWebapiApplication {
public static void main(String[] args) {
SpringApplication.run(CsmallStockWebapiApplication.class, args);
}
}
- 其他模块的service层利用
@DubboReference
注解来进行调用
// business模块会调用order模块的业务方法,所以order模块仍然需要编写生产者需要的注解
@DubboService
@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {
// 当前order模块消费stock模块的减少库存的方法
// 因为stock模块减少库存的方法已经注册到Nacos,所以当前order模块可以利用Dubbo调用
// 要想调用就必须使用@DubboReference,才能获得业务逻辑层实现类对象
@DubboReference
private IStockService stockService;
@Override
public void orderAdd(OrderAddDTO orderAddDTO) {
// 调用stock模块中的方法...
}
}
负载均衡(Loadbalance)
Dubbo内置4种负载均衡算法
- random loadbalance:随机分配策略(默认)
- round Robin Loadbalance:权重平均分配
- leastactive Loadbalance:活跃度自动感知分配
- consistanthash Loadbalance:一致性hash算法分配
实际运行过程中,每个服务器性能不同
在负载均衡时,都会有性能权重,这些策略算法都考虑权重问题
8.微服务的分布式事务(Seata)
8.1Seata是什么?
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
也是Spring Cloud Alibaba提供的组件
Seata官方文档
8.2为什么需要Seata?
我们之前学习了单体项目中的事务,使用的技术叫Spring声明式事务
能够保证一个业务中所有对数据库的操作要么都成功,要么都失败,来保证数据库的数据完整性,但是在微服务的项目中,业务逻辑层涉及远程调用,当前模块发生异常,无法操作远程服务器回滚
这时要想让远程调用也支持事务功能,就需要使用分布式事务组件Seata
事务的4个特性:ACID特性
- 原子性
- 一致性
- 隔离性
- 永久性
Seata保证微服务远程调用业务的原子性
Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
8.3Seata的运行原理(AT模式)
上面结构是比较典型的远程调用结构,如果account操作数据库失败需要让order模块和storage模块撤销(回滚)操作,声明式事务不能完成这个操作,需要使用Seata来解决
Seata构成部分包含:
- 事务协调器TC
- 事务管理器TM
- 资源管理器RM
AT模式运行过程:
1.事务的发起方™会向事务协调器(TC)申请一个全局事务id,并保存
2.Seata会管理事务中所有相关的参与方的数据源,将数据操作之前和之后的镜像都保存在undo_log表中,这个表是seata组件规定的表,没有它就不能实现效果,依靠它来实现提交(commit)或回滚(roll back)的操作
3.事务的发起方™会连同全局id一起通过远程调用运行资源管理器(RM)中的方法
4.RM接收到全局id,去运行指定方法,并将运行结果的状态发送给TC
5.如果所有分支运行都正常,事务管理器™会通过事务协调器通知所有模块执行数据库操作,真正影响数据库内容,反之如果有任何一个分支模块运行异常,都会通知TC,再由TC通知所有分支将数据库操作回滚,恢复成运行之前的样子
8.4其他模式简介
8.5如何使用Seata
数据库中添加undo_log
表
该表用于保存出现异常需要回滚的数据
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=68 DEFAULT CHARSET=utf8mb3;
配置Seata
- 添加依赖,cart\stock\order都是具备数据库操作功能的模块需要添加两个相关依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!-- Seata完成分布式事务需要的两个相关依赖(Seata需要下面两个依赖中的资源) -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
- 在dev.yml文件中添加配置
seata:
tx-service-group: csmall_group # 定义分组名称,一般是为了区分项目
service:
vgroup-mapping:
csmall_group: default # csmall_group组使用默认的Seata配置完成事务
grouplist:
default: localhost:8091 # 设置Seata所在的地址(默认端口号就是8091)
注意同一个事务必须在同一个tx-service-group中,同时指定相同的seata地址和端口
business模块配置更简单,因为它是服务的发起者,不需要数据库操作,所以配置更简单
pom文件seata依赖仍然需要,但是只需要seata依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
- 添加完配置后,使用
@GlobalTransactional
注解开启分布式事务
@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
// Dubbo调用order模块中生成订单的方法
@DubboReference
private IOrderService dubboOrderService;
// 一旦编写@GlobalTransactional注解标记这个方法
// 这个方法就是seata分布式事务的起点了,也就是TM
// 最终的效果就是由当前方法引发的所有远程调用对数据库的操作,要么都成功,要么都失败
@GlobalTransactional
@Override
public void buy() {
// 模拟购买业务
}
}
Windows环境下启动Seata
方式一:
解压文件的bin目录下输入cmd进入dos命令窗口
seata-server.bat -h 127.0.0.1 -m file
方式二:
参考配置nacos到idea中启动的方式.
测试Seata是否有效
可以在开启事务的方法中手动抛出异常,查看数据库中的数据是否发生改变,或者查看控制台输出信息.
9.微服务的限流(Sentinel,Hystrix)
9.1什么是Sentinel
Sentinel也是Spring Cloud Alibaba的组件,Sentinel英文翻译"哨兵\门卫"
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
9.2为什么需要Sentinel
-
丰富的应用场景
双11,秒杀,12306抢火车票
-
完备的实时状态监控
可以支持显示当前项目各个服务的运行和压力状态,分析出每台服务器处理的秒级别的数据
-
广泛的开源生态
很多技术可以和Sentinel进行整合,SpringCloud,Dubbo,而且依赖少配置简单
-
完善的SPI扩展
Sentinel支持程序设置各种自定义的规则
9.3如何使用Sentinel
限流针对的是控制器方法
- 添加依赖
<!-- Sentinel依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- application-dev.yml文件添加配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # 配置sentinel仪表台的位置
# 执行限流的端口,每个项目唯一(别的项目(例如cart)设置的话不能再用8721)
port: 8721
nacos:
discovery:
server-addr: localhost:8848
- 在要限流的控制器方法前添加
@SentinelResource
注解
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
// SentinelResource注解标记的控制器方法,会被Sentinel管理
// 在这个方法第一次运行后,可以在sentinel仪表台界面中设置限流规则
// "减少库存的方法"设置当前方法在仪表台中显示的名称
@SentinelResource("减少库存的方法")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
// 调用业务逻辑层
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
- 访问Sentinel并设置
打开浏览器并输入 http://localhost:8080/ 出现登录页面
用户名和密码都是sentinel
第一次运行控制器方法后
就可以在"簇点链路"中找到流控选项了!
9.4自定义限流方法
对与被限流的请求,我们可以自定义限流的处理方法
默认情况下可能不能正确给用户提示,一般情况下,对被限流的请求也要有"服务器忙请重试"或类似的提示
StockController类中@SentinelResource注解中,可以定义处理限流情况的方法
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
// SentinelResource注解标记的控制器方法,会被Sentinel管理
// 在这个方法第一次运行后,可以在sentinel仪表台界面中设置限流规则
// "减少库存的方法"设置当前方法在仪表台中显示的名称
// blockHandler 指定请求被限流时运行的方法名称
@SentinelResource(value = "减少库存的方法",blockHandler = "blockError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO){
// 调用业务逻辑层
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
// Sentinel 自定义限流方法的定义
// 1.访问修饰符必须是public
// 2.返回值类型必须和限流的控制器方法一致
// 3.方法名称必须是限流注解中定义的blockHandler指定的方法名称
// 4.方法参数列表必须和限流的控制器方法一致,而且还要添加一个BlockException类型的参数
public JsonResult blockError(StockReduceCountDTO stockReduceCountDTO, BlockException e){
// 限流方法一般直接返回限流信息即可
return JsonResult.failed(ResponseCode.BAD_REQUEST,"服务器忙,请稍后再试");
}
重启stock-webapi模块
再次尝试被限流,观察被限流的提示
QPS与并发线程数
QPS:是每秒请求数
单纯的限制在一秒内有多少个请求访问控制器方法
并发线程数:是当前正在使用服务器资源请求线程的数量
限制的是使用当前服务器的线程数
9.5自定义降级方法
所谓降级就是正常运行控制器方法的过程中
控制器方法发生了异常,Sentinel支持我们运行别的方法来处理异常,或运行别的业务流程处理
我们也学习过处理控制器异常的统一异常处理类,和我们的降级处理有类似的地方
但是Sentinel降级方法优先级高,而且针对单一控制器方法编写
StockController类中@SentinelResource注解中,可以定义处理降级情况的方法
@PostMapping("/reduce/count")
@ApiOperation("减少商品库存数")
// SentinelResource注解标记的控制器方法,会被Sentinel管理
// 在这个方法第一次运行后,可以在sentinel仪表台界面中设置限流规则
// "减少库存的方法"设置当前方法在仪表台中显示的名称
// blockHandler 指定请求被限流时运行的方法名称
// fallback 指定控制器方法发生异常时,要执行的降级方法名称
@SentinelResource(value = "减少库存的方法",blockHandler = "blockError",
fallback = "fallbackError")
public JsonResult reduceCommodityCount(StockReduceCountDTO stockReduceCountDTO) {
if(Math.random()<0.5){
throw new CoolSharkServiceException(ResponseCode.INTERNAL_SERVER_ERROR,
"抛出随机异常!");
}
// 调用业务逻辑层
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("商品库存减少完成!");
}
// Sentinel 自定义限流方法的定义
// 1.访问修饰符必须是public
// 2.返回值类型必须和限流的控制器方法一致
// 3.方法名称必须是限流注解中定义的blockHandler指定的方法名称
// 4.方法参数列表必须和限流的控制器方法一致,而且还要添加一个BlockException类型的参数
public JsonResult blockError(StockReduceCountDTO stockReduceCountDTO, BlockException e){
// 限流方法一般直接返回限流信息即可
return JsonResult.failed(ResponseCode.BAD_REQUEST,"服务器忙,请稍后再试");
}
// 这个方法是Sentinel注解中fallback指定的降级方法
// 当原定运行的控制器方法发送异常时,Sentinel会运行下面的方法
// 降级方法中,可以编写一些对正常逻辑的补救措施,使用户受到的损失最少
public JsonResult fallbackError(StockReduceCountDTO stockReduceCountDTO){
return JsonResult.failed(ResponseCode.BAD_REQUEST,"因为运行时发生异常,服务降级");
}
10.微服务的网关(SpringGateway,Zuul)
什么是网关
网关就是当前微服务项目的"统一入口"
程序中的网关就是当前微服务项目对外界开放的统一入口
因为提供了统一入口之后,方便对所有请求进行统一的检查和管理
网关的主要功能有:
- 将所有请求统一由经过网关
- 网关可以对这些请求进行检查
- 网关方便记录所有请求的日志
- 网关可以统一将所有请求路由到正确的模块\服务上
Spring Gateway简介
我们使用Spring Gateway作为当前项目的网关框架
Spring Gateway是Spring自己编写的,也是SpringCloud中的组件
SpringGateway官网
网关项目git地址
简单网关演示
SpringGateway网关是一个依赖,不是一个软件
所以我们要使用它的话,必须先创建一个SpringBoot项目
这个项目也要注册到Nacos注册中心,因为网关项目也是微服务项目的一个组成部分
beijing和shanghai是编写好的两个项目 链接: 网关demo
gateway项目就是网关项目,需要添加相关配置
我们从yml文件配置开始添加
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes: # gateway开始编写路由信息
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
# routes是一个数组类型的变量,yml文件中出现"- ..."表示当前配置的是数组元素
# 如果想访问下面数组元素的uri属性,实际结构为spring.cloud.gateway.routes[0].uri
- id: gateway-beijing # 设置这个路由的名称,名称和项目没有任何关联,只是别和其它的路由名称重复
# uri设置路由的目标
# lb就是loadBalance(负载均衡)的缩写,beijing是beijing项目注册到nacos的名称
uri: lb://beijing
# predicates翻译为断言,断言的意思就是判断某个条件为真时,要做的事情
predicates:
# Path会设置当访问9000端口时如果路径以bj开头,会路由到上面uri配置的路径
# Path就是路径断言,判断路径是否为/bj/开头,如果路径满足就会执行路由
- Path=/bj/**
# localhost:9000/bj/show 路由到 localhost:9001/bj/show
# 当前配置结构找到Path=/bj/**路径为 spring.cloud.gateway.routes[0].predicates[2]
上面的yml配置了bejing和shanghai项目的路由信息
我们使用
http://localhost:9000/bj/show可以访问beijing服务器的资源
http://localhost:9000/sh/show可以访问shanghai服务器的资源
以此类推,再有很多服务器时,我们都可以仅使用9000端口号来将请求路由到正确的服务器
就实现了gateway成为项目的统一入口的效果
动态路由
网关项目随着微服务数量的增多
gateway项目的yml文件配置会越来越多,维护的工作量也会越来越大
所以我们希望gateway能够设计一套默认情况下自动路由到每个模块的路由规则
这样的话,不管当前项目有多少个路由目标,都不需要维护yml文件了
这就是我们SpringGateway的动态路由功能
配置文件中开启即可
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 默认情况gateway不开启动态路由,通过enabled: true开启动态路由
# 路由规则是在9000端口号后面先编写路由目标项目注册到nacos的名称,再编写具体路径
enabled: true
路由规则是在9000端口号后面先编写路由目标项目注册到nacos的名称,再编写具体路径
内置断言 predicates
断言的意思就是判断某个条件是否满足
我们之前使用了Path断言,判断请求的路径是不是满足条件,例如是不是/sh/** /bj/**
如果路径满足这个条件,就路由到指定的服务器
但是Path实际上只是SpringGateway提供的多种内置断言中的一种
还有很多其它断言
- after
- before
- between
- cookie
- header
- host
- method
- path
- query
- remoteaddr
时间相关
after,before,between
判断当前时间在指定时间之前,之后或之间的操作
如果条件满足可以执行路由操作,否则拒绝访问
我们先利用下面代码
ZonedDateTime.now()
获得当前时间,这个时间的格式可能是
2022-07-28T09:48:47.264611600+08:00[Asia/Shanghai]
下面在yml配置中添加新的断言配置
使用After设置必须在指定时间之后访问
routes: # gateway开始编写路由信息
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- After=2022-07-28T09:46:47.264611600+08:00[Asia/Shanghai]
必须在指定时间之后才能访问服务
否则发生404错误拒绝访问
需要注意测试时,先启动Nacos,再启动shanghai之后启动gateway
测试时必须通过9000端口访问才能有效果
使用Before设置必须在指定时间之前访问
routes: # gateway开始编写路由信息
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- Before=2022-07-28T09:46:47.264611600+08:00[Asia/Shanghai]
使用Between设置必须在指定时间之间访问
routes: # gateway开始编写路由信息
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- Between=2022-07-28T09:54:30.264611600+08:00[Asia/Shanghai],2022-07-28T09:55:00.264611600+08:00[Asia/Shanghai]
要求指定请求参数
Query断言,判断是否包含指定的参数名称,包含指定参数名称才能通过路由
routes: # gateway开始编写路由信息
- id: gateway-shanghai
uri: lb://shanghai
predicates:
- Path=/sh/**
- Query=name # 检查请求中是否包含name属性,包含才能路由
内置过滤器
Gateway还提供的内置过滤器
不要和我们学习的filter混淆
内置过滤器允许我们在路由请求到目标资源的同时,对这个请求进行一些加工或处理
常见过滤器也有一些
我们给大家演示一下AddRequestParameter过滤器
它的作用是在请求中添加参数
routes: # gateway开始编写路由信息
- id: gateway-shanghai
uri: lb://shanghai
filters:
- AddRequestParameter=age,80
predicates:
- Path=/sh/**
- Query=name # 检查请求中是否包含name属性,包含才能路由
在shanghai的控制器方法中添加代码接收name,age的值
@RestController
@RequestMapping("/sh")
public class ShanghaiController {
@GetMapping("/show")
public String show(String name,Integer age){
System.out.println(ZonedDateTime.now());
return "这里是上海!"+name+","+age;
}
}
重启shanghai和gateway进行测试
http://localhost:9000/sh/show?name=tom
因为过滤器的存在,控制器可以获取网关过滤器添加的参数值
// csmall项目网关 == day06