0.代码

https://github.com/fengdaizang/OpenAPI

1.引入相关依赖

pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>OpenAPI</artifactId>
<groupId>com.fdzang.microservice</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion> <artifactId>api-gateway</artifactId> <dependencies>
     <!-- 公共模块引入了web模块,会与gateway产生冲突,故排除 -->
<dependency>
<groupId>com.fdzang.microservice</groupId>
<artifactId>api-common</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.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency> <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency> <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency> <dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency> <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency> <dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
     <!-- 引入gateway模块 -->    
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency>

     <!-- 引入eureka模块 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency>

     <!-- 引入openfeign模块,这里不要用feign,Springboot2.0已弃用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency> <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>${spring.cloud.starter.version}</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>

2.配置Gateway

server:
port: 7000

#注册到eureka
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7003/eureka/

#配置gateway拦截规则
spring:
application:
name: api-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: gateway
uri: http://www.baidu.com
predicates:
- Path=/**

#这里定义了鉴权的服务名,以及白名单
auth:
service-id: api-auth-v1
gateway:
white:
- /login #这里是id生成器的配置,Twitter-Snowflake
IdWorker:
workerId: 122
datacenterId: 1231

3.过滤器

3.1.ID生成拦截

对每个请求生成一个唯一的请求id

package com.fdzang.microservice.gateway.gateway;

import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; /**
* 生成一个请求的特定id
* @author tanghu
* @Date: 2019/11/5 18:42
*/
@Slf4j
@Component
public class SerialNoFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest(); String requestId= request.getHeaders().getFirst(GatewayConstant.REQUEST_TRACE_ID);
if (StringUtils.isEmpty(requestId)) {
Object attribute = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
if (attribute == null) {
requestId = String.valueOf(IdWorker.getWorkerId());
exchange.getAttributes().put(GatewayConstant.REQUEST_TRACE_ID,requestId);
}
}else{
exchange.getAttributes().put(GatewayConstant.REQUEST_TRACE_ID,requestId);
} return chain.filter(exchange);
} @Override
public int getOrder() {
return GatewayConstant.Order.SERIAL_NO_ORDER;
}
}

3.2.鉴权拦截

获取请求头中的鉴权信息,对信息校验,这里暂时没有做(AuthResult authService.auth(AuthRequest request)),这里需求请求其他模块对请求信息进行校验,返回校验结果

package com.fdzang.microservice.gateway.gateway;

