赞
踩
目录
四、MockController 拦截所有的请求,并对请求进行处理
六、根据用户请求,读取对应的结果文件(根据URI拼接成文件的路径)
11.2 方式二:采用装饰器模式对response数据进行处理
源码地址:
GitHub - 18713341733/mockServer
实现的功能很简单,就是对url请求的返回结果进行mock。但是里面细节比较多。
本文在讲解的时候,是根据某个功能的实现来针对性的讲解的。
想要整体的了解这个项目,需要自己去看源码。
我们可以将需要的返回结果数据,存放在数据库中,或者存放在本地文件。
本项目,将需要的返回结果,存放在了本地的txt文件中。具体存放在哪里根据需要来,各有各的好处。
将所有的数据都存放在了mock_data 的这个文件夹里面。
返回结果有2种情况。
情况1,就是这个接口请求,只有一种返回结果。那我们只需要有一个txt文件与之对应就好了。
如文件get_order_info 。
情况2,这个接口请求,根据请求传参不同,我们需要返回对应结果。则需要建立一个文件夹,将这个请求的各种返回结果全部存放在这个文件夹下。
我的项目中mock_data文件的路径为:
/Users/zhaohui/IdeaProjects/mock-server/src/main/resources/mock_data
我们将服务部署在本地,则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
我们再去真实的这个路径下,去获取这个文件。判断得到这个路径是一个文件,则我们直接将这个文件里的内容作为返回结果。
当一个请求,
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、 返回的数据中不能全部都是写死的,有的可能是随机的id,有的可能是时间戳,还有的可能是固定格式的数据
2、实际业务有一个case: 要返回merId:xxxx, 但是这个merId的获取,是要从别的业务的接口中获取返回信息。
mock的返回结果,需要调用数据库,或者其他http请求。
比如10个请求,请求mock服务,其中参数id=123的走mock,id=456的走真实的服务。
所以这个时候如果我们判断id=456了,我们需要去自己真实的拿着请求的参数,我们再去调真实服务。
拿到返回结果,在返回给调用端。
比如服务调我们的mock时,我们是直接给返回。
那要是模拟一下真实的服务处理,比如处理超时,假设用时 3秒在返回。
模拟超时处理
思考: 如果你做线上压测的时候,相应时间不能给返回一个固定值,所以返回是一个区间的概率。
比如请求的时候,请求参数携带一个requestId, 然后requestId本身还是个变化的,也是随机的。
然后在返回的时候,要把这个id带回去,即:虽然返回数据不能写死,但是你也不能自己生成,需要使用请求的参数。
注意:
在这个项目中,我将请求的返回结果存放在了resouces文件夹下了。
当读取这些文件时,我读取的是文件的绝对路径。当你使用这个项目时,你需要把文件的绝对路径,改成自己的路径。
修改位置:MockContext
1、收集用户输入信息,存到一个实体类里mockContext
2、将用户输入的URI,拼成一个路径。
路径是文件,直接返回文件内容
路径是目录,则读取这个目录下的所有文件。计算每个文件的权重,取出权重最大的返回结果
3、这里处理文件&文件夹用的是责任链模式
4、具体处理文件夹的逻辑,我们这里使用的是观察者模式。
这里用到了mockContext 这个实体类。mockContext 不仅存储了用户的输入数据,
还存储了根据接口读取的文件内容。
观察者模式,多个实体工具类,for循环处理 数据mockContext,处理完再将数据写入mockContext。 多个方法循环处理mockContext,这个mockContext是同一个变量。
- 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}},回调数据。
返回结果文件,
对应的实体类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;
-
- }
-
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;
我们是如何读取结果文件,将数据封装到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,读取文件,然后将文件结果封装到实体类中。
用户发起了请求
访问: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
我们再去真实的这个路径下,去获取这个文件。判断得到这个路径是一个文件,则我们直接将这个文件里的内容作为返回结果。
根据用户请求,拼接的路径,有可能是一个文件,或者是一个文件夹。正常处理的逻辑就是
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");
- }
- }
我们定义了MockContext 类,这个类包含了用户请求的信息,也包含了返回结果的信息。
定义这个类,是比较巧妙的。
当我们对数据进行mock处理时,
1、加载本地mock文件,转成我们需要的实体类。(处理完后返回MockContext)
2、基于请求的参数,计算权重。(处理完后返回MockContext)
3、透传处理。(处理完后返回MockContext)
4、对返回结果的response 数据进行处理(处理完后返回MockContext)
5、hook 处理(处理完后返回MockContext)
6、对请求的超时mock(处理完后返回MockContext)
我们对请求数据及返回结果,做了很多的处理。每次处理完成后,我们都把更新的数据放到
MockContext中,然后再拿着这个MockContext,给下一个逻辑处理。每个逻辑处理完,都把数据更新在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;
- }
-
-
- }
我们直接读取对应的文件就可以了。
调用我们上面的观察者模式来处理。进行数据匹配,找到文件夹中,与之对应的文件。
当一个请求,返回结果对应一个文件夹时,我们需要在该文件夹中找到,与之匹配的文件。
我们需要计算这一个接口文件夹下,每个文件,用户命中的权重之和。然后返回权重最大的那一个。
当一个请求,
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
* 场景:比如请求的时候,请求参数携带一个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. 回写
*/
我们这里讲解两种方式
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 串到观察者的链中就可以了。
在当前业务场景中,我们只遇到了一种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":"对象里的对象"}}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。