一、背景
读取数据改为从mysql数据库中读取。
Spring Boot 版本为2.4.4
mysql版本为8.0
二、设计
我们以前是直接读取的txt文件,现在该了直接从mysql读取。
本期改造没有使用任何设计模式,直接水流线实现 了我们的功能。
数据库数据:
根据用户请求的uri,作为条件,去表里查询数据。当同一个uri下,有多条数据时,就要去对比请求参数的命中的权重,取一个权重最大的返回数据。
举例:
在数据中,有两条uri为/baidu/search 的数据。
uri | response_content | params |
/baidu/search | {"type":"类型一","key11":"${random:id:6}","key2":"${random:str:10}","count":3,"person":[{"id":${hook:id}},"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}} | {"name=book":1,"classification=Language":5} |
/baidu/search | {"type":"类型二","key11":"${random:id:6}","key2":"${random:str:10}","count":3,"person":[{"id":${hook:id}},"name":"张三"},{"id":2,"name":"李四"}],"object":{"id":1,"msg":"对象里的对象"}} | {"name=book":1,"classification=Math":4} |
数据1的params 为:{"name=book":1,"classification=Language":5}
数据2的params 为:{"name=book":1,"classification=Math":4}
用户的请求为:
http://127.0.0.1:8081/baidu/search?name=book&classification=Math
数据1,命中"name=book",权重为1
数据2,命中"name=book",权重为1,命中"classification=Math",权重为4,数据2的总权重为1+4=5
5>1,所以我们取数据2的返回值进行返回。
对数据2的返回数据,再次进行加工,比如替换随机字符串等等。
三、数据初始化
1、创建为名mockserver的库
2、创建表response
3、插入几条用于演示的数据
/*
Navicat Premium Data Transfer
Source Server : 本地- mysql
Source Server Type : MySQL
Source Server Version : 50710
Source Host : localhost:3306
Source Schema : mockserver
Target Server Type : MySQL
Target Server Version : 50710
File Encoding : 65001
Date: 26/07/2023 16:41:48
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for response
-- ----------------------------
DROP TABLE IF EXISTS `response`;
CREATE TABLE `response` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uri` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`response_content` text COLLATE utf8mb4_unicode_ci,
`params` text COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ----------------------------
-- Records of response
-- ----------------------------
BEGIN;
INSERT INTO `response` (`id`, `uri`, `response_content`, `params`) VALUES (1, '/baidu/search', '{\"type\":\"sougou_1\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\",\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}', '{\"name=book\":1,\"classification=Language\":5}');
INSERT INTO `response` (`id`, `uri`, `response_content`, `params`) VALUES (2, '/baidu/search', '{\"type\":\"sougou_2\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\",\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}', '{\"name=book\":1,\"classification=Math\":4}');
INSERT INTO `response` (`id`, `uri`, `response_content`, `params`) VALUES (3, '/sougou/search', '{\"type\":\"sougou_3\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\",\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}', '{\"name=sougou\":2,\"classification=Math\":4}');
INSERT INTO `response` (`id`, `uri`, `response_content`, `params`) VALUES (4, '/sougou/search', '{\"type\":\"sougou_4\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\",\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}', '{\"name=sougou\":2,\"classification=Language\":5}');
INSERT INTO `response` (`id`, `uri`, `response_content`, `params`) VALUES (5, '/taobao/search', '{\"type\":\"类型一\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\"},\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}', '{\"name=lisi\":1,\"classification=Math\":4}');
INSERT INTO `response` (`id`, `uri`, `response_content`, `params`) VALUES (7, '/taobao/search', '{\"type\":\"张三\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\"},\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}', '{\"name=zhangsan\":1,\"classification=Math\":4}');
COMMIT;
四、相关接口
4.1 创建mock数据
POST请求
URL:http://127.0.0.1:8081/addResponse
请求体为json格式:
{
"uri":"/taobao/search",
"params":"{\"name=zhangsan\":1,\"classification=Math\":4}",
"response_content":"{\"type\":\"张三\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\"},\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}"
}
注意:
请求体中的双引号,一定要通过反斜杠转义
4.2 mock请求
GET请求 URL:http://127.0.0.1:8081/baidu/search?name=book&classification=Language
返回值:
{
"type": "sougou_1",
"key11": "816986",
"key2": "npUtLZWAnu",
"count": 3,
"person": [
{
"id": "${hook:id}",
"name": "张三"
},
{
"id": 2,
"name": "李四"
}
],
"object": {
"id": 1,
"msg": "对象里的对象"
}
}
4.3 修改mock数据
POST
URL:http://127.0.0.1:8081/updateResponse
请求体JSON
{
"id":5,
"uri":"/taobao/search",
"params":"{\"name=lisi\":1,\"classification=Math\":4}",
"response_content":"{\"type\":\"类型一\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\"},\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}"
}
注意:
请求体中的双引号,一定要通过反斜杠转义
4.4 删除mock数据
通过id进行删除
POST
http://127.0.0.1:8081/deleteResponse
请求体json
{
"id":6
}
4.5 查询mock列表
GET
http://127.0.0.1:8081/allResponse
五、对返回值数据进行加工
{
"uri":"/taobao/search",
"params":"{\"name=zhangsan\":1,\"classification=Math\":4}",
"response_content":"{\"type\":\"张三\",\"key11\":\"${random:id:6}\",\"key2\":\"${random:str:10}\",\"count\":3,\"person\":[{\"id\":\"${hook:id}\"},\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}],\"object\":{\"id\":1,\"msg\":\"对象里的对象\"}}"
}
response_conten 就是返回给用户的数据。
${random:id:6} 会生成6位的随机数字
${random:str:10} 会生成10位的随机字符串
六、应用MyBatis
6.1 依赖
- 在Spring Boot项目中添加所需的依赖。在你的项目的pom.xml文件中,添加以下依赖关系:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
<dependency>
6.2 配置数据库连接
配置数据库连接。在application.properties
或application.yml
文件中配置你的数据库连接信息,例如:
application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=dbuser
spring.datasource.password=dbpassword
application.yml:
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mockserver?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
创建MyBatis映射器接口和对应的SQL映射文件。在你的包结构中创建一个映射器接口,并为该接口定义需要执行的数据库操作。同时,在resources目录下创建与映射器接口相对应的XML文件,存放SQL语句和查询逻辑。
在Spring Boot主类中添加@MapperScan注解。在你的Spring Boot主类上添加@MapperScan注解,指定包路径以扫描MyBatis映射器接口。
6.3 实例层entity
存放的是我们需要的实例。
MockOnlyResponse 对应数据库中的response表中的数据
我们还在这个类中,提供了读取数据的方法。
package com.example.mockserver.entity;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
@Data
@NoArgsConstructor
public class MockOnlyResponse {
private String response_content;
private String params;
public MockOnlyResponse(String response_content,String params) {
this.response_content = response_content;
this.params = params;
}
public Map<String, Integer> getMapParams() {
// 将接口拿到的数据库中的请求体,设置为字典
return getMap(this.params);
}
// 将字符串直接转成字典
public Map<String, Integer> getMap(String jsonString) {
// String jsonString = "{\"name=book\":1,\"classification=Language\":5}";
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Integer> dictionary = objectMapper.readValue(jsonString, Map.class);
return dictionary;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
6.4 mybatis的mapper层
接口:
OnlyResponseMapper
package com.example.mockserver.mapper;
import com.example.mockserver.entity.MockOnlyResponse;
import org.springframework.stereotype.Repository;
import java.util.List;
import org.apache.ibatis.annotations.Select;
@Repository
public interface OnlyResponseMapper {
@Select("select r.response_content,r.params from response r where uri = #{uri}")
List<MockOnlyResponse> findByUri(String uri);
}
执行sql,通过uri 查询数据
6.5 spring boot service 层
service接口,OnlyResponseService
package com.example.mockserver.service;
import com.example.mockserver.entity.MockOnlyResponse;
import java.util.List;
public interface OnlyResponseService {
List<MockOnlyResponse> findByUri(String uri);
}
service实现类,ResponseServiceImpl
package com.example.mockserver.service.imp;
import com.example.mockserver.entity.MockOnlyResponse;
import com.example.mockserver.mapper.OnlyResponseMapper;
import com.example.mockserver.service.OnlyResponseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ResponseServiceImpl implements OnlyResponseService {
@Autowired
private OnlyResponseMapper onlyMapper;
@Override
public List<MockOnlyResponse> findByUri(String uri) {
return onlyMapper.findByUri(uri);
}
}
我们在spring boot service 实现类中,注入了mybatis 的 mapper接口。
6.6 controller层
ResponseController
package com.example.mockserver.controller;
import com.example.mockserver.entity.MockOnlyResponse;
import com.example.mockserver.service.OnlyResponseService;
import com.example.mockserver.util.ArrayUtil;
import com.example.mockserver.util.ReplaceRandomUtil;
import lombok.extern.slf4j.Slf4j;
import com.example.mockserver.model.MockContext;
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;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@Slf4j
public class ResponseController {
@Autowired
private OnlyResponseService onlyResponseService;
@Autowired
private HttpServletRequest request;
@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();
// [name=zhangsan, classification=Language]
List<String> userParamStringList = mockContext.getParamStringList();
Integer totalNum = 0;
String response = "";
System.out.println(userParamStringList);
// 开始遍历
List<MockOnlyResponse> mockOnlyResponseList = onlyResponseService.findByUri(mockContext.getRequestURI());
System.out.println(mockOnlyResponseList);
for (MockOnlyResponse mockOnlyResponse : mockOnlyResponseList) {
Integer num = 0;
for(String str:userParamStringList){
// 字典
Map<String, Integer> mapParams = mockOnlyResponse.getMapParams();
if (mapParams.containsKey(str)) {
num += mapParams.get(str);
}
}
if(num>totalNum){
totalNum = num;
response = mockOnlyResponse.getResponse_content();
}
}
// 随机数字/字符串处理
response = ReplaceRandomUtil.replaceRandomFields(response);
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;
}
}
6.7 应用启动层MockServerApplication
package com.example.mockserver;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.example.mockserver.mapper")
public class MockServerApplication {
public static void main(String[] args) {
SpringApplication.run(MockServerApplication.class, args);
}
}
我们需要在项目启动层,增加mapper的扫描。
相对于单纯的mybatis 使用,spring boot 应用mybatis 缺少了mybatis 的总配置文件。
这里填写包名。@MapperScan(basePackages =
七、对数据处理
数据的处理逻辑,主要都在ResponseController 中,我们暂时没有把它封装出来。
逻辑:
1、获取用户的请求 uri 与请求体
2、将用户的请求体转换为Map
3、根据请求uri,去数据库捞取数据,返回值是一个List
4、对List遍历,取权重最大的返回值的数据
5、对得到的数据进行加工,处理随机字符串等等。
八、演示