import com.fdzang.microservice.common.entity.auth.AuthCode;
import com.fdzang.microservice.common.entity.auth.AuthRequest;
import com.fdzang.microservice.common.entity.auth.AuthResult;
import com.fdzang.microservice.gateway.service.AuthService;
import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.WhiteUrl;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import java.util.List;
import java.util.Map;
import java.util.TreeMap; /**
* 权限校验
* @author tanghu
* @Date: 2019/10/22 18:00
*/
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered { @Autowired
private AuthService authService; @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestId = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
String url = exchange.getRequest().getURI().getPath(); ServerHttpRequest request = exchange.getRequest(); //跳过白名单
if(null != WhiteUrl.getWhite() && WhiteUrl.getWhite().contains(url)){
return chain.filter(exchange);
} //获取权限校验部分
//Authorization: gateway:{AccessId}:{Signature}
String authHeader = exchange.getRequest().getHeaders().getFirst(GatewayConstant.AUTH_HEADER);
if(StringUtils.isBlank(authHeader)){
log.warn("request has no authorization header, uuid:{}, request:{}",requestId, url); throw new IllegalArgumentException("bad request");
} List<String> auths = Splitter.on(":").trimResults().omitEmptyStrings().splitToList(authHeader);
if(CollectionUtils.isEmpty(auths) || auths.size() != 3 || !GatewayConstant.AUTH_LABLE.equals(auths.get(0))){
log.warn("bad authorization header, uuid:{}, request:[{}], header:{}",
requestId, url, authHeader); throw new IllegalArgumentException("bad request");
} //校验时间戳是否合法
String timestamp = exchange.getRequest().getHeaders().getFirst(GatewayConstant.TIMESTAMP_HEADER);
if (StringUtils.isBlank(timestamp) || isTimestampExpired(timestamp)) {
log.warn("wrong timestamp:{}, uuid:{}, request:{}",
timestamp, requestId, url);
} String accessId = auths.get(1);
String sign = auths.get(2); String stringToSign = getStringToSign(request, timestamp); AuthRequest authRequest = new AuthRequest();
authRequest.setAccessId(accessId);
authRequest.setSign(sign);
authRequest.setStringToSign(stringToSign);
authRequest.setHttpMethod(request.getMethodValue());
authRequest.setUri(url); AuthResult authResult = authService.auth(authRequest); if (authResult.getStatus() != AuthCode.SUCEESS.getAuthCode()) {
log.warn("checkSign failed, uuid:{}, accessId:{}, request:[{}], error:{}",
requestId, accessId, url, authResult.getDescription());
throw new RuntimeException(authResult.getDescription());
} log.info("request auth finished, uuid:{}, orgCode:{}, userName:{}, accessId:{}, request:{}, serviceName:{}",
requestId, authResult.getOrgCode(),
authResult.getUsername(), accessId,
url, authResult.getServiceName()); exchange.getAttributes().put(GatewayConstant.SERVICE_NAME,authResult.getServiceName()); return chain.filter(exchange);
} /**
* 获取原始字符串(签名前)
* @param request
* @param timestamp
* @return
*/
private String getStringToSign(ServerHttpRequest request, String timestamp){
// headers
TreeMap<String, String> headersInSign = new TreeMap<>();
HttpHeaders headers = request.getHeaders();
for (Map.Entry<String,List<String>> header:headers.entrySet()) {
String key = header.getKey();
if (key.startsWith(GatewayConstant.AUTH_HEADER_PREFIX)) {
headersInSign.put(key, header.getValue().get(0));
}
} StringBuilder headerStringBuilder = new StringBuilder();
for (Map.Entry<String, String> entry : headersInSign.entrySet()) {
headerStringBuilder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
}
String headerString = null;
if (headerStringBuilder.length() != 0) {
headerString = headerStringBuilder.deleteCharAt(headerStringBuilder.length()-1).toString();
} // Url_String
TreeMap<String, String> paramsInSign = new TreeMap<>();
MultiValueMap<String, String> parameterMap = request.getQueryParams();
if (MapUtils.isNotEmpty(parameterMap)) {
for (Map.Entry<String, List<String>> entry : parameterMap.entrySet()) {
paramsInSign.put(entry.getKey(), entry.getValue().get(0));
}
} // 原始url
String originalUrl = request.getURI().getPath(); StringBuilder uriStringBuilder = new StringBuilder(originalUrl);
if (!parameterMap.isEmpty()) {
uriStringBuilder.append("?");
for (Map.Entry<String, String> entry : paramsInSign.entrySet()) {
uriStringBuilder.append(entry.getKey());
if (StringUtils.isNotBlank(entry.getValue())) {
uriStringBuilder.append("=").append(entry.getValue());
}
uriStringBuilder.append("&");
}
uriStringBuilder.deleteCharAt(uriStringBuilder.length()-1);
} String uriString = uriStringBuilder.toString(); String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE); //这里可以对请求参数进行MD5校验,暂时不做
String contentMd5 = headers.getFirst(GatewayConstant.CONTENTE_MD5); String[] parts = {
request.getMethodValue(),
StringUtils.isNotBlank(contentMd5) ? contentMd5 : "",
StringUtils.isNotBlank(contentType) ? contentType : "",
timestamp,
headerString,
uriString
}; return Joiner.on(GatewayConstant.STRING_TO_SIGN_DELIM).skipNulls().join(parts);
} /**
* 校验时间戳是否超时
* @param timestamp
* @return
*/
private boolean isTimestampExpired(String timestamp){
long l = NumberUtils.toLong(timestamp, 0L);
if (l == 0) {
return true;
} return Math.abs(System.currentTimeMillis() - l) > GatewayConstant.EXPIRE_TIME_SECONDS *1000;
} @Override
public int getOrder() {
return GatewayConstant.Order.AUTH_ORDER;
}
}

3.3.服务分发

根据鉴权后的结果能得到服务名,然后重写路由以及请求,对该次请求进行转发

package com.fdzang.microservice.gateway.gateway;

import com.fdzang.microservice.gateway.util.GatewayConstant;
import com.fdzang.microservice.gateway.util.WhiteUrl;
import com.google.common.base.Splitter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import java.util.List; /**
* @author tanghu
* @Date: 2019/11/6 15:39
*/
@Slf4j
@Component
public class ModifyRequestFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
ServerHttpRequest request = exchange.getRequest(); //跳过白名单
if(null != WhiteUrl.getWhite() && WhiteUrl.getWhite().contains(url)){
return chain.filter(exchange);
} String serviceName = exchange.getAttribute(GatewayConstant.SERVICE_NAME); //修改路由
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
Route newRoute = Route.async()
.asyncPredicate(route.getPredicate())
.filters(route.getFilters())
.id(route.getId())
.order(route.getOrder())
.uri(GatewayConstant.URI.LOAD_BALANCE+serviceName).build(); exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR,newRoute); //修改请求路径
List<String> strings = Splitter.on("/").omitEmptyStrings().trimResults().limit(3).splitToList(url);
String newServletPath = "/" + strings.get(2); ServerHttpRequest newRequest = request.mutate().path(newServletPath).build(); return chain.filter(exchange.mutate().request(newRequest).build());
} @Override
public int getOrder() {
return GatewayConstant.Order.MODIFY_REQUEST_ORDER;
}
}

