目录
一、实现功能
1、使用spring boot 实现mock平台
2、返回结果数据的存放:
3、如何根据url返回对应的结果?
1.3.1 将请求的URI拼成返回结果的文件/文件夹路径
1.3.2 根据请求的ip不同,返回不同的结果。
1.3.3 根据参数不同,返回对应的数据。
1.4 返回结果不是写死的数据,而是动态数据
1.5 调用其他服务/透传请求
1.6.模拟响应时间
1.7 hook参数
二、注意事项
三、整体架构&实现思路
四、MockController 拦截所有的请求,并对请求进行处理
五、用户的请求与返回结果的封装
5.1 返回结果文件对应的实体类
5.2 最终返回结果信息的封装MockContext
5.3 读取结果文件,封装到实体类YamlUtil
六、根据用户请求,读取对应的结果文件(根据URI拼接成文件的路径)
6.1 责任链设计模式处理文件&文件夹
七、观察者模式对mock数据的处理
7.1 MockContext mock内容类的作用
7.2 观察者模式MockContext 处理
八、 对文件&文件夹的处理逻辑
8.1 请求只有一个返回结果,对应一个文件
8.2 当请求对应一个文件夹时
九、匹配返回结果文件(权重计算)
十、 处理动态变量,使用了装饰器模式
十一、hook参数处理
11.1 方式一:直接对response数据进行处理
11.2 方式二:采用装饰器模式对response数据进行处理
十二、依赖
十二、启动项目
一、实现功能
源码地址:
GitHub - 18713341733/mockServer
实现的功能很简单,就是对url请求的返回结果进行mock。但是里面细节比较多。
本文在讲解的时候,是根据某个功能的实现来针对性的讲解的。
想要整体的了解这个项目,需要自己去看源码。
1、使用spring boot 实现mock平台
2、返回结果数据的存放:
我们可以将需要的返回结果数据,存放在数据库中,或者存放在本地文件。
本项目,将需要的返回结果,存放在了本地的txt文件中。具体存放在哪里根据需要来,各有各的好处。
3、如何根据url返回对应的结果?
将所有的数据都存放在了mock_data 的这个文件夹里面。
返回结果有2种情况。
情况1,就是这个接口请求,只有一种返回结果。那我们只需要有一个txt文件与之对应就好了。
如文件get_order_info 。
情况2,这个接口请求,根据请求传参不同,我们需要返回对应结果。则需要建立一个文件夹,将这个请求的各种返回结果全部存放在这个文件夹下。
我的项目中mock_data文件的路径为:
/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data
1.3.1 将请求的URI拼成返回结果的文件/文件夹路径
我们将服务部署在本地,则mock平台的host为127.0.0.1
将用户请求中的uri拼成一个文件/文件夹的名称,去匹配对应的结果。
如上图中,我们返回结果有文件也有文件。
用户请求
http:127.0.0.1:8080/get/user?id=123&name=zhangsan
获取请求的URI, /get/user
去掉第一个/,将后面的/替换成_,然后拼成一个文件/文件夹的名称。
/get/user ==> get_user
将得到的名称在前边拼接上数据存放的目录,(拼接上mock_data文件的路径)
/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data/get_user
这个路径,是我们通过用户请求的URI拼接出来的,我们再去对应的位置,找这个文件。
在真实的路径/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data/get_user
下,判断get_user 是一个文件夹,则我们取文件夹下的某个一文件作为返回结果进行返回。
用户请求,http:127.0.0.1:8080/get/order_info?id=123&name=zhangsan
获取请求的URI, /get/order/info,转换成文件/文件夹的名称get_order_info
再拼接上mock_data文件的路径,则我们通过用户请求得到的路径为
/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data/get_order_info
我们再去真实的这个路径下,去获取这个文件。判断得到这个路径是一个文件,则我们直接将这个文件里的内容作为返回结果。
1.3.2 根据请求的ip不同,返回不同的结果。
1.3.3 根据参数不同,返回对应的数据。
当一个请求,
http:127.0.0.1:8080/get/user?id=123&name=zhangsan
有多个返回结果,如果根据传参不同,返回对应的结果?
如,get_user请求,对应多个返回结果。
在返回结果中文件中,我们标明对应的传参与这个参数的权重。取权重最大的。
a文件:传参,id=123的权重为8,name=zhangsan的权重为10
b文件:传参,id=456的权重为2,name=zhangsan的权重为4.
则请求:
http:127.0.0.1:8080/get/user?id=123&name=zhangsan
与a文件匹配,id=123与name=zhangsan都命中了,则a文件的权重为8+10=18
与b文件匹配,只命中了name=zhangsan,则b文件的权重为4。
在文件夹内,a匹配权重最大,我们取a的数据作为返回结果。
1.4 返回结果不是写死的数据,而是动态数据
1、 返回的数据中不能全部都是写死的,有的可能是随机的id,有的可能是时间戳,还有的可能是固定格式的数据
2、实际业务有一个case: 要返回merId:xxxx, 但是这个merId的获取,是要从别的业务的接口中获取返回信息。
1.5 调用其他服务/透传请求
mock的返回结果,需要调用数据库,或者其他http请求。
比如10个请求,请求mock服务,其中参数id=123的走mock,id=456的走真实的服务。
所以这个时候如果我们判断id=456了,我们需要去自己真实的拿着请求的参数,我们再去调真实服务。
拿到返回结果,在返回给调用端。
1.6.模拟响应时间
比如服务调我们的mock时,我们是直接给返回。
那要是模拟一下真实的服务处理,比如处理超时,假设用时 3秒在返回。
模拟超时处理
思考: 如果你做线上压测的时候,相应时间不能给返回一个固定值,所以返回是一个区间的概率。
1.7 hook参数
比如请求的时候,请求参数携带一个requestId, 然后requestId本身还是个变化的,也是随机的。
然后在返回的时候,要把这个id带回去,即:虽然返回数据不能写死,但是你也不能自己生成,需要使用请求的参数。
二、注意事项
注意:
在这个项目中,我将请求的返回结果存放在了resouces文件夹下了。
当读取这些文件时,我读取的是文件的绝对路径。当你使用这个项目时,你需要把文件的绝对路径,改成自己的路径。
修改位置:MockContext
三、整体架构&实现思路
1、收集用户输入信息,存到一个实体类里mockContext
2、将用户输入的URI,拼成一个路径。
路径是文件,直接返回文件内容
路径是目录,则读取这个目录下的所有文件。计算每个文件的权重,取出权重最大的返回结果
3、这里处理文件&文件夹用的是责任链模式
4、具体处理文件夹的逻辑,我们这里使用的是观察者模式。
这里用到了mockContext 这个实体类。mockContext 不仅存储了用户的输入数据,
还存储了根据接口读取的文件内容。
观察者模式,多个实体工具类,for循环处理 数据mockContext,处理完再将数据写入mockContext。 多个方法循环处理mockContext,这个mockContext是同一个变量。
四、MockController 拦截所有的请求,并对请求进行处理
package com.example.mockserver.controller;
import cn.hutool.core.io.FileUtil;
import com.example.mockserver.model.MappingParamsEntity;
import com.example.mockserver.model.MockContext;
import com.example.mockserver.model.MockDataInfo;
import com.example.mockserver.service.MockService;
import com.example.mockserver.util.ArrayUtil;
import com.example.mockserver.util.YamlUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@Slf4j
public class MockController {
@Autowired
private HttpServletRequest request;
@Autowired
private MockService mockService;
@RequestMapping("/**")
public String doMock() throws IOException {
log.info("请求的URI---------:"+request.getRequestURI());
log.info("请求IP---------:"+request.getRemoteAddr());
log.info("请求的参数---------:"+request.getParameterMap());
// 将获取的用户数据 ip 参数 URI ,存储到 mockContext 这个类里
MockContext mockContext = MockContext.builder()
.requestIp(request.getRemoteAddr()) // 获取ip
.requestParams(getParams(request.getParameterMap()))
.requestURI(request.getRequestURI()) // 获取请求的URI
.build();
String response = mockService.doMock(mockContext);
return response ;
}
// 获取用户的传参,value是一个数组。这里为了将来处理方便,我们将这数组转成一个字符串。
// 我们默认,这个数据的长度是1,那我们只需要取出来数组的第一个值就可以了。
public Map<String,String> getParams(Map<String,String[]> parameterMap){
Map<String,String> params = parameterMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(),e -> ArrayUtil.getFirst(e.getValue())));
return params;
}
}
1、拦截所有用户请求
2、将用户的所有请求信息,封装到mockContext 这个类里
3、对用户的请求信息mockContext 进行处理。
五、用户的请求与返回结果的封装
用户,进行请求。我们需要用一个实体类来存储用户的请求。
http://127.0.0.1:8081/get/user?name=lisi&id=123
这里我们并没有用一个单独的实体类存储用户信息,我们用了一个比较综合的实体类来存储用户请求信息。 MockContext
MockContext 不仅存储了请求的信息,也存储了对应接口返回的信息。
package com.example.mockserver.model;
import com.example.mockserver.consts.MockConst;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Data
@Builder
public class MockContext {
// 用户传入信息
// 一次用户请求,对应一个MockContext
private String requestURI;
private Map<String,String> requestParams;
private String requestIp;
// 返回结果的List
// 一个接口,对应的返回结果,是一个List
private List<MockDataInfo> mockDataInfoList;
private String finalResponse;
private Long timeout;
private boolean timeoutSet;
private String realUrl;
private boolean realUrlSet;
public void setRealUrl(String realUrl) {
if(StringUtils.isNotEmpty(realUrl)){
this.realUrl = realUrl;
this.realUrlSet = true;
}
}
public void setTimeout(Long timeout){
if(timeout != null && timeout >0 ){
this.timeout = timeout;
timeoutSet = true;
}
}
// 根据uri,得到文件名
// /get/order/info -> get_order_info
// 去掉第一个/ ,取后面的字符串
public String getFileName(){
String str = StringUtils.substringAfter(this.requestURI, "/");
String fileName = StringUtils.replace(str, "/", "_");
return fileName;
}
// 得到文件的路径
public String getFilePath(){
String filePath = MockConst.MOCK_DATA_PATH+"/"+this.getFileName();
return filePath;
}
// 将用户传参,组成一个k=v 的List
public List<String> getParamStringList(){
// 计算权重的方法
// 用户的传参,mockContext.getRequestParams(),是一个Map
// 将用户的传参Map,转换成list。
// 如 k:v, id:123,name:zhangsan 转换成 【"id=123","name=zhangsan"]
List<String> paramStrList = this.getRequestParams().entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.toList());
return paramStrList;
}
}
1、用户的请求信息,包含请求的uri、请求ip、请求参数。这些信息从请求中获取
2、是请求返回结果相关信息的封装。
我们将接口的返回信息,用文件存储了起来。看一下这个存储接口返回
查看aaa文件
mappingHost: 127.0.0.1
timeout: 3000
realUrl: http://www.baidu.com
mappingParams:
- params:
id: 123
weight: 8
- params:
name: "zhangsan"
weight: 10
response: '{"key11":"${random:id:6}","key2":"${random:str:10}","count":3,"person":[{"id":${hook:id}},"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}'
这个请求结果返回文件,存储了3块内容。
1、存储的是请求的逻辑处理。
timeout: 3000 ,用来设置我们接口请求的返回时间。我们不想请求的mock结果瞬间返回,想要有个3s的延迟,再返回结果。这个场景在压测中可能会遇到。比如我们模拟第三方的服务,接口请求不是瞬间返回的,会有一个3秒的处理过程。
realUrl: http://www.baidu.com,请求的透传。应用场景:
同一个接口请求,根据传参不通,部分请求走mock数据,部分请求走真实的服务。
这里就是设置的对应的真实服务url的地址。
2、存贮的是请求的参数。
用户的请求如下:
http://127.0.0.1:8081/get/user?name=lisi&id=123
我们存储的请求参数为,name=lisi&id=123。
注意这里请求参数的格式。
一个请求,有多个参数。每个参数作为一个对象。所有的请求作为一个List集合。
为啥要每个参数单独作为一个对象?
因为我们将来要根据请求的参数不同,匹配对应的返回结果文件。这样好处理一些。
3、请求的返回结果
3存储的就是请求的最终返回结果,一个json格式的数据。
请求的返回结果不一定是死数据,有可能是动态数据。
response: '{"key11":"${random:id:6}","key2":"${random:str:10}","count":3,"person":[{"id":${hook:id}},"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}'
"key2":"${random:str:10}",就是生成一个10位的随机字符串。
"key11":"${random:id:6}",生成一个6位的随机数字
"person":[{"id":${hook:id}},回调数据。
5.1 返回结果文件对应的实体类
返回结果文件,
对应的实体类MockDataInfo
package com.example.mockserver.model;
import lombok.Data;
import java.util.List;
@Data
public class MockDataInfo {
private String mappingHost;
private String response;
private List<MappingParamsEntity> mappingParams;
private Long timeout;
private String realUrl;
}
5.2 最终返回结果信息的封装MockContext
MockContext
package com.example.mockserver.model;
import com.example.mockserver.consts.MockConst;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Data
@Builder
public class MockContext {
// 用户传入信息
// 一次用户请求,对应一个MockContext
private String requestURI;
private Map<String,String> requestParams;
private String requestIp;
// 返回结果的List
// 一个接口,对应的返回结果,是一个List
private List<MockDataInfo> mockDataInfoList;
private String finalResponse;
private Long timeout;
private boolean timeoutSet;
private String realUrl;
private boolean realUrlSet;
public void setRealUrl(String realUrl) {
if(StringUtils.isNotEmpty(realUrl)){
this.realUrl = realUrl;
this.realUrlSet = true;
}
}
public void setTimeout(Long timeout){
if(timeout != null && timeout >0 ){
this.timeout = timeout;
timeoutSet = true;
}
}
// 根据uri,得到文件名
// /get/order/info -> get_order_info
// 去掉第一个/ ,取后面的字符串
public String getFileName(){
String str = StringUtils.substringAfter(this.requestURI, "/");
String fileName = StringUtils.replace(str, "/", "_");
return fileName;
}
// 得到文件的路径
public String getFilePath(){
String filePath = MockConst.MOCK_DATA_PATH+"/"+this.getFileName();
return filePath;
}
// 将用户传参,组成一个k=v 的List
public List<String> getParamStringList(){
// 计算权重的方法
// 用户的传参,mockContext.getRequestParams(),是一个Map
// 将用户的传参Map,转换成list。
// 如 k:v, id:123,name:zhangsan 转换成 【"id=123","name=zhangsan"]
List<String> paramStrList = this.getRequestParams().entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.toList());
return paramStrList;
}
}
当我们一个请求,可能有多个返回结果时,就对应多个结果文件。
经过一系列的逻辑判断,我们最终只能返回其中一个文件。
MockContext 中,针对一个请求,最终只对应一个文件。
private List<MockDataInfo> mockDataInfoList; 就是存在的这个请求,所有的返回结果。
经过一些列逻辑判断,我们最终只能应用其中一个文件中的数据。
private String finalResponse; private Long timeout; private boolean timeoutSet; private String realUrl; private boolean realUrlSet;
这些都是经过逻辑判断后,最终应用的数据。
最终的返回结果finalResponse,最终的超时时间private Long timeout;,
最终的透传地址:private String realUrl;
5.3 读取结果文件,封装到实体类YamlUtil
我们是如何读取结果文件,将数据封装到MockDataInfo实体类中的呢?
YamlUtil
package com.example.mockserver.util;
import com.example.mockserver.model.MockDataInfo;
import org.yaml.snakeyaml.Yaml;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class YamlUtil {
// 这个工具的作用就是,读取yaml文件,将yaml文件里的内容转成一个实体类
// 穿参path,yam文件的路径
// 穿参Class<T> cls,要被转成的实体类
public static <T> T readForObject(String path,Class<T> cls){
try {
Yaml yaml = new Yaml();
// loadAs传参1是文件的流,传参2是要转换成哪个类的对象
T t = yaml.loadAs(new FileInputStream(path), cls);
return t;
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new IllegalArgumentException(e);
}
}
public static void main(String[] args) {
MockDataInfo mockDataInfo = readForObject("/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data/get_user/aaa", MockDataInfo.class);
System.out.println("mockDataInfo = " + mockDataInfo);
}
}
我们借助yaml.loadAs,读取文件,然后将文件结果封装到实体类中。
六、根据用户请求,读取对应的结果文件(根据URI拼接成文件的路径)
用户发起了请求
访问:http://127.0.0.1:8081/get/user?name=lisi
我们如何根据这个请求,去找到对应的文件?
根据URI拼接成文件的路径
将所有的数据都存放在了mock_data 的这个文件夹里面。
返回结果有2种情况。
情况1,就是这个接口请求,只有一种返回结果。那我们只需要有一个txt文件与之对应就好了。
如文件get_order_info 。
情况2,这个接口请求,根据请求传参不同,我们需要返回对应结果。则需要建立一个文件夹,将这个请求的各种返回结果全部存放在这个文件夹下。
我的项目中mock_data文件的路径为:
/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data
将请求的URI拼成返回结果的文件/文件夹路径
我们将服务部署在本地,则mock平台的host为127.0.0.1
将用户请求中的uri拼成一个文件/文件夹的名称,去匹配对应的结果。
如上图中,我们返回结果有文件也有文件。
用户请求
http:127.0.0.1:8080/get/user?id=123&name=zhangsan
获取请求的URI, /get/user
去掉第一个/,将后面的/替换成_,然后拼成一个文件/文件夹的名称。
/get/user ==> get_user
将得到的名称在前边拼接上数据存放的目录,(拼接上mock_data文件的路径)
/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data/get_user
这个路径,是我们通过用户请求的URI拼接出来的,我们再去对应的位置,找这个文件。
在真实的路径/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data/get_user
下,判断get_user 是一个文件夹,则我们取文件夹下的某个一文件作为返回结果进行返回。
用户请求,http:127.0.0.1:8080/get/order_info?id=123&name=zhangsan
获取请求的URI, /get/order/info,转换成文件/文件夹的名称get_order_info
再拼接上mock_data文件的路径,则我们通过用户请求得到的路径为
/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data/get_order_info
我们再去真实的这个路径下,去获取这个文件。判断得到这个路径是一个文件,则我们直接将这个文件里的内容作为返回结果。
6.1 责任链设计模式处理文件&文件夹
根据用户请求,拼接的路径,有可能是一个文件,或者是一个文件夹。正常处理的逻辑就是
if 文件,一个处理逻辑,ifelse 文件夹一个处理逻辑。
这里我们使用责任链设计模式来代替if ..else
AbstractHandler 责任链的处理模版
package com.example.mockserver.chain;
import com.example.mockserver.model.MockContext;
import lombok.Setter;
import java.io.IOException;
@Setter
public abstract class AbstractHandler<T,R> {
// 属性是下一节链条
private AbstractHandler<T,R> nextHandler;
// 当前链条是否能处理
protected abstract boolean preHandle(T t);
// 具体处理的逻辑
protected abstract R onHandle(T t) throws Exception;
// 总的模版处理逻辑
public R doHandle(T t){
// 能处理,直接处理
if (preHandle(t)){
try {
return onHandle(t);
} catch (Exception e) {
e.printStackTrace();
}
}
// 下一节链条处理
if (nextHandler != null){
return nextHandler.doHandle(t);
}
// 所有链条都不能处理,抛出异常
throw new RuntimeException("责任链中,所有链条都不能处理");
}
}
责任链的Manager,ChainManager
package com.example.mockserver.chain;
import com.example.mockserver.model.MockContext;
public class ChainManager {
// 属性就是链条的头
private AbstractHandler<MockContext,String> handler;
// 构造器,私有,不能被new
private ChainManager(){
// 构造器,给属性赋值。链条的头
this.handler = initHandler();
}
private AbstractHandler<MockContext, String> initHandler() {
// 串成链条,返回头
FileHandler fileHandler = new FileHandler();
DirectoryHandle directoryHandle = new DirectoryHandle();
fileHandler.setNextHandler(directoryHandle);
return fileHandler;
}
// ClassHolder属于静态内部类,在加载类Demo03的时候,只会加载内部类ClassHolder,
// 但是不会把内部类的属性加载出来
private static class ClassHolder{
// 这里执行类加载,是jvm来执行类加载,它一定是单例的,不存在线程安全问题
// 这里不是调用,是类加载,是成员变量
private static final ChainManager holder =new ChainManager();
}
public static ChainManager of(){//第一次调用getInstance()的时候赋值
return ClassHolder.holder;
}
// 处理数据
public String doMapping(MockContext mockContext){
return handler.doHandle(mockContext);
}
}
DirectoryHandle 文件夹的处理方式
package com.example.mockserver.chain;
import cn.hutool.core.io.FileUtil;
import com.example.mockserver.model.MockContext;
import com.example.mockserver.observer.ObserverManager;
public class DirectoryHandle extends AbstractHandler<MockContext,String> {
@Override
protected boolean preHandle(MockContext mockContext) {
// 判断是否是目录
return FileUtil.isDirectory(mockContext.getFilePath());
}
@Override
protected String onHandle(MockContext mockContext) throws Exception {
return ObserverManager.of().getMockData(mockContext);
}
}
FileHandler 文件的处理方式
package com.example.mockserver.chain;
import cn.hutool.core.io.FileUtil;
import com.example.mockserver.model.MockContext;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public class FileHandler extends AbstractHandler<MockContext,String>{
@Override
protected boolean preHandle(MockContext mockContext) {
return FileUtil.isFile(mockContext.getFilePath());
}
@Override
protected String onHandle(MockContext mockContext) throws Exception {
return FileUtils.readFileToString(new File(mockContext.getFilePath()),"utf-8");
}
}
七、观察者模式对mock数据的处理
7.1 MockContext mock内容类的作用
我们定义了MockContext 类,这个类包含了用户请求的信息,也包含了返回结果的信息。
定义这个类,是比较巧妙的。
当我们对数据进行mock处理时,
1、加载本地mock文件,转成我们需要的实体类。(处理完后返回MockContext)
2、基于请求的参数,计算权重。(处理完后返回MockContext)
3、透传处理。(处理完后返回MockContext)
4、对返回结果的response 数据进行处理(处理完后返回MockContext)
5、hook 处理(处理完后返回MockContext)
6、对请求的超时mock(处理完后返回MockContext)
我们对请求数据及返回结果,做了很多的处理。每次处理完成后,我们都把更新的数据放到
MockContext中,然后再拿着这个MockContext,给下一个逻辑处理。每个逻辑处理完,都把数据更新在MockContext中。
7.2 观察者模式MockContext 处理
我们要对mock数据进行各种逻辑的处理,每个逻辑处理完,都把数据更新在MockContext,给下一个逻辑处理。这里使用观察者的设计模式。
IObserver
package com.example.mockserver.observer;
import com.example.mockserver.model.MockContext;
public interface IObserver<T> {
void update(T t);
}
ObserverManager
将各种处理逻辑,串成一个链条。
package com.example.mockserver.observer;
import com.example.mockserver.model.MockContext;
import com.google.common.collect.Lists;
import java.util.List;
public class ObserverManager {
// 属性就是List。观察者就是遍历List处理同一个数据
private List<IObserver<MockContext>> observers;
// 构造器,私有,不能被new
private ObserverManager(){
// 构造器,构造这个属性List
// 这是一个工具实体类的表列
observers = Lists.newArrayList(
new LoadMockFileObserver(),// 1、加载本地mock文件,转成我们需要的实体类
new CalcWeightObserver(), // 2 基于请求的参数,计算权重
new RealObserver(), // 插入一个透传
new PackObserver(), // 3 处理数据
new HookResponseObserver(), // 4 hook
new TimeOutObserver() // 超时
);
}
// ClassHolder属于静态内部类,在加载类Demo03的时候,只会加载内部类ClassHolder,
// 但是不会把内部类的属性加载出来
private static class ClassHolder{
// 这里执行类加载,是jvm来执行类加载,它一定是单例的,不存在线程安全问题
// 这里不是调用,是类加载,是成员变量
private static final ObserverManager holder =new ObserverManager();
}
public static ObserverManager of(){//第一次调用getInstance()的时候赋值
return ClassHolder.holder;
}
// 处理数据的方法
public String getMockData(MockContext mockContext){
for (IObserver observer:this.observers){
// 每一个observer,处理mockContext ,都是没有返回值的
// 我们把所有的结果处理结果,都回写进了mockContext
// 这里用的for循环,我们处理的是同一个mockContext,修改的变量得以保存
observer.update(mockContext);
}
return mockContext.getFinalResponse();
}
}
1、先读取接口对应所有的文件,转成一个实体类的List
2、再计算List里,每一个对象的权重大小,取出权重最大的结果。
3、将最后的结果动态变量进行替换。
IObserver
package com.example.mockserver.observer;
import com.example.mockserver.model.MockContext;
public interface IObserver<T> {
void update(T t);
}
LoadMockFileObserver加载本地mock文件,转成我们需要的实体类List
package com.example.mockserver.observer;
import com.example.mockserver.model.MockContext;
import com.example.mockserver.model.MockDataInfo;
import com.example.mockserver.util.YamlUtil;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 加载本地mock文件,转成我们需要的实体类List
*/
public class LoadMockFileObserver implements IObserver<MockContext>{
@Override
public void update(MockContext mockContext) {
// 根据请求的目录,获取目录下所有的文件
File[] files = new File(mockContext.getFilePath()).listFiles();
List<MockDataInfo> mockDataInfoList = Arrays.stream(files)
// 转换,把每一个文件转成对象MockDataInfo
.map(f -> YamlUtil.readForObject(f.getAbsolutePath(), MockDataInfo.class))
// 将数组,转成List
.collect(Collectors.toList());
// 将一个接口,对应的所有返回信息List ,回写进MockContext
mockContext.setMockDataInfoList(mockDataInfoList);
}
}
计算一个接口对应的所有文件(List对象)的权重,返回权重大的结果
CalcWeightObserver
package com.example.mockserver.observer;
import com.example.mockserver.model.MappingParamsEntity;
import com.example.mockserver.model.MockContext;
import com.example.mockserver.model.MockDataInfo;
import com.example.mockserver.util.YamlUtil;
import java.io.File;
import java.util.List;
/**
* 计算一个接口对应的所有文件(List对象)的权重,返回权重大的结果
*/
public class CalcWeightObserver implements IObserver<MockContext>{
@Override
public void update(MockContext mockContext) {
// 定义最终的权重结果 和最终的response
int weightResult = 0;
String response = "";
for(MockDataInfo mockDataInfo: mockContext.getMockDataInfoList()){
// 取出实体类的参数,dd得到当前对象的参数list
List<MappingParamsEntity> mappingParams = mockDataInfo.getMappingParams();
int weight = 0;
for (MappingParamsEntity mappingParamsEntity:mappingParams){
// 将参数转成k=v
String paramStr = mappingParamsEntity.getParams().entrySet().stream()
.map(e -> e.getKey()+"="+e.getValue() )
.findFirst().get(); // 我们这里的Map,只有一个值
// 判断 我们yml文件里指定的参数策略,在不在用户传参url的参数列表里。
if(mockContext.getParamStringList().contains(paramStr)){
// 如果在,则累计权重
weight = weight + mappingParamsEntity.getWeight();
}
}
// 每一个文件的权重比较大小,最终返回权重最大的response
if(weight>weightResult){
weightResult = weight;
response = mockDataInfo.getResponse();
}
}
mockContext.setFinalResponse(response);
}
}
替换动态变量PackObserver
字符串
response: '{"key11":"${random:id:6}","key2":"${random:str:10}","count":3,"person":[{"id":1,"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}'
将${random:id:6} 替换成6位的数字,将${random:str:10} 替换成10位的字符串。
package com.example.mockserver.observer;
import com.example.mockserver.decorator.DecoratorManager;
import com.example.mockserver.model.MockContext;
import com.example.mockserver.util.RandomUtil;
import org.apache.commons.lang3.StringUtils;
public class PackObserver implements IObserver<MockContext> {
// @Override
// public void update(MockContext mockContext) {
// String finalResponse = mockContext.getFinalResponse();
// // random -> 随机字符
// String packResponse = StringUtils.replace(finalResponse,"${random}", RandomUtil.random());
// mockContext.setFinalResponse(packResponse);
//
// }
@Override
public void update(MockContext mockContext) {
String finalResponse = mockContext.getFinalResponse();
// random -> 随机字符
String packResponse = DecoratorManager.of().doPack(finalResponse);
mockContext.setFinalResponse(packResponse);
}
}
ObserverManager
package com.example.mockserver.observer;
import com.example.mockserver.model.MockContext;
import com.google.common.collect.Lists;
import java.util.List;
public class ObserverManager {
// 属性就是List。观察者就是遍历List处理同一个数据
private List<IObserver<MockContext>> observers;
// 构造器,私有,不能被new
private ObserverManager(){
// 构造器,构造这个属性List
// 这是一个工具实体类的表列
observers = Lists.newArrayList(
new LoadMockFileObserver(),// 1、加载本地mock文件,转成我们需要的实体类
new CalcWeightObserver(), // 2 基于请求的参数,计算权重
new PackObserver() // 3 处理数据
);
}
// ClassHolder属于静态内部类,在加载类Demo03的时候,只会加载内部类ClassHolder,
// 但是不会把内部类的属性加载出来
private static class ClassHolder{
// 这里执行类加载,是jvm来执行类加载,它一定是单例的,不存在线程安全问题
// 这里不是调用,是类加载,是成员变量
private static final ObserverManager holder =new ObserverManager();
}
public static ObserverManager of(){//第一次调用getInstance()的时候赋值
return ClassHolder.holder;
}
// 处理数据的方法
public String getMockData(MockContext mockContext){
for (IObserver observer:this.observers){
// 每一个observer,处理mockContext ,都是没有返回值的
// 我们把所有的结果处理结果,都回写进了mockContext
// 这里用的for循环,我们处理的是同一个mockContext,修改的变量得以保存
observer.update(mockContext);
}
return mockContext.getFinalResponse();
}
}
再补充一下完善后的MockContext
package com.example.mockserver.model;
import com.example.mockserver.consts.MockConst;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Data
@Builder
public class MockContext {
// 用户传入信息
// 一次用户请求,对应一个MockContext
private String requestURI;
private Map<String,String> requestParams;
private String requestIp;
// 返回结果的List
// 一个接口,对应的返回结果,是一个List
private List<MockDataInfo> mockDataInfoList;
private String finalResponse;
// 根据uri,得到文件名
// /get/order/info -> get_order_info
// 去掉第一个/ ,取后面的字符串
public String getFileName(){
String str = StringUtils.substringAfter(this.requestURI, "/");
String fileName = StringUtils.replace(str, "/", "_");
return fileName;
}
// 得到文件的路径
public String getFilePath(){
String filePath = MockConst.MOCK_DATA_PATH+"/"+this.getFileName();
return filePath;
}
// 将用户传参,组成一个k=v 的List
public List<String> getParamStringList(){
// 计算权重的方法
// 用户的传参,mockContext.getRequestParams(),是一个Map
// 将用户的传参Map,转换成list。
// 如 k:v, id:123,name:zhangsan 转换成 【"id=123","name=zhangsan"]
List<String> paramStrList = this.getRequestParams().entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.toList());
return paramStrList;
}
}
八、 对文件&文件夹的处理逻辑
8.1 请求只有一个返回结果,对应一个文件
我们直接读取对应的文件就可以了。
8.2 当请求对应一个文件夹时
调用我们上面的观察者模式来处理。进行数据匹配,找到文件夹中,与之对应的文件。
九、匹配返回结果文件(权重计算)
当一个请求,返回结果对应一个文件夹时,我们需要在该文件夹中找到,与之匹配的文件。
我们需要计算这一个接口文件夹下,每个文件,用户命中的权重之和。然后返回权重最大的那一个。
当一个请求,
http:127.0.0.1:8080/get/user?id=123&name=zhangsan
有多个返回结果,如果根据传参不同,返回对应的结果?
如,get_user请求,对应多个返回结果。
在返回结果中文件中,我们标明对应的传参与这个参数的权重。取权重最大的。
a文件:传参,id=123的权重为8,name=zhangsan的权重为10
b文件:传参,id=456的权重为2,name=zhangsan的权重为4.
则请求:
http:127.0.0.1:8080/get/user?id=123&name=zhangsan
与a文件匹配,id=123与name=zhangsan都命中了,则a文件的权重为8+10=18
与b文件匹配,只命中了name=zhangsan,则b文件的权重为4。
在文件夹内,a匹配权重最大,我们取a的数据作为返回结果。
(其实就是将传参得到一个k=v的list,然后遍历文件,将每个文件里面的参数,都转成k=v的,在判断这个在不在list里面,在则权重相加)
先了解一下,我们这个实体类的组成。
用户传参MockContext实体类:
用户的实体类MockContext,传参requestParams 是一个Map。
我们先将Map转成List
Map
{
"id":"123",
"name":"zhangsan"
}
转成list
["id"="123","name"="zhangsan"]
// 计算权重的方法
// 用户的传参,mockContext.getRequestParams(),是一个Map
// 将用户的传参Map,转换成list。
// 如 k:v, id:123,name:zhangsan 转换成 【"id=123","name=zhangsan"]
List<String> paramStrList = mockContext.getRequestParams().entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.toList());
返回数据的实体类MockDataInfo:
所有的参数,是一个List。每个字段(包含权重)是一个小的实体类。实体类的map里只有一个值
1、取出实体类的参数,得到当前对象的参数list
List<MappingParamsEntity> mappingParams = mockDataInfo.getMappingParams();
2、遍历这个list,把每个元素,转成k=v的格式
String paramStr = mappingParamsEntity.getParams().entrySet().stream()
.map(e -> e.getKey()+"="+e.getValue() )
.findFirst().get(); // 我们这里的Map,只有一个值
3、然后再判断这个k=v格式的元素,在不在用户传参的List里面,如果在里面,则权重相加。
// 判断 我们yml文件里指定的参数策略,在不在用户传参url的参数列表里。
if(paramStrList.contains(paramStr)){
// 如果在,则累计权重
weight = weight + mappingParamsEntity.getWeight();
}
整体实现代码
// 如果是文件夹,获取所有文件。是一个数组
// 取出所有的文件
File[] files = file.listFiles();
// 定义最终的权重结果 和最终的response
int weightResult = 0;
String response = "";
// 遍历所有的文件
for(File f:files){
// 循环,将每个文件都转成一个对象。把yml文件转成实体类
MockDataInfo mockDataInfo = YamlUtil.readForObject(f.getAbsolutePath(), MockDataInfo.class);
// 取出实体类的参数,得到当前对象的参数list
List<MappingParamsEntity> mappingParams = mockDataInfo.getMappingParams();
int weight = 0;
//
for (MappingParamsEntity mappingParamsEntity:mappingParams){
// 将参数转成k=v
String paramStr = mappingParamsEntity.getParams().entrySet().stream()
.map(e -> e.getKey()+"="+e.getValue() )
.findFirst().get(); // 我们这里的Map,只有一个值
// 判断 我们yml文件里指定的参数策略,在不在用户传参url的参数列表里。
if(paramStrList.contains(paramStr)){
// 如果在,则累计权重
weight = weight + mappingParamsEntity.getWeight();
}
}
//
if(weight>weightResult){
weightResult = weight;
response = mockDataInfo.getResponse();
}
}
十、 处理动态变量,使用了装饰器模式
在处理动态变量时,我们使用的观察者模式,PackObserver(),调用了
DecoratorManager.of().doPack(finalResponse);
这里具体对字符串进行处理的,用了装饰器模式 。
先处理数字,再处理字符串
1、先写基类的接口IDecorator
这里用了泛型
public interface IDecorator<T> {
T decorate(T data);
}
2、装饰器的基类BaseResponseDecorator
package com.example.mockserver.decorator;
public abstract class BaseResponseDecorator<T> implements IDecorator<T>{
private BaseResponseDecorator<T> decorator;
// 构造器
public BaseResponseDecorator(BaseResponseDecorator<T> decorator) {
this.decorator = decorator;
}
// 自己装饰的方法,重写这个方法
public abstract T onDecorator(T t);
// 整体调用的逻辑
public T decorate(T t){
// 先判断,当前属性是否为空
if(decorator != null){
// 不为空,先让下一节decorator装饰
t = decorator.decorate(t);
// 再自己装饰一次,一共装饰了2次
return onDecorator(t);
}
// 为空,就调用自己的装饰方法。只装饰一次
return onDecorator(t);
}
}
3、对数字的处理RandomIdDecorator
package com.example.mockserver.decorator;
import com.example.mockserver.util.RandomUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RandomIdDecorator extends BaseResponseDecorator<String>{
private static final Pattern PATTERN = Pattern.compile("\\$\\{random:id:(\\d+?)\\}");
// 构造器
public RandomIdDecorator(BaseResponseDecorator<String> decorator) {
super(decorator);
}
@Override
public String onDecorator(String data) {
Matcher matcher = PATTERN.matcher(data);
while (matcher.find()){
String replaceStr = matcher.group(0);
int size = Integer.parseInt(matcher.group(1));
// 替换
data = StringUtils.replace(data,replaceStr, RandomUtil.randomNum(size));
}
return data;
}
}
4、对字符串的处理RandomStrDecorator
package com.example.mockserver.decorator;
import com.example.mockserver.util.RandomUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RandomStrDecorator extends BaseResponseDecorator<String>{
private static final Pattern PATTERN = Pattern.compile("\\$\\{random:str:(\\d+?)\\}");
// 构造器
public RandomStrDecorator(BaseResponseDecorator<String> decorator) {
super(decorator);
}
@Override
public String onDecorator(String data) {
Matcher matcher = PATTERN.matcher(data);
while (matcher.find()){
String replaceStr = matcher.group(0);
int size = Integer.parseInt(matcher.group(1));
// 替换
data = StringUtils.replace(data,replaceStr, RandomUtil.randomStr(size));
}
return data;
}
}
5、manager DecoratorManager
package com.example.mockserver.decorator;
public class DecoratorManager {
// 属性
private IDecorator<String> decorator;
// 构造器,私有,不能被new
private DecoratorManager(){
decorator = new RandomIdDecorator(new RandomStrDecorator(null));
}
// ClassHolder属于静态内部类,在加载类Demo03的时候,只会加载内部类ClassHolder,
// 但是不会把内部类的属性加载出来
private static class ClassHolder{
// 这里执行类加载,是jvm来执行类加载,它一定是单例的,不存在线程安全问题
// 这里不是调用,是类加载,是成员变量
private static final DecoratorManager holder =new DecoratorManager();
}
public static DecoratorManager of(){//第一次调用getInstance()的时候赋值
return ClassHolder.holder;
}
public String doPack(String response){
return decorator.decorate(response);
}
}
6、调用
String packResponse = DecoratorManager.of().doPack(finalResponse);
十一、hook参数处理
/**
* 建设点:请求数据 hook
* 场景:比如请求的时候,请求参数携带一个requestId, 然后requestId本身还是个变化的,也是随机的。
* 然后在返回的时候,要把这个id带回去,即:虽然返回数据不能写死,但是你也不能自己生成,需要使用请求的参数
* {"key1":"${random:id:6}","key2":"${random:str:10}","count":3,"person":[{"id":${hook:userId},"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}
* ${hook:userId} => userId
* 实现思路:
* 1. 要保证request params 有我们需要的参数
* 2. 设定标记 ${hook:userId}
* 3. 通过正则 获取参数名
* 4. 从request params 获取参数值
* 5. 完成替换
* 6. 回写
*/
我们这里讲解两种方式
11.1 方式一:直接对response数据进行处理
HookResponseObserver0
package com.example.mockserver.observer;
import com.example.mockserver.model.MockContext;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 建设点:请求数据 hook
* 场景:比如请求的时候,请求参数携带一个requestId, 然后requestId本身还是个变化的,也是随机的。
* 然后在返回的时候,要把这个id带回去,即:虽然返回数据不能写死,但是你也不能自己生成,需要使用请求的参数
* {"key1":"${random:id:6}","key2":"${random:str:10}","count":3,"person":[{"id":${hook:userId},"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}
* ${hook:userId} => userId
* 实现思路:
* 1. 要保证request params 有我们需要的参数
* 2. 设定标记 ${hook:userId}
* 3. 通过正则 获取参数名
* 4. 从request params 获取参数值
* 5. 完成替换
* 6. 回写
*/
public class HookResponseObserver0 implements IObserver<MockContext> {
private static final Pattern PATTERN = Pattern.compile("\\$\\{hook:(.*?)\\}");
@Override
public void update(MockContext mockContext) {
// 拿到返回结果
String finalResponse = mockContext.getFinalResponse();
// 拿到请求参数
Map<String, String> requestParams = mockContext.getRequestParams();
Matcher matcher = PATTERN.matcher(finalResponse);
while ((matcher.find())){
String replaceStr = matcher.group(0);
String paramName = matcher.group(1);
// 如果用户传参数里不包含要替换的值,直接返回
if(!requestParams.containsKey(paramName)){
break;
}
String value = requestParams.get(paramName);
finalResponse = StringUtils.replace(finalResponse,replaceStr,value);
}
// 回写数据
mockContext.setFinalResponse(finalResponse);
}
}
再把这个HookResponseObserver0 串到观察者的链中就可以了。
11.2 方式二:采用装饰器模式对response数据进行处理
在当前业务场景中,我们只遇到了一种hook数据的处理。当多种hook数据处理时,不太容易扩展。
我们采用装饰器的设计模式,方便扩展,可以处理更多种情况的hook数据。
具体的处理逻辑CommonHookDecorator
package com.example.mockserver.decorator;
import com.example.mockserver.model.HookContext;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CommonHookDecorator extends BaseResponseDecorator<HookContext>{
private static final Pattern PATTERN = Pattern.compile("\\$\\{hook:(.*?)\\}");
public CommonHookDecorator(BaseResponseDecorator<HookContext> decorator) {
super(decorator);
}
@Override
public HookContext onDecorator(HookContext hookContext) {
// 拿到返回结果
String finalResponse = hookContext.getFinalResponse();
// 拿到请求参数
Map<String, String> requestParams = hookContext.getRequestParams();
Matcher matcher = PATTERN.matcher(finalResponse);
while ((matcher.find())){
String replaceStr = matcher.group(0);
String paramName = matcher.group(1);
// 如果用户传参数里不包含要替换的值,直接返回
if(!requestParams.containsKey(paramName)){
break;
}
String value = requestParams.get(paramName);
finalResponse = StringUtils.replace(finalResponse,replaceStr,value);
}
// 回写数据
hookContext.setFinalResponse(finalResponse);
return hookContext;
}
}
DecoratorManager 在装饰器链条中,加入hook装饰器
package com.example.mockserver.decorator;
import com.example.mockserver.model.HookContext;
public class DecoratorManager {
// 属性
private IDecorator<String> packDecorator;
private IDecorator<HookContext> hookDecorator;
// 构造器,私有,不能被new
private DecoratorManager(){
packDecorator = new RandomIdDecorator(new RandomStrDecorator(null));
// 这个hook链条,目前只有一个
hookDecorator = new CommonHookDecorator(null);
}
// ClassHolder属于静态内部类,在加载类Demo03的时候,只会加载内部类ClassHolder,
// 但是不会把内部类的属性加载出来
private static class ClassHolder{
// 这里执行类加载,是jvm来执行类加载,它一定是单例的,不存在线程安全问题
// 这里不是调用,是类加载,是成员变量
private static final DecoratorManager holder =new DecoratorManager();
}
public static DecoratorManager of(){//第一次调用getInstance()的时候赋值
return ClassHolder.holder;
}
public String doPack(String response){
return packDecorator.decorate(response);
}
public HookContext doHook(HookContext hookContext){
return this.hookDecorator.decorate(hookContext);
}
}
把hook 装饰器,加入到观察者模式中
HookResponseObserver
package com.example.mockserver.observer;
import com.example.mockserver.decorator.DecoratorManager;
import com.example.mockserver.model.HookContext;
import com.example.mockserver.model.MockContext;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 建设点:请求数据 hook
* 场景:比如请求的时候,请求参数携带一个requestId, 然后requestId本身还是个变化的,也是随机的。
* 然后在返回的时候,要把这个id带回去,即:虽然返回数据不能写死,但是你也不能自己生成,需要使用请求的参数
* {"key1":"${random:id:6}","key2":"${random:str:10}","count":3,"person":[{"id":${hook:userId},"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}
* ${hook:userId} => userId
* 实现思路:
* 1. 要保证request params 有我们需要的参数
* 2. 设定标记 ${hook:userId}
* 3. 通过正则 获取参数名
* 4. 从request params 获取参数值
* 5. 完成替换
* 6. 回写
*/
public class HookResponseObserver implements IObserver<MockContext> {
private static final Pattern PATTERN = Pattern.compile("\\$\\{hook:(.*?)\\}");
@Override
public void update(MockContext mockContext) {
HookContext hookContext = HookContext.builder()
.finalResponse(mockContext.getFinalResponse())
.requestParams(mockContext.getRequestParams())
.build();
hookContext = DecoratorManager.of().doHook(hookContext);
// 再回血mockContext
mockContext.setFinalResponse(hookContext.getFinalResponse());
}
}
将hook观察者加入到链条中ObserverManager
十二、依赖
springframework.boot 用的2.4.4版本
pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>AutoApi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>AutoApi</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.26</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.7.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
十二、启动项目
---------启动项目
访问:
http://127.0.0.1:8081/get/user?name=zhangsan&id=123
返回结果:
{"key11":"931604","key2":"CsmBVUDAXu","count":3,"person":[{"id":1,"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}