栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

spring boot 实现mock平台

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

spring boot 实现mock平台

使用spring boot 写mock平台。

一、实现场景: 1.1请求同一个接口,不同的传参/ip返回的结果不一样。

数据的匹配

以create_account接口为例,参数有accountId
accountId=123,返回成功
accountId=456,返回失败
accountId=456,accountName=zhangsan,返回处理中
ip=123.123.123.1 返回成功
ip=123.123.123.2 返回失败

解决方案:

将数据存储在yml文件里。如create_account接口,只返回一个固定的结果,那么我们就将返回值放到一个yml文件里。

如create_name接口,根据传参不同,返回不同的id。一个接口对应一个文件夹, 将create_name接口的返回值写多个yml文件,放到同一个文件夹里。然后再去匹配。

1.2 返回的结果不是写死的,是动态数据,需要对数据进行处理

  • response: {xxxxx,"orderId":123456}
  • response: {xxxxx,"orderId":123456}
  • response: {xxxxx,"createTime":123456}

1、 返回的数据中不能全部都是写死的,有的可能是随机的id,有的可能是时间戳,还有的可能是固定格式的数据

2、实际业务有一个case: 要返回merId:xxxx, 但是这个merId的获取,是要从别的业务的接口中获取返回信息。

1.3 回调能力

就是外部请求到我mock服务之后,我mock服务做了返回,但同时我会按照要求给它去完成某些能力,如:

  • 调http
  • 调公司内部的RPC
  • 或mq
  • 还有可能是写某个db.
1.4响应时间

比如服务调我们的mock时,我们是直接给返回。

那要是模拟一下真实的服务处理,比如处理超时,假设用时 3秒在返回。

模拟超时处理

思考: 如果你做线上压测的时候,相应时间不能给返回一个固定值,所以返回是一个区间的概率。

1.5 hook参数

比如请求的时候,请求参数携带一个requestId, 然后requestId本身还是个变化的,也是随机的。

然后在返回的时候,要把这个id带回去,即:虽然返回数据不能写死,但是你也不能自己生成,需要使用请求的参数。

1.6 透传请求

比如10个请求,请求mock服务,其中参数id=123的走mock,id=456的走真实的服务。

所以这个时候如果我们判断id=456了,我们需要去自己真实的拿着请求的参数,我们再去调真实服务。

拿到返回结果,在返回给调用端。

总结:

其实就是把数据源放在文件里,根据用户的传参,对数据进行处理,然后再返回。

二、依赖

springframework.boot 用的2.4.4版本

pom.xml



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.4.4
         
    
    com.example
    AutoApi
    0.0.1-SNAPSHOT
    AutoApi
    Demo project for Spring Boot
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.yaml
            snakeyaml
            1.26
        
        
            org.projectlombok
            lombok
            1.18.16
            provided
        
        
            com.google.guava
            guava
            30.1.1-jre
        
        
            org.apache.commons
            commons-lang3
            3.11
        
        
            com.squareup.okhttp3
            okhttp
            4.9.0
        
        
            commons-io
            commons-io
            2.6
        
        
            com.google.code.gson
            gson
            2.8.6
        
        
            com.fasterxml.jackson.dataformat
            jackson-dataformat-yaml
            2.12.3
        
        
            org.springframework
            spring-jdbc
            5.3.8
        
        
            mysql
            mysql-connector-java
            8.0.20
        
        
            com.alibaba
            druid
            1.2.6
        
        
            cn.hutool
            hutool-core
            5.7.5
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


三、实现简单的demo

3.1 新建一个controller,简单的建一个服务,试试是否能ping通

 PingController

package com.example.mockserver.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PingController {

    @RequestMapping("/ping")
    public String ping(){
        return "ok";

    }
}

要有@RestController 注解

 

修改application.properties 格式,换成yml  application.yml 。并指定端口号。

application.yml

server:
  port: 8081

启动spring boot ,运行 MockServerApplication 。访问一下127.0.0.1:8081/ping。

返回ok就是正常的。

