Getting Started with Microservices---SpringCloud (2)
- 1.Nacos configuration management
- 2.Feign remote call
- 3.Gateway service gateway
1.Nacos configuration management
In addition to being a registration center, Nacos can also be used for configuration management.
1.1. Unified configuration management
When more and more instances of microservices are deployed, reaching tens or hundreds, modifying the configuration of microservices one by one will be frustrating and error-prone. We need a unified configuration management solution that can centrally manage the configuration of all instances.
On the one hand, Nacos can centrally manage configurations, and on the other hand, it can promptly notify microservices when configuration changes to achieve hot updates of configurations.
1.1.1. Add configuration file in nacos
How to manage configuration in nacos?
Then fill in the configuration information in the pop-up form:
Note: The core configuration of the project needs to be hot-updated before it is necessary to put it into nacos management. Some configurations that rarely change are better stored locally in the microservice.
1.1.2. Pull configuration from microservices
The microservice must pull the configuration managed in nacos and merge it with the local application.yml configuration to complete the project startup.
But if application.yml has not been read, how to know the nacos address?
Therefore, spring introduces a new configuration file: the bootstrap.yaml file, which will be read before application.yml. The process is as follows:
1) Introduce nacos-config dependency
First, in the user-service service, introduce the client dependency of nacos-config:
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2) Add bootstrap.yaml
Then, add a bootstrap.yaml file in user-service with the following content:
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
Here, the nacos address will be obtained based on spring.cloud.nacos.server-addr, and then based on
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
As the file id, read the configuration.
In this case, it is to read userservice-dev.yaml
:
3) Read nacos configuration
Add business logic to UserController in user-service and read the pattern.dateformat configuration:
Complete code:
package cn.itcast.user.web;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
// ...略
}
When accessing the page, you can see the effect:
1.2. Configure hot update
Our ultimate goal is to modify the configuration in nacos so that the configuration can take effect in the microservice without restarting, that is, configuration hot update .
To implement hot update of configuration, two methods can be used:
1.2.1. Method 1
Add the annotation @RefreshScope to the class where the variable injected by @Value is located:
1.2.2. Method 2
Use @ConfigurationProperties annotation instead of @Value annotation.
In the user-service service, add a class and read the pattern.dateformat attribute:
package cn.itcast.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
Use this class instead of @Value in UserController:
Complete code:
package cn.itcast.user.web;
import cn.itcast.user.config.PatternProperties;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties patternProperties;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
// 略
}
1.3.Configuration sharing
In fact, when the microservice starts, it will go to nacos to read multiple configuration files, for example:
-
[spring.application.name]-[spring.profiles.active].yaml
, for example: userservice-dev.yaml -
[spring.application.name].yaml
, for example: userservice.yaml
It does not[spring.application.name].yaml
contain the environment, so it can be shared by multiple environments.
Below we use a case to test configuration sharing
1) Add an environment shared configuration
We add a userservice.yaml file in nacos:
2) Read the shared configuration in user-service
In the user-service service, modify the PatternProperties class and read the newly added properties:
In the user-service service, modify the UserController and add a method:
3) Run two UserApplications, using different profiles
Modify the startup item UserApplication2 and change its profile value:
In this way, the profile used by UserApplication (8081) is dev, and the profile used by UserApplication2 (8082) is test.
Start UserApplication and UserApplication2
Visit http://localhost:8081/user/prop, the result is:
Visit http://localhost:8082/user/prop, the result is:
It can be seen that both the dev and test environments have read the value of the envSharedValue attribute.
4) Configure the priority of sharing
When the same attribute appears in nacos and service local at the same time, the priority is divided into high and low:
1.4. Build Nacos cluster
In the Nacos production environment, it must be deployed in a cluster state. For the deployment method, please refer to the documents in the pre-course materials:
2.Feign remote call
Let’s first look at the code we used to use RestTemplate to initiate remote calls:
The following problems exist:
•Poor code readability and inconsistent programming experience
•URLs with complex parameters are difficult to maintain
Feign is a declarative http client, official address : https://github.com/OpenFeign/feign
Its function is to help us elegantly implement the sending of http requests and solve the problems mentioned above.
2.1.Feign replaces RestTemplate
The steps to use Fegin are as follows:
1) Introduce dependencies
We introduce the feign dependency in the pom file of the order-service service:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2) Add annotations
Add annotations to the startup class of order-service to enable Feign's function:
3) Write Feign client
Create a new interface in order-service with the following content:
package cn.itcast.order.client;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
This client is mainly based on SpringMVC annotations to declare remote call information, such as:
- Service name: userservice
- Request method: GET
- Request path:/user/{id}
- Request parameters: Long id
- Return value type: User
In this way, Feign can help us send http requests without using RestTemplate ourselves.
4) Test
Modify the queryOrderById method in the OrderService class in order-service and use Feign client instead of RestTemplate:
Doesn't it look more elegant?
5) Summary
Steps to use Feign:
①Introduce dependencies
② Add @EnableFeignClients annotation
③ Write FeignClient interface
④ Use the methods defined in FeignClient instead of RestTemplate
2.2. Custom configuration
Feign can support many custom configurations, as shown in the following table:
type | effect | illustrate |
---|---|---|
feign.Logger.Level | Modify log level | Contains four different levels: NONE, BASIC, HEADERS, FULL |
feign.codec.Decoder | parser for response results | Parse the results of http remote calls, such as parsing json strings into java objects |
feign.codec.Encoder | Request parameter encoding | Encode request parameters for easy sending via http request |
feign. Contract | Supported annotation formats | The default is SpringMVC annotation |
feign. Retryer | Failure retry mechanism | Retry mechanism for failed requests. The default is none, but Ribbon's retry will be used. |
Under normal circumstances, the default value is sufficient for our use. If you want to customize it, you only need to create a custom @Bean to override the default Bean.
The following uses logs as an example to demonstrate how to customize the configuration.
2.2.1.Configuration file method
Modifying the log level of feign based on the configuration file can target a single service:
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
Or for all services:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
There are four levels of logs:
- NONE: Do not record any log information, this is the default value.
- BASIC: Only record the requested method, URL, and response status code and execution time
- HEADERS: Based on BASIC, additional header information of requests and responses is recorded.
- FULL: Record the details of all requests and responses, including header information, request body, and metadata.
2.2.2.Java code method
You can also modify the log level based on Java code. First declare a class and then declare a Logger.Level object:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
If you want it to take effect globally , put it in the @EnableFeignClients annotation of the startup class:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
If it takes effect locally , put it in the corresponding @FeignClient annotation:
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
2.3.Feign usage optimization
The bottom layer of Feign initiates http requests and relies on other frameworks. Its underlying client implementation includes:
-
URLConnection: Default implementation, does not support connection pooling
-
Apache HttpClient: supports connection pooling
-
OKHttp: supports connection pooling
Therefore, the main way to improve Feign's performance is to use a connection pool instead of the default one URLConnection
.
Here we use it Apache
for HttpClient
demonstration.
1) Introduce dependencies
Introduce Apache's HttpClient dependency into the pom file of order-service:
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2) Configure the connection pool
Add configuration in application.yml of order-service:
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
Next, break the point in the method FeignClientFactoryBean
:loadBalance
Start the order-service service in Debug mode. You can see the client here. The bottom layer is Apache HttpClient:
Summary, Feign’s optimization:
1. Try to use basic log level
2. Use HttpClient or OKHttp instead of URLConnection
-
① Introduce feign-httpClient dependency
-
② Turn on the httpClient function in the configuration file and set the connection pool parameters
2.4. Best Practices
The so-called recent practice refers to the experience summarized during use, which is the best way to use it.
From self-study observation, we can find that Feign's client and service provider's controller code are very similar:
feign client:
UserController:
Is there a way to simplify this repetitive code writing?
2.4.1.Inheritance method
Identical code can be shared through inheritance:
1) Define an API interface, use the definition method, and make declarations based on SpringMVC annotations.
2) Feign client and Controller both integrate modified interfaces
advantage:
- Simple
- Enabled code sharing
shortcoming:
-
Service providers and service consumers are tightly coupled
-
The annotation mapping in the parameter list will not be inherited, so the method, parameter list, and annotations must be declared again in the Controller.
2.4.2. Extraction method
Extract Feign's Client into an independent module, and put the POJO related to the interface and the default Feign configuration into this module for use by all consumers.
For example, the default configurations of UserClient, User, and Feign are extracted into a feign-api package, and all microservices reference this dependency package and can be used directly.
2.4.3. Implement extraction-based best practices
1) Extraction
First create a module named feign-api:
Project structure:
Then introduce feign's starter dependency in feign-api
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Then, the UserClient, User, and DefaultFeignConfiguration written in order-service are copied to the feign-api project.
2) Use feign-api in order-service
First, delete the UserClient, User, DefaultFeignConfiguration and other classes or interfaces in order-service.
Introduce the dependency of feign-api into the pom file of order-service:
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
Modify all the import packages related to the above three components in order-service and change them to import the packages in feign-api.
3) Restart the test
After restarting, I found that the service reported an error:
This is because UserClient is now under the cn.itcast.feign.clients package,
The @EnableFeignClients annotation of order-service is under the cn.itcast.order package. It is not in the same package and cannot scan the UserClient.
4) Solve the scanning package problem
method one:
Specify which packages Feign should scan:
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
Method two:
Specify the Client interface that needs to be loaded:
@EnableFeignClients(clients = {
UserClient.class})
3.Gateway service gateway
Spring Cloud Gateway is a new project of Spring Cloud. This project is a gateway developed based on reactive programming and event streaming technologies such as Spring 5.0, Spring Boot 2.0 and Project Reactor. It aims to provide a simple and effective unification for microservice architecture. API routing management method.
3.1. Why do we need a gateway?
Gateway Gateway is the gatekeeper of our services and the unified entrance for all microservices.
Core functional features of the gateway :
- Request routing
- Permission control
- Limiting
Architecture diagram:
Permission control : As the entrance to microservices, the gateway needs to verify whether the user is qualified to request, and if not, intercept it.
Routing and load balancing : All requests must first go through the gateway, but the gateway does not process business, but forwards the request to a certain microservice according to certain rules. This process is called routing. Of course, when there are multiple target services for routing, load balancing is also required.
Current limiting : When the request traffic is too high, the gateway will release the request at the speed that the downstream microservices can accept to avoid excessive service pressure.
There are two types of gateway implementations in Spring Cloud:
- gateway
- zul
Zuul is a Servlet-based implementation and belongs to blocking programming. Spring Cloud Gateway is based on WebFlux provided in Spring 5, which is an implementation of reactive programming and has better performance.
3.2.gateway quick start
Next, we will demonstrate the basic routing function of the gateway. The basic steps are as follows:
- Create a SpringBoot project gateway and introduce gateway dependencies
- Write startup class
- Write basic configuration and routing rules
- Start the gateway service for testing
1) Create gateway service and introduce dependencies
Create service:
Introduce dependencies:
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2) Write startup class
package cn.itcast.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3) Write basic configuration and routing rules
Create application.yml file with the following content:
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
We will Path
proxy all requests that meet the rules to uri
the address specified by the parameter.
In this example, we proxy /user/**
the initial request to lb://userservice
lb which is load balancing, and pulls the service list based on the service name to achieve load balancing.
4) Restart the test
Restart the gateway and access http://localhost:10010/user/1, which complies with /user/**
the rules. The request is forwarded to uri: http://userservice/user/1, and the result is obtained:
5) Flow chart of gateway routing
The entire visit process is as follows:
Summarize:
Gateway construction steps:
-
Create a project and introduce nacos service discovery and gateway dependencies
-
Configure application.yml, including basic service information, nacos address, and routing
Routing configuration includes:
-
Route id: the unique identifier of the route
-
Routing target (uri): the target address of the route, http represents a fixed address, and lb represents load balancing based on the service name
-
Routing assertions (predicates): rules for determining routing,
-
Routing filters: Process requests or responses
Next, focus on learning the detailed knowledge of routing assertions and routing filters.
3.3. Assertion factory
The assertion rules we wrote in the configuration file are just strings. These strings will be read and processed by the Predicate Factory and turned into conditions for routing judgments.
For example, Path=/user/** is matched according to the path. This rule is composed of
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
Class comes
For processing, there are more than a dozen assertion factories like this one in SpringCloudGateway:
name | illustrate | Example |
---|---|---|
After | It is a request after a certain point in time | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | It is a request before a certain point in time | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | It is a request before a certain two points in time. | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | The request must contain certain cookies | - Cookie=chocolate, ch.p |
Header | The request must contain certain headers | - Header=X-Request-Id, \d+ |
Host | The request must be to access a certain host (domain name) | - Host=.somehost.org,.anotherhost.org |
Method | The request method must be the specified method | - Method=GET,POST |
Path | The request path must comply with the specified rules | - Path=/red/{segment},/blue/** |
Query | The request parameters must contain the specified parameters | - Query=name, Jack or - Query=name |
RemoteAddr | The requester's IP must be in the specified range | - RemoteAddr=192.168.1.1/24 |
Weight | Weight processing |
We only need to master the routing engineering of Path.
3.4. Filter factory
GatewayFilter is a filter provided in the gateway, which can process requests entering the gateway and responses returned by microservices:
3.4.1. Types of routing filters
Spring provides 31 different route filter factories. For example:
name | illustrate |
---|---|
AddRequestHeader | Add a request header to the current request |
RemoveRequestHeader | Remove a request header from the request |
AddResponseHeader | Add a response header to the response result |
RemoveResponseHeader | Removes a response header from the response result |
RequestRateLimiter | Limit requested traffic |
3.4.2. Request header filter
Let's take AddRequestHeader as an example to explain.
Requirement : Add a request header to all requests entering userservice: Truth=itcast is freaking awesome!
You only need to modify the application.yml file of the gateway service and add route filtering:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
The current filter is written under the userservice route, so it is only effective for requests to access userservice.
3.4.3.Default filter
If you want it to be effective for all routes, you can write the filter factory to default. The format is as follows:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, Itcast is freaking awesome!
3.4.4. Summary
What does the filter do?
① Process the routing request or response, such as adding request headers
② The filters configured under routing only take effect on requests for the current route.
What is the role of defaultFilters?
① Filter that takes effect on all routes
3.5.Global filters
The gateway provides 31 types of filters learned in the previous section, but the function of each filter is fixed. If we want to intercept requests, we can't do it by doing our own business logic.
3.5.1. Global filter function
The role of the global filter is also to process all requests and microservice responses entering the gateway, which is the same as the role of GatewayFilter. The difference is that GatewayFilter is defined through configuration and the processing logic is fixed; while the logic of GlobalFilter needs to be written and implemented by yourself.
The definition is to implement the GlobalFilter interface.
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
Writing custom logic in filter can achieve the following functions:
- Login status judgment
- Permission verification
- Request current limit, etc.
3.5.2. Customize global filters
Requirements: Define global filters, intercept requests, and determine whether the request parameters meet the following conditions:
-
Whether there is authorization in the parameters?
-
Whether the authorization parameter value is admin
If both are satisfied, release, otherwise intercept
accomplish:
Define a filter in the gateway:
package cn.itcast.gateway.filters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.获取authorization参数
String auth = params.getFirst("authorization");
// 3.校验
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.拦截
// 4.1.禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
3.5.3. Filter execution order
Requests entering the gateway will encounter three types of filters: current route filters, DefaultFilter, and GlobalFilter
After requesting routing, the current routing filter, DefaultFilter, and GlobalFilter will be merged into a filter chain (set), and each filter will be executed in sequence after sorting:
What are the rules for sorting?
- Each filter must specify an order value of type int. The smaller the order value, the higher the priority and the higher the execution order .
- GlobalFilter specifies the order value by implementing the Ordered interface or adding the @Order annotation, which we specify ourselves.
- The order of routing filters and defaultFilter is specified by Spring, and the default is to increase from 1 in the order of declaration.
- When the order values of the filters are the same, they will be executed in the order of defaultFilter > routing filter > GlobalFilter.
For details, you can view the source code:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
The method is to load the defaultFilters first, then load the filters of a certain route, and then merge them.
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
The method will load the global filter, merge it with the previous filter, sort it according to order, and organize the filter chain.
3.6. Cross-domain issues
3.6.1. What are cross-domain issues?
Cross-domain: If the domain name is inconsistent, it means cross-domain, which mainly includes:
-
Different domain names: www.taobao.com and www.taobao.org and www.jd.com and miaosha.jd.com
-
The domain name is the same but the ports are different: localhost:8080 and localhost8081
Cross-domain problem: The browser prohibits cross-domain ajax requests between the request initiator and the server, and the request is intercepted by the browser.
Solution: CORS, you should have learned this before, so I won’t go into details here. Friends who don’t know can check it out
3.6.2. Simulate cross-domain issues
Find the page file of the pre-class materials:
Put it into a web server such as tomcat or nginx, start it and access it.
You can see the following error in the browser console:
Accessing localhost:10010 from localhost:8090 has different ports, which is obviously a cross-domain request.
3.6.3. Solving cross-domain issues
In the application.yml file of the gateway service, add the following configuration:
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期