在此感谢黑马程序员的SpringCloud课程
所有笔记、生活分享首发于个人博客
想要获得最佳的阅读体验(无广告且清爽),请访问本篇笔记
随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢?
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
单体架构的优缺点:
优点: 架构简单、部署成本低
缺点: 耦合度高(维护困难、升级困难)
分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。
分布式架构的优缺点:
优点: 降低服务耦合、有利于服务升级和拓展
缺点: 服务调用关系错综复杂
分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:
人们需要制定一套行之有效的标准来约束分布式架构。
微服务的架构特征:

微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。
因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。
其中在Java领域最引人注目的就是SpringCloud提供的方案了。
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
其中常见的组件包括:

| Release Train | Boot Version |
|---|---|
| 2020.0.x aka llford | 2.4.x |
| Hoxton | 2.2.x,2.3.x (Starting with SR5) |
| Greenwich | 2.1.x |
| Finchley | 2.0.x |
| Edgware | 1.5.x |
| Dalston | 1.5.X |
这里我总结了微服务拆分时的几个原则:

CREATE DATABASE cloud_order;
USE cloud_order;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_order
-- ----------------------------
DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单id',
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称',
`price` bigint(20) NOT NULL COMMENT '商品价格',
`num` int(10) NULL DEFAULT 0 COMMENT '商品数量',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of tb_order
-- ----------------------------
INSERT INTO `tb_order` VALUES (101, 1, 'Apple 苹果 iPhone 12 ', 699900, 1);
INSERT INTO `tb_order` VALUES (102, 2, '雅迪 yadea 新国标电动车', 209900, 1);
INSERT INTO `tb_order` VALUES (103, 3, '骆驼(CAMEL)休闲运动鞋女', 43900, 1);
INSERT INTO `tb_order` VALUES (104, 4, '小米10 双模5G 骁龙865', 359900, 1);
INSERT INTO `tb_order` VALUES (105, 5, 'OPPO Reno3 Pro 双模5G 视频双防抖', 299900, 1);
INSERT INTO `tb_order` VALUES (106, 6, '美的(Midea) 新能效 冷静星II ', 544900, 1);
INSERT INTO `tb_order` VALUES (107, 2, '西昊/SIHOO 人体工学电脑椅子', 79900, 1);
INSERT INTO `tb_order` VALUES (108, 3, '梵班(FAMDBANN)休闲男鞋', 31900, 1);
SET FOREIGN_KEY_CHECKS = 1;
CREATE DATABASE cloud_user;
USE cloud_user;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人',
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES (1, '柳岩', '湖南省衡阳市');
INSERT INTO `tb_user` VALUES (2, '文二狗', '陕西省西安市');
INSERT INTO `tb_user` VALUES (3, '华沉鱼', '湖北省十堰市');
INSERT INTO `tb_user` VALUES (4, '张必沉', '天津市');
INSERT INTO `tb_user` VALUES (5, '郑爽爽', '辽宁省沈阳市大东区');
INSERT INTO `tb_user` VALUES (6, '范兵兵', '山东省青岛市');
SET FOREIGN_KEY_CHECKS = 1;
order-service和user-service,将其配置文件中的数据库修改为自己的配置,随后将这两个服务启动,开始我们的调用案例@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
}
{
"id": 101,
"price": 699900,
"name": "Apple 苹果 iPhone 12 ",
"num": 1,
"userId": 1,
"user": null
}
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
{
"id": 1,
"username": "柳岩",
"address": "湖南省衡阳市"
}
修改order-service中的根据id查询订单业务,要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一并返回