3.4.统一响应

对响应进行统一封装

package com.fdzang.microservice.gateway.gateway;

import com.alibaba.fastjson.JSON;
import com.fdzang.microservice.common.entity.ApiResult;
import com.fdzang.microservice.gateway.entity.GatewayError;
import com.fdzang.microservice.gateway.entity.GatewayResult;
import com.fdzang.microservice.gateway.entity.GatewayResultEnums;
import com.fdzang.microservice.gateway.util.GatewayConstant;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import java.nio.charset.Charset; /**
* @author tanghu
* @Date: 2019/11/7 8:58
*/
@Slf4j
@Component
public class ModifyResponseFilter implements GlobalFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestId = exchange.getAttribute(GatewayConstant.REQUEST_TRACE_ID);
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
//释放掉内存
DataBufferUtils.release(dataBuffer); String originalbody = new String(content, Charset.forName("UTF-8"));
String finalBody = originalbody; ApiResult apiResult = JSON.parseObject(originalbody,ApiResult.class); GatewayResult result = new GatewayResult();
result.setCode(GatewayResultEnums.SUCC.getCode());
result.setMsg(GatewayResultEnums.SUCC.getMsg());
result.setReq_id(requestId);
if (apiResult.getCode() == null && apiResult.getMsg() == null) {
// 尝试解析body为网关的错误信息
GatewayError gatewayError = JSON.parseObject(originalbody,GatewayError.class);
result.setSub_code(gatewayError.getStatus());
result.setSub_msg(gatewayError.getMessage());
} else {
result.setSub_code(apiResult.getCode());
result.setSub_msg(apiResult.getMsg());
} result.setData(apiResult.getData()); finalBody = JSON.toJSONString(result); return bufferFactory.wrap(finalBody.getBytes());
}));
} return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
} @Override
public int getOrder() {
return GatewayConstant.Order.MODIFY_RESPONSE_ORDER;
}
}

4.测试

10:25:54.961 [main] INFO  c.f.microservice.mock.util.SignUtil - StringToSign:
GET 1573093554201
/v2/base/zuul/tag/getMostUsedTags?from=2017-11-25 00:00:00&plate_num=部A11110&to=2017-11-30 00:00:00
10:25:54.979 [main] INFO c.f.microservice.mock.util.HttpUtil - sign:Y+usbpHlwOw4F2sq4b0pNjgXGDAXoYgs1syOOPxPFAE=
10:25:59.868 [main] INFO com.fdzang.microservice.mock.Demo - {"code":0,"data":[{"tagPublishedRefCount":3,"tagTitle":"Solo","id":"1533101769023","tagReferenceCount":3},{"tagPublishedRefCount":1,"tagTitle":"tetet","id":"1559285894006","tagReferenceCount":1}],"msg":"succ","req_id":"2627469547766022144","sub_code":0,"sub_msg":"ok"} Process finished with exit code 0

由返回结果,可知此次请求完成。

5.注意事项

转发的目标服务需要跟网关注册在同一个注册中心下,路由uri配置为 lb://service_name,则会转发到对应的服务下,并且gateway会自动采用负载均衡机制

响应请求的顺序需要小于 NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER 该值为-1

其他拦截器的顺序无固定要求,值越小越先执行

