Spring Boot与MyBatis结合实现mock平台

news2024/11/13 10:01:13

上一章: 

测开工具:spring boot 实现mock平台_springboot搭建mock_做测试的喵酱的博客-CSDN博客

代码地址:

GitHub - 18713341733/mock: Spring Boot与MyBatis结合 实现对mock平台改造

一、背景 

读取数据改为从mysql数据库中读取。

Spring Boot 版本为2.4.4 

mysql版本为8.0

二、设计

我们以前是直接读取的txt文件,现在该了直接从mysql读取。

本期改造没有使用任何设计模式,直接水流线实现 了我们的功能。

数据库数据:

 根据用户请求的uri,作为条件,去表里查询数据。当同一个uri下,有多条数据时,就要去对比请求参数的命中的权重,取一个权重最大的返回数据。

举例:

在数据中,有两条uri为/baidu/search 的数据。

uriresponse_contentparams
/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 依赖

  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.propertiesapplication.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、对得到的数据进行加工,处理随机字符串等等。

八、演示

九、相关文章

spring boot 应用mybatis_springboot mybatis debug_做测试的喵酱的博客-CSDN博客


                

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/802804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

redis群集(主从复制)

---------------------- Redis 主从复制 ---------------------------------------- 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(Master)&#xff0c;后者称为从节点(Slave)&#xff1b;数据的复制是单向的&#xf…

QT基于TCP协议实现数据传输以及波形绘制——安卓APP及Windows程序双版本

文章代码有非常非常之详细的解析&#xff01;&#xff01;&#xff01;诸位可放心食用 这个玩意我做了两个&#xff0c;一个是安卓app&#xff0c;一个是Windows程序。代码并非全部都是由我从无到有实现&#xff0c;只是实现了我想要的功能。多亏了巨人的肩膀&#xff0c;开源…

农业中的计算机视觉 2023

物体检测应用于检测田间收割机和果园苹果 一、说明 欢迎来到Voxel51的计算机视觉行业聚焦博客系列的第一期。每个月&#xff0c;我们都将重点介绍不同行业&#xff08;从建筑到气候技术&#xff0c;从零售到机器人等&#xff09;如何使用计算机视觉、机器学习和人工智能来推动…

【导入外部jar包到maven项目中--亲测可行】

若项目为springweb项目&#xff0c;则先将jar放到WEB-INF/lib 目录下选中对应的jar包&#xff0c;右键选项 add-lirrary &#xff1b;成功加入之后的jar包是一个项目的目录结构&#xff1a; 至此&#xff0c;项目能够正常运行&#xff0c;在代码周也能够进行导包 转折点&…

Vue2 第二节 ----初识Vue(简单示例,模板语法,数据绑定)

知识点&#xff1a; 1.Vue的简单示例 2.模板语法 3.数据绑定 4.el和data的两种写法 5.MVVM模型 一. Vue的简单实例 <div id"root"><h1>hello, {{name.toUpperCase()}}, {{address}}</h1></div><script type"text/javascript&q…

2023年二季度中国手机销量排行榜:华为逆袭上榜,苹果仅位列第五,第一名很意外!

近日&#xff0c;国际数据公司&#xff08;IDC&#xff09;现发布最新手机季度跟踪报告显示&#xff0c;2023 年第二季度&#xff0c;中国智能手机市场出货量约 6,570 万台&#xff0c;同比下降 2.1%&#xff0c;降幅明显收窄。 今年上半年&#xff0c;中国智能手机市场出货量…

了解Unity编辑器之组件篇Physics 2D(十二)

一、Area Effector 2D区域施加力&#xff09;&#xff1a;用于控制区域施加力的行为 Use Collider Mask&#xff08;使用碰撞器遮罩&#xff09;&#xff1a;启用后&#xff0c;区域施加力仅会作用于特定的碰撞器。可以使用Collider Mask属性选择要作用的碰撞器。 Collider Ma…

揭秘低代码谜团,好用到不行

一、前言 低代码“灵活、快速、低门槛”的标签&#xff0c;为其带来了诸多争议。在低代码平台上是否只能搭建极其简单、无亮点的小功能&#xff1f;低代码带来的“全民程序员”化是否能真正带来社会价值&#xff1f;这是一场繁荣的泡沫假象&#xff0c;还是真实的市场需求&…

浅谈深拷贝与浅拷贝