因此,我们需要在order-service 中向user-service 发起一个http 请求,调用http://localhost:8081/user/{userId} 这个接口。
大概步骤如下
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2. 远程查询User
// 2.1 url地址,这里的url是写死的,后面会改进
String url = "http://localhost:8081/user/" + order.getUserId();
// 2.2 发起调用
User user = restTemplate.getForObject(url, User.class);
// 3. 存入order
order.setUser(user);
// 4.返回
return order;
}
}
{
"id": 101,
"price": 699900,
"name": "Apple 苹果 iPhone 12 ",
"num": 1,
"userId": 1,
"user": {
"id": 1,
"username": "柳岩",
"address": "湖南省衡阳市"
}
}
服务提供者:一次业务中,被其他微服务调用的服务(提供接口给其他微服务)服务消费者:一次业务中,调用其他微服务的服务(调用其他微服务提供的接口)问题一:order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?问题二:有多个user-service实例地址,order-service调用时,该如何选择?问题三:order-service如何得知某个user-service实例是否健康,是不是已经宕机?这些问题都需要利用SpringCloud中的注册中心来解决,其中最广为人知的注册中心就是Eureka,其结构如下

那么现在来回答之前的各个问题
问题一 :order-service如何得知user-service实例地址?
问题二:order-service如何从多个user-service实例中选择具体的实例?
问题三:order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
注意:一个微服务,即可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class);
}
}
server:
port: 10086 ## 服务端口
spring:
application:
name: eureka-server ## eureka的服务名称
eureka:
client:
service-url: ## eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
如果出现报错,请检查你的JDK版本是否是17,是17请降级为11,就能正常启动!

<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
-Dserver.port=8082,点击确定之后,在IDEA的服务选项卡中,就会出现两个user-service启动配置,一个端口是8081,一个端口是8082
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
spring:
application:
name: orderservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
@LoadBalanced注解@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2. 远程查询User
// 2.1 url地址,用user-service替换了localhost:8081
String url = "http://user-service/user/" + order.getUserId();
// 2.2 发起调用
User user = restTemplate.getForObject(url, User.class);
// 3. 存入order
order.setUser(user);
// 4.返回
return order;
}
@LoadBalanced注解
LoadBalancerInterceptor
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}

可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:
request.getURI():获取请求uri,本例中就是 http://user-service/user/8originalUri.getHost():获取uri路径的主机名,其实就是服务id,user-servicethis.loadBalancer.execute():处理服务id,和用户请求。这里的this.loadBalancer是LoadBalancerClient类型,我们继续跟入。
继续跟入execute方法:

代码是这样的:
放行后,再次访问并跟踪,发现获取的是8081:果然实现了负载均衡。

public Server chooseServer(Object key) {
if (this.counter == null) {
this.counter = this.createCounter();
}
this.counter.increment();
if (this.rule == null) {
return null;
} else {
try {
return this.rule.choose(key);
} catch (Exception var3) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});
return null;
}
}
}



| 内置负载均衡规则类 | 规则描述 |
|---|---|
| RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
| AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的…ActiveConnectionsLimit属性进行配置。 |
| WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
| ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
| BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
| RandomRule | 随机选择一个可用的服务器。 |
| RetryRule | 重试机制的选择逻辑 |
通过定义IRule实现,可以修改负载均衡规则,有两种方式
所有微服务均有效@Bean
public IRule randomRule(){
return new RandomRule();
}
user-service: ## 给某个微服务配置负载均衡规则,这里是user-service服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule ## 负载均衡规则
注意:一般使用默认的负载均衡规则,不做修改
懒加载,即第一次访问时,才会去创建LoadBalanceClient,请求时间会很长ribbon:
eager-load:
enabled: true ## 开启饥饿加载
clients: user-service ## 指定对user-service这个服务进行饥饿加载,可以指定多个服务
Ribbon负载均衡规则
负载均衡自定义方式
饥饿加载
enable: true
clients:
- user-service
- xxx-service
SpringCloud Alibaba也推出了一个名为Nacos的注册中心startup.cmd -m standalone #单机启动
SpringCloud Alibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos与使用Eureka对于微服务来说,并没有太大区别<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.6.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
注意:同时也要将eureka的依赖注释/删除掉
spring:
cloud:
nacos:
server-addr: localhost:8848
注意:同时也要将eureka的地址注释掉

集群spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ ## 集群名称,杭州
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SH ## 集群名称,上海