SpringCloud:搭建基于Gateway的微服务网关(二)的更多相关文章

  1. SpringCloud:搭建基于Gateway的微服务网关(一)

    1.需求 最近在尝试着写一个开放平台,于是先搭建网关. 作用:统一的请求入口,完成对请求的跟踪,限流(未做),鉴权,分发,封装响应 2.工作原理 2.1.请求 在开放平台中申请对接口的使用,申请通过后 ...

  2. 小D课堂 - 新版本微服务springcloud+Docker教程_6-06 zuul微服务网关集群搭建

    笔记 6.Zuul微服务网关集群搭建     简介:微服务网关Zull集群搭建 1.nginx+lvs+keepalive      https://www.cnblogs.com/liuyisai/ ...

  3. 【SpringCloud构建微服务系列】微服务网关Zuul

    一.为什么要用微服务网关 在微服务架构中,一般不同的微服务有不同的网络地址,而外部客户端(如手机APP)可能需要调用多个接口才能完成一次业务需求.例如一个电影购票的手机APP,可能会调用多个微服务的接 ...

  4. 基于SpringBoot-Dubbo的微服务快速开发框架

    简介: 基于Dubbo的分布式/微服务基础框架,为前端提供脚手架开发服务,结合前一篇--Web AP快速开发基础框架,可快速上手基于Dubbo的分布式服务开发,项目代码: https://github ...

  5. SpringCloud Gateway微服务网关实战与源码分析-上

    概述 定义 Spring Cloud Gateway 官网地址 https://spring.io/projects/spring-cloud-gateway/ 最新版本3.1.3 Spring Cl ...

  6. springcloud(十四):搭建Zuul微服务网关

    springcloud(十四):搭建Zuul微服务网关 1. 2. 3. 4.

  7. 使用 Node.js 搭建微服务网关

    目录 Node.js 是什么 安装 node.js Node.js 入门 Node.js 应用场景 npm 镜像 使用 Node.js 搭建微服务网关 什么是微服务架构 使用 Node.js 实现反向 ...

  8. 微服务网关 Spring Cloud Gateway

    1.  为什么是Spring Cloud Gateway 一句话,Spring Cloud已经放弃Netflix Zuul了.现在Spring Cloud中引用的还是Zuul 1.x版本,而这个版本是 ...

  9. 微服务网关实战——Spring Cloud Gateway

    导读 作为Netflix Zuul的替代者,Spring Cloud Gateway是一款非常实用的微服务网关,在Spring Cloud微服务架构体系中发挥非常大的作用.本文对Spring Clou ...

随机推荐

  1. Google Chrome 浏览器JS无法更新解决办法

    JS无法更新原因: 浏览器为了加载快,默认是按照自定规则更新缓存,非实时更新. 我们在开发的时候,JS变动很快,需要即时让浏览器加载最新文件,也就是禁用浏览器缓存 (1)使用F12进入开发者模式,找到 ...

  2. Boleto 银行付款

    Boleto是由多家巴西银行共同支持的一种支付方式,在巴西占据绝对主导地位,客户可以到巴西任何一家银行.ATM机.caipiao网点或使用网上银行授权银行转账. 该支付渠道有如下特点:1. 一旦付款, ...

  3. Vue搭建脚手架2

    Vue2.0搭建Vue脚手架(vue-cli) 在网上找了很多的搭建脚手架教程,但都不求甚解.终于找到2个比较好的教程,读者可对比阅读1和2,在这里分享给大家,希望对初学者有所帮助.ps:高手请绕道. ...

  4. 使用DES算法实现加密解密

    使用DES算法实现加密解密 我们常见的加密算法有DES.MD5.IDEA.AES等等,这篇随笔介绍使用DES算法实现加密解密 首先介绍一下DES算法: DES算法为密码体制中的对称密码体制,又被称为美 ...

  5. Vue学习之Webpack小结(十二)

    一.nrm: nrm是专门用来管理和快速切换私人配置的registry; nrm提供了一些最常用的npm包镜像地址,能够让我们快速的切换安装包时候的服务器地址: 二.镜像: 原来   包    刚一开 ...

  6. 单词cymophanite猫眼石cymophanite英语

    金绿石的猫眼石(Cymophanite)是所谓正宗的猫眼石,非常罕有,尤其是5卡以上而质优的,其售价可以高达七万多港元一卡. 相传这类猫眼石是宝石学家的宝石,从此可知其地位在珠宝玉石之中的重要性.颜色 ...

  7. spring中WebApplicationContext、DispatcherServlet与web容器的ServletContext关系梳理

    学习源码过程中,对各种context(上下文)表示很懵逼.特地留此一篇. 1.要了解各个上下文之间的关系.首先走一遍spring在web容器(tomcat)中的启动过程 a) ServletConte ...

  8. PHP的SPL标准库

    1,简介 SPL,全称 Standard PHP Library 中文是 标准PHP类库.是php内置的一些拓展类和拓展接口,其内容包含数据结构.迭代器.接口.异常.SPL函数,文件处理等内容.SPL ...

  9. Linux chage命令详解

    原文 chage命令用于密码实效管理,该是用来修改帐号和密码的有效期限,接下来通过本文给大家介绍Linux chage命令相关知识,本文介绍的非常详细,具有参考借鉴价值,感兴趣的朋友一起学习吧 lin ...

  10. jmeter 实现登录参数化

    业务场景 在测试过程中,一般需要模拟不同的用户登录,这样压测的数据比较平均,也能更好的模拟真实的压力情况. 如果使用同一个用户账号进行测试,那么比如在查询代办的时候,此人的待办太多,也不符合实际的情况 ...