一、拷贝&#xff08;克隆&#xff09;的意义的场景 意义&#xff1a;保证原数据的完整性和独立性 常见场景&#xff1a;复制数据、函数入参、class构造函数 二、浅拷贝 只克隆对象的第一层级如果属性值是原始数据类型&#xff0c;拷贝其值&#xff0c;即&#xff1a;值拷贝…

anaconda切换python版本

1 查看环境 conda env list结果如下图&#xff0c;左侧表示已下载的环境信息&#xff0c;当前我已经下载了python3.10&#xff08;python310&#xff09;和3.9&#xff08;python39&#xff09;两个版本 2 切换python版本 conda activate python3103 下载python # 下载pyt…

【玩转pandas系列】巧妙处理某瓣电影top250空数据

向阳花花花花 - 个人主页 迄今所有人生都大写着失败&#xff0c;但并不妨碍我继续向前 Python 数据分析专栏 正在火热更新中 &#x1f525; 文章目录 前言一、处理某瓣电影top250空数据二、对于空值&#xff0c;有没有别的处理办法&#xff1f;三、上述案例总结3.1 查看数据信…

个人博客系统 -- 博客列表页删除Markdown字符

之前的博客系统的列表页会有在markdown编辑器中的特殊字符,比如标题的字符#之类的,在列表页进行展示的时候,我们需要将这些字符进行筛选. 对这些字符进行筛选,我们可以通过排设计正则表达式进行筛选,也可以使用组件的方式进行筛选.下面我来总结一下,使用组件的方式进行筛选. 这…

Codeforces Round 886 (Div. 4)F题解

文章目录 [We Were Both Children](https://codeforces.com/contest/1850/problem/F)问题建模问题分析1.分析到达的点与跳跃距离的关系2.方法1倍数法累计每个点所能达到的青蛙数代码 方法2试除法累计每个点能到达的青蛙数代码 We Were Both Children 问题建模 给定n个青蛙每次…

Mac平台首选原生轻量级的嵌入式数据库引擎:Native SQLite Manager for Mac

亲爱的读者&#xff0c;如果你是一位在Mac平台上使用SQLite数据库的开发者或数据分析师&#xff0c;那么本文将为你介绍一款非常实用的工具——原生SQLite管理器。 SQLite是一种轻量级的嵌入式数据库引擎&#xff0c;被广泛应用于各种应用程序和系统中。它具有高效、可靠和易于…

ThirdAI 的私有和可个性化神经数据库:增强检索增强生成(第 3/3 部分)

这是我们关于使用检索增强生成构建 AI 代理的系列的最后一章 &#xff08;3/3&#xff09;。在第 1/3 部分中&#xff0c;我们讨论了断开连接的嵌入和基于矢量的检索管道的局限性。在第 2/3 部分中&#xff0c;我们介绍了神经数据库&#xff0c;它消除了存储和操作繁重且昂贵的…

GitHub上怎么寻找项目?

前言 下面由我精心整理的关于github项目资源搜索的一些方法&#xff0c;这些方法可以帮助你更快更精确的搜寻到你需要的符合你要求的项目。 写文章不易&#xff0c;如果这一篇问文章对你有帮助&#xff0c;求点赞求收藏~ 好&#xff0c;下面我们直接进入正题——> 首先我…

接口测试必备的,2种常⽤的JSON解析⽅法

JSON简介 一、JSON是什么&#xff1f; JSON: JavaScript Object Notation JS对象简谱&#xff0c;是一种轻量级的数据交换模式。 二、JSON语法&#xff1a; 对象中通过键值对 (key: value)的形式来表示对象的属性 注意&#xff1a;value即可以表示属性变量&#xff0c;又可…

【数据结构(C++版)】哈希表(散列表)

目录 1. 散列表的概念 2. 散列函数的构造方法 2.1 直接定址法 2.2 除留余数法 2.3 数字分析法 2.4 平方取中法 3. 处理冲突的方法 3.1 开放定址法 3.1.1 线性探测法 3.1.2 平方探测法 3.1.3 双散列法 3.1.4 伪随机序列法 3.2 拉链法&#xff08;链接法&#xff09…

数据结构---并查集

目录标题 为什么会有并查集并查集的原理模拟实现并查集准备工作构造函数FindRootUnionSetCount 并查集实战题目一&#xff1a;省份数量题目解析题目二&#xff1a;等式方程的可满足性题目解析 为什么会有并查集 这里可以使用生活中的一个例子来带着大家理解并查集&#xff0c;…