默认的ZoneAvoidanceRule并不能根据同集群优先来实现负载均衡
因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ ## 集群名称,杭州
user-service: ## 给某个微服务配置负载均衡规则,这里是user-service服务
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule ## 负载均衡规则
那我们现在访问http://localhost:8080/order/101 ,同时观察三个user-service的日志输出,集群名称为HZ的两个user-service可以看到日志输出,而集群名称为SH的user-service则看不到日志输出
那我们现在将集群名称为HZ的两个user-service服务停掉,那么现在访问http://localhost:8080/order/101, 则集群名称为SH的user-service会输出日志
NacosRule负载均衡策略
注意:若权重修改为0,则该实例
永远不会被访问
我们可以将某个服务的权重修改为0,然后进行更新,然后也不会影响到用户的正常访问别的服务集群,之后我们可以给更新后的该服务,设置一个很小的权重,这样就会有一小部分用户来访问该服务,测试该服务是否稳定(类似于灰度测试)
命名空间 -> 新建命名空间 -> 填写表单,可以创建一个新的namespace
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: f5c56ca7-1bd6-40a4-b5af-75c1ff283594 ## 命名空间,填上图中的命名空间ID

spring:
cloud:
nacos:
discovery:
ephemeral: false ## 设置为非临时实例
Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异

热更新配置列表 -> 点击右侧加号pattern:
dateformat: yyyy-MM-dd HH:mm:ss

注意:只有需要热更新的配置才有放到Nacos管理的必要,基本不会变更的一些配置,还是保存到微服务本地比较好(例如数据库连接配置等)
微服务要拉取Nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动
但如果尚未读取application.yml,又如何得知Nacos地址呢?
Spring引入了一种新的配置文件:bootstrap.yml文件,会在application.yml之前被读取,流程如下
引入nacos-config依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
添加bootstrap.yml
spring:
application:
name: user-service ## 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 ## Nacos地址
config:
file-extension: yaml ## 文件后缀名
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("/test")
public String test() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
打开浏览器,访问http://localhost:8081/user/test, 看到如下结果,则说明确实读取到了配置信息

@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("/test")
public String test() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}
测试是否热更新
2024-05-30 18:42:39pattern:
dateformat: yyyy年MM月dd日 HH:mm:ss
2024年05月30日18:50:07,说明确实是热更新@Value注解pattern.dateformat属性@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
@Value@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Autowired
private PatternProperties patternProperties;
@GetMapping("/test")
public String test() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
}
[spring.application.name]-[spring.profiles.active].yaml,例如:user-service-dev.yaml[spring.application.name].yaml,例如:userservice.yaml[spring.application.name].yaml不包含环境,因此可以被多个环境共享Data ID为user-service.yml文件,编写的配置内容如下pattern:
envSharedValue: 多环境共享属性值
pattern:
dateformat: yyyy/MM/dd HH:mm:ss
env: user-service开发环境配置
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
private String envSharedValue;
private String env;
}
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Autowired
private PatternProperties patternProperties;
@GetMapping("/prop")
public PatternProperties prop(){
return patternProperties;
}
}
pattern:
dateformat: yyyy-MM-dd HH:mm:ss
env: user-service测试环境配置
//dev 环境下
{
"dateformat": "yyyy-MM-dd HH:mm:ss",
"envSharedValue": "多环境共享属性值",
"env": "user-service开发环境配置"
}
//test 环境下
{
"dateformat": "yyyy-MM-dd HH:mm:ss",
"envSharedValue": "多环境共享属性值",
"env": "user-service测试环境配置"
}
Nacos生产环境下一定要部署为集群状态
官方给出的Nacos集群图

其中包含3个Nacos节点,然后一个负载均衡器代理3个Nacos。这里的负载均衡器可以使用Nginx。
我们计划的集群结构