3.2   新建MockController1,/** 这里指的是任意的URI

MockController1

package com.example.mockserver.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MockController1 {

    @RequestMapping("/**")
    public String doMock(){
        return "do mock server";
    }
}
 

"/**" 这里指的是任意的URI 。启动服务,当请求ping时返回ok,请求其他任何URL时,返回 do mock server

3.3 将不同的接口返回值,放到不同的txt文件里。根据请求的URI,来返回对应的内容。

新建2个txt文件,get_order_info 与 get_user_info 文件。存放不同的内容。

 新建MockController2

MockController2

拿到请求的URI,将URI拼成对应的文件路径,然后读取这个文件。

package com.example.mockserver.controller;


import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
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.IOException;

@RestController
public class MockController2 {
    // 文件存放路径,常量
    public static final String MOCK_DATA_PATH = "mock_data";
    // 注入
    @Autowired
    private HttpServletRequest request;

    @RequestMapping("/**")
    public String doMock() throws IOException {
        // 获取请求的URI
        String requestURI = request.getRequestURI();
        // 将输入的URI,拼成文件的名称
        // /get/order/info => get_order_info
        // 取/get/order/info ,第一个/后面的数据,get/order/info
        requestURI =StringUtils.substringAfter(requestURI,"/");
        // 将/替换成_,将uri拼成文件名称
        String fileName = StringUtils.replace(requestURI, "/", "_");
        // 拼成文件路径
        String filePath = MOCK_DATA_PATH+"/"+fileName;
        // 读取文件
        String response = IOUtils.toString(MockController2.class.getClassLoader().getResourceAsStream(filePath));
        return response;
    }
}

访问 http://127.0.0.1:8081/get/order/info

注意:

我们这里存放mock文件,放到了项目的resources目录下。正常情况下,是不可以这么写的。当我们打包时,会将这个文件一起打进去,这样再修改数据不好修改了。

这个路径应该写磁盘上的某个路径。

方法一,支持用户配置自己的路径

方法二,默认路径,为磁盘上的某个位置,比如“/user/local/aa" 

四、实现思路&实体类的封装 4.1场景:

场景一

1、create_name 接口是一个简单接口,只对应一个文件。则我们读取URI,根据文件名匹配这个文件,读取这个文件,并返回

场景二

2、create_user 接口是一个复杂接口,有2个参数,id 与 name 。

当id=123时,返回A数据

当id=456时,返回B数据

当name=zhangsan时,返回C数据

当name=lisi时,返回D数据。

那么当id与name组合时候,应该怎么返回数据呢?

方式一:

就是把所有的参数字段,拼到文件名称上,我们直接根据文件名称来读取数据。

当参数过多时,导致文件名称非常长,这是不可取的。

方式二:

给每个一个字段设置一个权重,遍历每一个文件,取值最大的。

如匹配name与id字段。我们设置了 a、b、c、d 四个文件。

a文件

mappingHost: 127.0.0.1
mappingParams:
    - params:
        id: 123
      weight: 2
    - params:
        name: "zhangsan"
      weight: 4
response: 'AAA'

b文件

mappingHost: 127.0.0.1
mappingParams:
    - params:
        id: 456
      weight: 2
    - params:
        name: "zhangsan"
      weight: 4
response: 'BBBB'

c文件

mappingHost: 127.0.0.1
mappingParams:
    - params:
        id: 123
      weight: 2
    - params:
        name: "lisi"
      weight: 4
response: 'CCCC'

d文件

mappingHost: 127.0.0.1
mappingParams:
    - params:
        id: 456
      weight: 2
    - params:
        name: "lisi"
      weight: 4
response: 'DDDD'

举例:

当传参id=123&name=zhangsan时,遍历这四个文件。

a文件值为2+4=6

b文件值为4

c文件值为2

d文件值为0

我们取最大的值返回,a文件的值,最大,我们则把a文件返回。

返回值的模版

mappingHost: 127.0.0.1
mappingParams:
    - params:
        id: 123
      weight: 2
    - params:
        name: "zhangsan"
      weight: 4
response: '{"key1":"${random:id}","key2":"value2","count":3,"person":[{"id":1,"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}'
返回值,是一个yml格式的,里面不仅仅包含了返回值response,还包含了传参数。
这里参数是为了匹配哪个文件而设置的。还包含了发起请求的ip

4.2整体思路:

1、简单的接口,对应一个文件,直接读取并返回。复杂接口,计算权重,返回大的文件。

2、得到用户的请求。包含ip,传参,URI。

URL用来匹配文件夹,传参用来计算权重,匹配具体的文件

3、构造用户的请求类。用来储存,传递 用户的请求

4、构造返回值的类,用来储存和计算比较权重,返回权重大的数据。

4.3 构建MockContext类,存储传递用户请求

我们根据用户传进来的ip、传参数和URI返回不同的数据。所以我们建一个MockContext类,用来存储传递这些用户信息。

MockContext

package com.example.mockserver.model;

import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;

@Data
@Builder
public class MockContext {
    private String requestURI;
    private Map requestParams;
    private String requestIp;

    public String getFileName(){
        String str = StringUtils.substringAfter(this.requestURI, "/");
        String fileName = StringUtils.replace(str, "/", "_");
        return fileName;
    }
}

构造MockContext的对象,建造者模式,直接往里面填数据

        // 将获取的用户数据 ip 参数 URI ,存储到 mockContext 这个类里
        MockContext mockContext = MockContext.builder()
                .requestIp(remoteAddr)
                .requestParams(collect)
                .requestURI(uri)
                .build();

4.4 构建MockDataInfo类,存储返回值

我们先查看返回值的模版,是yml格式的。

mappingHost: 127.0.0.1
mappingParams:
    - params:
        id: 123
      weight: 2
    - params:
        name: "zhangsan"
      weight: 4
response: '{"key1":"${random:id}","key2":"value2","count":3,"person":[{"id":1,"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}}'

MockDataInfo 这个类,mappingHost 、response 是字符串。

mappingParams 是一个list。

我们先构造mappingParams  list里面的对象MappingParamsEntity

MappingParamsEntity

package com.example.mockserver.model;

import lombok.Data;

import java.util.Map;

@Data
public class MappingParamsEntity {
    private Map params;
    private int weight;

}

再构建MockDataInfo 这个类

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 mappingParams;


}

构造MockDataInfo类的对象。这里就是读取文件,将文件内容封装在这个类里。

这里我们写一个读取文件,将文件内容转换成这个类的方法。YamlUtil

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 {
    // 这个工具的作用就是,读取返回值的文件,然后转换成一个对象
    public static  T readForObject(String path,Class 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);
    }
}

因为我们文件内容本身是yml格式的,这里我们利用snakeyaml模块的能力,可以直接读取文件内容,并转我们想要的对象。

 T t = yaml.loadAs(new FileInputStream(path), cls);
五、权重如何计算

我们需要计算这一个接口文件夹下,每个文件,用户命中的权重之和。然后返回权重最大的那一个。

(其实就是将传参得到一个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 paramStrList = mockContext.getRequestParams().entrySet().stream()
                .map(e -> e.getKey() + "=" + e.getValue())
                .collect(Collectors.toList());

返回数据的实体类MockDataInfo:

所有的参数,是一个List。每个字段(包含权重)是一个小的实体类。实体类的map里只有一个值

1、取出实体类的参数,得到当前对象的参数list

 List 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 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();
            }

        }

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/993909.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号