3个Nacos节点的地址
| 节点 | ip | port |
|---|---|---|
| nacos1 | 192.168.150.1 | 8845 |
| nacos2 | 192.168.150.1 | 8846 |
| nacos3 | 192.168.150.1 | 8847 |
搭建集群的基本步骤
CREATE DATABASE IF NOT EXISTS nacos_config;
USE nacos_config;
CREATE TABLE `config_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
`group_id` VARCHAR(255) DEFAULT NULL,
`content` LONGTEXT NOT NULL COMMENT 'content',
`md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` TEXT COMMENT 'source user',
`src_ip` VARCHAR(50) DEFAULT NULL COMMENT 'source ip',
`app_name` VARCHAR(128) DEFAULT NULL,
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',
`c_desc` VARCHAR(256) DEFAULT NULL,
`c_use` VARCHAR(64) DEFAULT NULL,
`effect` VARCHAR(64) DEFAULT NULL,
`type` VARCHAR(64) DEFAULT NULL,
`c_schema` TEXT,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
`group_id` VARCHAR(255) NOT NULL COMMENT 'group_id',
`datum_id` VARCHAR(255) NOT NULL COMMENT 'datum_id',
`content` LONGTEXT NOT NULL COMMENT '内容',
`gmt_modified` DATETIME NOT NULL COMMENT '修改时间',
`app_name` VARCHAR(128) DEFAULT NULL,
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
`group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',
`app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',
`content` LONGTEXT NOT NULL COMMENT 'content',
`beta_ips` VARCHAR(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` TEXT COMMENT 'source user',
`src_ip` VARCHAR(50) DEFAULT NULL COMMENT 'source ip',
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
`group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` VARCHAR(128) NOT NULL COMMENT 'tag_id',
`app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',
`content` LONGTEXT NOT NULL COMMENT 'content',
`md5` VARCHAR(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` TEXT COMMENT 'source user',
`src_ip` VARCHAR(50) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` BIGINT(20) NOT NULL COMMENT 'id',
`tag_name` VARCHAR(128) NOT NULL COMMENT 'tag_name',
`tag_type` VARCHAR(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` VARCHAR(255) NOT NULL COMMENT 'data_id',
`group_id` VARCHAR(128) NOT NULL COMMENT 'group_id',
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT 'tenant_id',
`nid` BIGINT(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` BIGINT(64) UNSIGNED NOT NULL,
`nid` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`data_id` VARCHAR(255) NOT NULL,
`group_id` VARCHAR(128) NOT NULL,
`app_name` VARCHAR(128) DEFAULT NULL COMMENT 'app_name',
`content` LONGTEXT NOT NULL,
`md5` VARCHAR(32) DEFAULT NULL,
`gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` TEXT,
`src_ip` VARCHAR(50) DEFAULT NULL,
`op_type` CHAR(10) DEFAULT NULL,
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` VARCHAR(128) NOT NULL COMMENT 'kp',
`tenant_id` VARCHAR(128) DEFAULT '' COMMENT 'tenant_id',
`tenant_name` VARCHAR(128) DEFAULT '' COMMENT 'tenant_name',
`tenant_desc` VARCHAR(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` VARCHAR(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` BIGINT(20) NOT NULL COMMENT '创建时间',
`gmt_modified` BIGINT(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE `users` (
`username` VARCHAR(50) NOT NULL PRIMARY KEY,
`password` VARCHAR(500) NOT NULL,
`enabled` BOOLEAN NOT NULL
);
CREATE TABLE `roles` (
`username` VARCHAR(50) NOT NULL,
`role` VARCHAR(50) NOT NULL,
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` VARCHAR(50) NOT NULL,
`resource` VARCHAR(255) NOT NULL,
`action` VARCHAR(8) NOT NULL,
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
INSERT INTO users (username, PASSWORD, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
127.0.0.1:8845
127.0.0.1:8846
127.0.0.1:8847
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
将nacos文件夹复制3份,分别命名为:nacos1、nacos2、nacos3
然后分别修改这三个文件夹中的application.properties
server.port=8845
server.port=8846
server.port=8847
4.Nginx反向代理
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
bootstrap.yml中的Nacos地址修改为localhost:80,user-service和order-service中都改spring:
cloud:
nacos:
server-addr: localhost:80 ## Nacos地址 Ngnix做了监听

String url = "http://user-service/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
Feign来解决上面提到的问题Feign的使用步骤如下
引入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
添加注解
@EnableFeignClients注解,开启Feign的功能编写Feign客户端
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
测试
//修改后的代码
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1. 查询订单
Order order = orderMapper.findById(orderId);
// 2. 利用Feign发起http请求,查询用户
User user = userClient.findById(order.getUserId());
// 3. 封账user到order
order.setUser(user);
// 4. 返回
return order;
}
}
总结
| 类型 | 作用 | 说明 |
|---|---|---|
| feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
| feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
| feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
| feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
| feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
feign:
client:
config:
userservice: ## 针对某个微服务的配置
loggerLevel: FULL ## 日志级别
feign:
client:
config:
default: ## 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL ## 日志级别
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; //日志级别设置为 BASIC
}
}
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
@FeignClient(value = "user-service", configuration = DefaultFeignConfiguration.class)
Feign底层发起http请求,依赖于其他框架,其底层客户端实现包括
因此提高Frign的性能主要手段就是使用连接池,代替默认的URLConnection
这里我们使用Apache的HttpClient来演示
引入依赖
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
配置连接池
feign
client:
config:
default: ## default全局的配置
logger-level: BASIC ## 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true ## 开启feign对HttpClient的支持
max-connections: 200 ## 最大的连接数
max-connections-per-route: 50 ## 每个路径的最大连接数
小结,Feign的优化
{% tabs test4 %}
@FeignClient(value = "user-service",configuration = DefaultFeignConfiguration.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
{% endtabs %}
这两部分相同的代码,可以通过继承来共享
public interface UserAPI{
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
{% tabs test4 %}
@FeignClient(value = "user-service")
public interface UserClient extends UserAPI{}
@RestController
public class UserController implents UserAPI{
public User findById(@PathVariable("id") Long id){
// ...实现业务逻辑
}
}
{% endtabs %}
优点
缺点

抽取
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

在order-service中使用feign-api
<dependency>
<groupId>cn.itcast.demogroupId>
<artifactId>feign-apiartifactId>
<version>1.0version>
dependency>
解决包扫描问题
现在UserClient在cn.itcast.feign.clients包下,而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
@EnableFeignClients(clients = {UserClient.class})

下面,我们就来演示一下网关的基本路由功能,基本步骤如下
创建SpringBoot工程gateway,引入网关依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
编写启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
server:
port: 10010 ## 网关端口
spring:
application:
name: gateway ## 服务名称
cloud:
nacos:
server-addr: localhost:80 ## nacos地址(我这里还是用的nginx反向代理,你们可以启动一个单体的nacos,用8848端口)
gateway:
routes:
- id: user-service ## 路由id,自定义,只需要唯一即可
uri: lb://user-service ## 路由的目标地址,lb表示负载均衡,后面跟服务名称
## uri: http://localhost:8081 ## 路由的目标地址,http就是固定地址
predicates: ## 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** ## 这个是按照路径匹配,只要是以/user开头的,就符合规则
- id: order-service ## 按照上面的写法,再配置一下order-service
uri: lb://order-service
predicates:
- Path=/order/** ##注意Path是大写!!!
{
"id": 1,
"username": "柳岩",
"address": "湖南省衡阳市"
}
{
"id": 101,
"price": 699900,
"name": "Apple 苹果 iPhone 12 ",
"num": 1,
"userId": 1,
"user": {
"id": 1,
"username": "柳岩",
"address": "湖南省衡阳市"
}
}

总结
网关搭建的步骤
路由配置包括
接下来我们就来重点学习路由断言和路由过滤器的详细知识
Predicate Factory读取并处理,转变为路由判断的条件Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的,像这样的断言工厂,在SpringCloudGatewway还有十几个| 名称 | 说明 | 示例 |
|---|---|---|
| After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
| Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
| Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
| Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
| Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
| Host | 请求必须是访问某个host(域名) | - Host=.somehost.org,.anotherhost.org |
| Method | 请求方式必须是指定方式 | - Method=GET,POST |
| Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
| Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
| RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
| Weight | 权重处理 |

| 名称 | 说明 |
|---|---|
| AddRequestHeader | 给当前请求添加一个请求头 |
| RemoveRequestHeader | 移除请求中的一个请求头 |
| AddResponseHeader | 给响应结果中添加一个响应头 |
| RemoveResponseHeader | 从响应结果中移除有一个响应头 |
| RequestRateLimiter | 限制请求的流量 |
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
需求:给所有进入user-service的请求都添加一个请求头:Truth=Hello World!
server:
port: 10010 ## 网关端口
spring:
application:
name: gateway ## 服务名称
cloud:
nacos:
server-addr: localhost:8848 ## nacos地址
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- AddRequestHeader=Truth,Hello World! ## 添加请求头
@GetMapping("/test")
public void test(@RequestHeader("Truth") String word) {
System.out.println(word);
}
Hello World!,证明我们的配置已经生效YML
server:
port: 10010 ## 网关端口
spring:
application:
name: gateway ## 服务名称
cloud:
nacos:
server-addr: localhost:80 ## nacos地址
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
default-filters:
- AddRequestHeader=Truth, Hello World! ## 添加请求头
Hello World!!,证明我们的配置已经生效public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono} 返回标示当前过滤器业务结束
*/
Mono filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
需求:定义全局过滤器,拦截请求,判断请求参数是否满足下面条件
如果同时满足,则放行,否则拦截
具体实现如下
@Component
@Order(-1)//执行优先级
public class AuthorizationFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2. 获取authorization参数
String authorization = params.getFirst("authorization");
// 3. 校验
if ("admin".equals(authorization)) {
// 4. 满足需求则放行
return chain.filter(exchange);
}
// 5. 不满足需求,设置状态码,这里的常量底层就是401,在restFul中401表示未登录
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 6. 结束处理
return exchange.getResponse().setComplete();
}
}
重启网关,测试我们的拦截器是否生效,打开浏览器访问http://localhost:10010/user/1,无法正常访问;加上需要的请求参数访问http://localhost:10010/user/1?authorization=admin, 可以看到正常数据
{
"id": 1,
"username": "柳岩",
"address": "湖南省衡阳市"
}
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器

那么排序的规则是什么呢?
每个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前(默认值为2147483647,即int最大值)
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值(像上面我们自定义全局过滤器那样),需要我们自己指定
实现Ordered接口
使用
public class AuthorizationFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2. 获取authorization参数
String authorization = params.getFirst("authorization");
// 3. 校验
if ("admin".equals(authorization)) {
// 4. 满足需求则放行
return chain.filter(exchange);
}
// 5. 不满足需求,设置状态码,这里的常量底层就是401,在restFul中401表示未登录
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 6. 结束处理
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -1;
}
}
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobalFilter的顺序执行
server:
port: 10010 ## 网关端口
spring:
application:
name: gateway ## 服务名称
cloud:
nacos:
server-addr: localhost:80 ## nacos地址
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- AddRequestHeader=Truth, Hello World! ## 1
- AddRequestHeader=Truth, Hello World! ## 2
- AddRequestHeader=Truth, Hello World! ## 3
default-filters:
- AddRequestHeader=Truth, Hello World! ## 1
- AddRequestHeader=Truth, Hello World! ## 2
- AddRequestHeader=Truth, Hello World! ## 3
详细内容,可以查看源码:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链www.baidu.com和www.baidu.org,www.js.com和miaosha.js.comCORS
spring:
cloud:
gateway:
globalcors: ## 全局的跨域处理
add-to-simple-url-handler-mapping: true ## 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: ## 允许哪些网站的跨域请求
- "http://localhost:1234"
allowedMethods: ## 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" ## 允许在请求中携带的头信息
allowCredentials: true ## 是否允许携带cookie
maxAge: 360000 ## 这次跨域检测的有效期