C++项目 -- 负载均衡OJ(三)online_judge

C++项目 – 负载均衡OJ(三)online_judge

文章目录

  • C++项目 -- 负载均衡OJ(三)online_judge
  • 一、基于MVC结构的oj服务设计
    • 1.结构与功能
  • 二、oj_model.hpp
    • 1.建立文件版题库
    • 2.文件版题库的服务模块
    • 3. MySQL版题库
      • 3.1.创建名为oj_client的用户,创建数据库oj,并给oj_client赋权
      • 3.2. 设计表结构
      • 3.3.引入MySQL链接库
      • 3.4.在oj_model中访问连接数据库
  • 三、oj_view.hpp
    • 1.安装与测试ctemplate库
    • 2.view模块编写
  • 四、oj_control.hpp
    • 1.负载均衡模块
    • 2.Control模块
  • 五、oj_server.cc
    • 1.Makefile
    • 2.设置用户请求的服务路由功能
    • 3.构建正式的OJ功能
    • 4.形成正式的oj_server
    • 5.前端界面
    • 6.测试
  • 六、编写顶层makefile


一、基于MVC结构的oj服务设计

1.结构与功能

该模块功能:

  1. 获取首页,用题目列表充当
  2. 编辑区域页面
  3. 提交判题功能(编译并运行)

MVC结构:

  • M: Model,通常是和数据交互的模块,比如,对题库进行增删改查(文件版,MySQL)
  • V: view, 通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的(浏览器)
  • C: control, 控制器,就是我们的核心业务逻辑

二、oj_model.hpp

这是和数据交互的模块,对外提供访问数据的接口;

1.建立文件版题库

题目的信息包括:

  • 题目的编号
  • 题目的标题
  • 题目的难度
  • 题目的描述,题面
  • 时间要求(内部处理)
  • 空间要求(内部处理)

两批文件构成

  • questions.list : 题目列表(不需要题目的内容)
    所有的题目都存放在questions路径下
    在这里插入图片描述
  • 题目的描述(desc.txt),题目的预设置代码(header.cpp), 测试用例代码(tail.cpp)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    测试用例tail.cpp
    为了在实际编译的时候文件中没有#include “header.hpp”,需要在编译服务调用g++的时候,后面加上一个编译选项-D COMPILER_ONLINE:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

这两个内容是通过题目的编号,产生关联的

2.文件版题库的服务模块

文件版model模块

  • 当用户提交代码后,OJ是将用户写好的header.cpp拼接上题号对应的测试用例tail.cpp形成新的源代码,并发送到compile_and_run模块运行,运行结果返回给用户
  • 测试用例中的条件编译不想让编译器编译的时候,保留它,而是裁剪掉(g++ -D COMPILER_ONLINE
  • 根据题目list文件,加载所有的题目信息道内存中;
  • 题目的所有信息由一个结构体类型存储;
  • Model类中,使用哈希表保存题号与题目信息的映射;
    • LoadQuestionList函数用于加载所有的题目信息道内存中,以哈希表的形式;
    • GetAllQuestions用于获取所有的题目信息;
    • GetOneQuestion用于获取指定题目信息;

3. MySQL版题库

3.1.创建名为oj_client的用户,创建数据库oj,并给oj_client赋权

mysql> use mysql
mysql> create user oj_client@'%' identified by 'password';
mysql> create database oj;
mysql> grant all on oj.* to oj_client@'%';

在这里插入图片描述
登录oj_client用户,可以看到oj数据库
在这里插入图片描述

3.2. 设计表结构

  • 使用MySQLWorkbench来进行图形化界面建表:
    创建与服务器MySQL的连接:
    在这里插入图片描述
    连接上,在oj数据库创建oj_questions表:
    在这里插入图片描述
    在这里插入图片描述
create table if not exists `oj_questions` (`number` int primary key auto_increment comment '题目的编号',`title` varchar(128) not null comment '题目的标题',`star` varchar(8) not null comment '题目的难度',`desc` text not null comment '题目的描述',`header` text not null comment '题目预设给用户的代码',`tail` text not null comment '题目的测试用例代码',`cpu_limit` int default 1 comment '题目的cpu运行时间限制',`mem_limit` int default 50000 comment '题目的内存空间限制'
)engine=InnoDB default charset=utf8;
  • 在Workbench中录题:
    在这里插入图片描述
    如果想只执行这一条语句,就选中然后执行;
    点击form editer开始录入:
    在这里插入图片描述
    录制完成后点apply;
    录制成功:
    在这里插入图片描述

3.3.引入MySQL链接库

MySQL版model模块

  • 如果系统中本身就有MySQL连接的库,就不需要再引入了:
    在这里插入图片描述
    如果系统只有动态库文件,没有devel(开发库文件),可以尝试用yum安装:
yum install -y mysql-community-devel

安装好devel(开发库)后,我们只需要用 #include <mysql/mysql.h> 就可引入mysql库。
编译指令为:

g++ -o oj_server oj_server.cc -std=c++11 -L/usr/lib64/mysql/ -lmysqlclient
  • 如果系统中没有,就需要自己安装:
    MySQL官网下载:
    在这里插入图片描述
    导入服务器并解压:
    在这里插入图片描述
    重命名:
    在这里插入图片描述
    在oj_server目录下引入软链接:
    在这里插入图片描述
  • 如果在运行时发现找不到MySQL的库:
    在这里插入图片描述
    在这里插入图片描述
    将库所在的路径写入该配置文件中,这样运行时系统就知道去哪里寻找库了:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.4.在oj_model中访问连接数据库

oj_server是基于MVC实现的,和数据打交道的只有oj_mode模块,只需要更改该部分代码即可

  • QueryMySQL函数用于执行查询sql语句,并将查询结果封装成Question插入到out中;
    • 关于MySQL Connector C中的接口作用,见博客MySQL Connection C中的API介绍
  • GetAllQuestions用于向MySQL发出查询所有题目的指令;
  • GetOneQuestion用于向MySQL发出查询单个题目的指令;

三、oj_view.hpp

这是构建网页的模块;

1.安装与测试ctemplate库

ctemplate库的github仓库
这是谷歌开源的cpp网页渲染库;
在ctemplate中数据是以字典的格式存放的
在这里插入图片描述
待渲染的网页中写入的是数据的key值,渲染之后将key换成对应的value;

测试网页渲染功能:

  • html中待替换的key值需要使用{{key}}
    在这里插入图片描述
  • TemplateDictionary root是建立ctemplate参数目录结构,相当于 unordered_map<string, string> test;
  • root.SetValue向目录中添加你要替换的数据,kv的,相当于test.insert({ket, value});
  • GetTemplate获取待渲染对象,DO_NOT_STRIP是指保持html网页原貌;
  • tpl->Expand开始渲染,替换字典中的kv,返回新的网页结果到out_html;
#include <iostream>
#include <string>
#include <ctemplate/template.h>using namespace std;int main()
{//html网页的地址string html = "./test.html";string html_info = "lmx_xdu";//建立ctemplate参数目录结构//相当于 unordered_map<string, string> test;ctemplate::TemplateDictionary root("test"); //向目录中添加你要替换的数据,kv的//第一个参数是key,第二个参数是value,将html中的key全部替换为value//相当于test.insert({ket, value});root.SetValue("info", html_info);//获取待渲染对象//DO_NOT_STRIP是指保持html网页原貌ctemplate::Template *tpl = ctemplate::Template::GetTemplate(html, ctemplate::DO_NOT_STRIP);//开始渲染,替换字典中的kv,返回新的网页结果到out_htmlstring out_html;tpl->Expand(&out_html, &root);cout << "渲染的带参html是:" << endl;cout << out_html << endl;return 0;
}

源html网页:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><!--渲染参数,会被我们C++代码中的数据替换, info就是上面SetValue("info", html_info)代码中的info,会自动被std::string info_html中的内容替换--><p>{{info}}</p><p>{{info}}</p><p>{{info}}</p><p>{{info}}</p>
</body>
</html>

渲染后的html网页:
在这里插入图片描述

2.view模块编写

view模块代码

View类用于网页的渲染;

  • 待渲染的网页模板在/template_html路径下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>在线OJ题目列表</title>
</head>
<body><table><tr><th>编号</th><th>标题</th><th>难度</th></tr>{{#question_list}}<tr><td>{{number}}</td><td><a href="/question/{{number}}">{{title}}</a></td><td>{{star}}</td></tr>{{/question_list}}</table>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{{number}}.{{title}}</title>
</head>
<body><h4>{{number}}.{{title}}.{{star}}</h4><P>{{desc}}</P><textarea name="code" cols="100" rows="50">{{pre_code}}</textarea></body>
</html>
  • AllExpandHtml函数将读取到的所有题目信息都渲染到网页上;
    • 在创建了root根目录后,再向根目录中添加子目录,用于替换question_list中的内容;
      在这里插入图片描述
      可以将html中{{#question_list}}修饰的所有内容循环渲染;
      在题目的title处加上链接,可以跳转到这道题的做题界面;
  • OneExpandHtml用于单个题目信息的渲染;

四、oj_control.hpp

这是业务的核心逻辑模块;

1.负载均衡模块

负载均衡模块用于帮助Control选取负载最低的编译服务器,所有编译服务器的配置信息都在以下文件中:
在这里插入图片描述
Machine类用于保存每台编译服务器的具体信息,一个编译服务对应一个Machine

  • 包括ip、端口、负载以及每台服务器的锁;
  • 由于mutex禁止拷贝,因此使用指针;

LoadBalance类用于实现负载均衡算法:

  • 类中保存所有服务器的类Machine,记录所有上线和下线的主机,并且有一把锁保证LoadBalance的数据安全;
  • LoadConf函数用于将配置文件中所有的服务器信息全部读取并保存;
  • SmartChoice函数用于根据所有上线服务器的负载信息,选择负载最低的机器;
  • 负载均衡的算法有:1.随机数+hash;2.轮询+hash;
    这里选取轮询+hash,通过遍历的方式,找到所有负载最小的机器;
  • OfflineMachine函数用于将指定的主机离线;
  • OnlineMachine函数用于上线所有已离线的主机,是将所有_offline中的主机全部插入到_online中,并删除_offline中的主机;

2.Control模块

Control模块代码

Control类用于根据Model类中获取的题目数据,来调用View类中的方法构建OJ网页;

  • RecoveryMachine用于将所有的离线主机恢复为上线模式;
  • AllQuestions使用Model模块获取所有的题目信息,再通过View模块将题目信息渲染到网页上;
  • Question根据指定题目构建网页;
  • Judge实现判题功能,步骤如下:
    • 根据题目编号,拿到对应的题目细节;
      in_json进行反序列化,得到题目的id,得到用户提交的源代码,input输入参数;
      重新拼接用户代码+测试用例代码,形成新的代码;
      选择负载最低的主机;规则:一直选择,直到主机可用,否则,就是全部挂掉;
      然后发起http请求,得到结果;
      将结果赋值给out_json;
    • Result中定义了bool类型强转,因此Result可以直接放在if语句里,如果返回值存在就会返回true;
      在这里插入图片描述
    • Post请求:第一个参数是请求,第二个参数是请求的参数,第三个参数是请求的类型;
      cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8")
    • Post请求的返回值是Result对象
      成员res_是Response的指针,Result重载了->,能够直接访问到Response;
      在这里插入图片描述
      Response中的成员有statue状态码,其值等于200才证明这个http请求是成功的;

五、oj_server.cc

1.Makefile

oj_server:oj_server.ccg++ -o $@ $^ -std=c++11 -L/usr/lib64/mysql/ -lpthread -ljsoncpp -lctemplate -lmysqlclient.PHONY:clean
clean:rm -f oj_server

2.设置用户请求的服务路由功能

  • \d+是正则表达式,能够匹配到用户输入的所有数字;
    \d代表数字,+代表一个或多个;
    正则匹配到的内容会存放在Request类中的matchs里面;
    question/100 ->正则匹配
  • R"()",原始字符串,保持字符串内容的原貌,不用做相关的转义;
  • 设置首页为wwwroot,在其中添加html网页(vdcode中!+Tab可以生成网页骨架)
#include <iostream>
#include "../Comm/httplib.h"
#include "../Comm/util.hpp"using namespace httplib;int main()
{//用户请求的服务路由功能Server svr;//获取所有题目列表svr.Get("/all_questions", [](const Request &req, Response &resp){resp.set_content("这是所有题目的列表", "text/plain; charset=utf-8");}); //用户要根据题目编号,获取题目的内容//question/100 ->正则匹配//R"()",原始字符串,保持字符串内容的原貌,不用做相关的转义svr.Get(R"(/question/(\d+))", [](const Request &req, Response &resp){string number = req.matches[1];resp.set_content("这是指定的一道题: " + number, "text/plain; charset=utf-8");});//用户提交代码,使用我们的判题功能(1.每道题的测试用例  2.compile_and_run)svr.Get(R"(/judge/(\d+))", [](const Request &req, Response &resp){string number = req.matches[1];resp.set_content("指定题目的判题: " + number, "text/plain; charset=utf-8");});svr.set_base_dir("./wwwroot");svr.listen("0.0.0.0", 8080);return 0;
}

首页:
在这里插入图片描述
题目列表:
在这里插入图片描述
指定题目:
在这里插入图片描述
判题:
在这里插入图片描述

3.构建正式的OJ功能

  • 通过Control模块获取由所有题目信息构建的网页,形成网页服务
    • set_content中的格式设置为html;
      在这里插入图片描述
  • 通过Control模块获取单个题目信息构建的网页,形成网页服务
    在这里插入图片描述

首页:
在这里插入图片描述
题目列表:
在这里插入图片描述
做题界面:
在这里插入图片描述

4.形成正式的oj_server

oj_server代码

添加Control对象,实现加载题目和判题功能的请求:

  • 通过捕捉信号上线所有主机:
    通过捕捉ctrl + \信号,触发时调用Recovery重新上线所有主机;

使用PostMan进行测试:

  • 创建三个compile_server服务,端口号都是基于配置文件service_machine.conf中的:
    使用PostMan进行Post请求,请求的文本形式为json,代码内容为无法运行的初始代码,返回的内容中有报错信息:
    在这里插入图片描述
    在这里插入图片描述

5.前端界面

wwwroot首页
template_html界面

index.html
在这里插入图片描述

all_questions.html
在这里插入图片描述

one_question.html
这部分包含前后端交互:

  • 提交给Judge功能判题时,需要的in_json内容主要有input和code;
  • submit函数用于获取页面上的题目信息,形成请求url,并通过ajax向后台发起基于http的json请求:
  • show_result函数用于得到结果,解析并显示到 result中
    在这里插入图片描述

6.测试

负载均衡测试
在这里插入图片描述
每次都会选择负载最低的机器运行;

主机离线上线测试:
在这里插入图片描述
所有主机离线后,再次上线,触发ctrl + c信号,就可以上线所有主机;

六、编写顶层makefile

顶层makefile用于项目的编译、清理和发布

  • 语句前面加@是在运行时不显示这条语句;
  • 项目的发布:将生成的可执行程序和需要的库文件、网页文件等全部复制到output路径下;
.PHONY: all
all :
#编译@cd compiler_server;\make;\cd -;\cd online_judge;\make;\cd -;#项目发布
.PHONY : output
output :@mkdir -p output/compiler_server;\mkdir -p output/online_judge;\cp -rf compiler_server/compile_server output/compiler_server;\cp -rf compiler_server/temp output/compiler_server;\cp -rf online_judge/conf output/online_judge;\cp -rf online_judge/questions output/online_judge;\cp -rf online_judge/template_html output/online_judge;\cp -rf online_judge/wwwroot output/online_judge;\cp -rf online_judge/oj_server output/online_judge;#项目清理
.PHONY : clean
clean :@cd compiler_server;\make clean;\cd -;\cd online_judge;\make clean;\cd -;\rm -rf output;

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

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

相关文章

【uniapp】引入uni-ui组件库

&#xff08;1&#xff09;新建项目的时候选择 uni-ui项目 &#xff08;2&#xff09;已经创建好的项目去官网单独安装 跳转单独安装组件 https://uniapp.dcloud.net.cn/component/uniui/quickstart.html#%E9%80%9A%E8%BF%87-uni-modules-%E5%8D%95%E7%8B%AC%E5%AE%89%E8%A3%8…

202462读书笔记|《一世珍藏的诗歌200首》——你曾经羞赧地向我问起, 是谁最早在此留下足印

202462读书笔记|《一世珍藏的诗歌200首》——你曾经羞赧地向我问起&#xff0c; 是谁最早在此留下足印 《一世珍藏的诗歌200首》作者金宏宇&#xff0c;很多美好的诗&#xff0c;有徐志摩&#xff0c;戴望舒&#xff0c;林徽因&#xff0c;舒婷等的诗精选&#xff0c;很值得一读…

动态库和静态库

文章目录 一、 静态库二、动态库 一、 静态库 静态库&#xff08;.a&#xff09;&#xff1a;程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库&#xff0c;因为他已经在你字节写的程序中。 编译静态库 将所有的.h文件拷贝到lib/include中…

2024年腾讯云服务器价格一览表

随着云计算技术的快速发展&#xff0c;越来越多的企业和个人开始选择使用云服务器来满足他们的数据存储和计算需求。腾讯云作为国内领先的云服务提供商&#xff0c;其服务器产品因性能稳定、安全可靠而备受用户青睐。那么&#xff0c;2024年腾讯云服务器的价格情况如何呢&#…

网络运输层之(3)GRE协议

网络运输层之(3)GRE协议 Author: Once Day Date: 2024年4月8日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文档可参考专栏&#xff1a;通信网络技术_Once-Day的…

OpenHarmony多媒体-video_trimmer

简介 videotrimmer是在OpenHarmony环境下&#xff0c;提供视频剪辑能力的三方库。 效果展示&#xff1a; 安装教程 ohpm install ohos/videotrimmerOpenHarmony ohpm环境配置等更多内容&#xff0c;请参考 如何安装OpenHarmony ohpm包 。 使用说明 目前支持MP4格式。 视频…

ansible模块实战-部署rsync服务端

目录 1、根据部署流程所用到的命令找出模块 2.实战部署 2.1 服务部署&#xff1a;yum 安装 2.2 准备好rsync服务的配置文件 &#xff0c;并将配置文件通过copy模块分发给192.168.81.136这台受控主机 2.3 创建虚拟机用户 2.4 创建密码文件和改权限 2.5 模块对应目录&…

《QT实用小工具·二十九》托盘图标控件

1、概述 源码放在文章末尾 托盘图标控件 可设置托盘图标对应所属主窗体。 可设置托盘图标。 可设置提示信息。 自带右键菜单。 下面是demo演示&#xff1a; 项目部分代码如下&#xff1a; #ifndef TRAYICON_H #define TRAYICON_H/*** 托盘图标控件* 1. 可设置托盘图标…

基于SpringBoot+Vue的大学生心理咨询系统(源码+文档+包运行)

一.系统概述 使用旧方法对学生心理咨询评估信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在学生心理咨询评估信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次…

Unity解决:导出安卓apk 安装时报错:应用未安装:软件包似乎无效

Unity2018.4.36 导出安卓apk 安装时报错&#xff1a;应用未安装&#xff1a;软件包似乎无效 解决办法&#xff1a;因为安装到安卓12 需要添加添加过滤规则 在AS工程AndroidManifest.xml 添加过滤规则即可。 android:exported"true"

算法训练营第25天回溯(分割)

回溯算法&#xff08;分割&#xff09; 131.分割回文串 力扣题目链接(opens new window) 题目 给定一个字符串 s&#xff0c;将 s 分割成一些子串&#xff0c;使每个子串都是回文串。 返回 s 所有可能的分割方案。 示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“…

Matlab 将数据写入excel文件

Matlab 将数据写入excel文件 函数&#xff1a;writematrix 功能&#xff1a;将数据写入文件 语法 writematrix(A) writematrix(A,filename) writematrix(___,Name,Value) 说明 writematrix(A) 将同构数组 A 写入以逗号分隔的文本文件。文件名为数组的工作区变量名称&…

IDEA如何配置 Maven 及 Maven 安装过程(详细版)

IDEA如何配置 Maven&#xff08;详细版&#xff09; 一、安装Maven 1、下载Maven 安装包 官网&#xff1a;https://maven.apache.org/ 2、点击Download 》选择 apache-maven-3.8.6-bin.zip 3、下载后解压产生此文件夹 4、文件夹如图所示 二、环境变量配置 1、点击我的电脑-…

爬虫——如何应对具有反爬机制的网站

&#x1f345; 写在前面 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;这里是hyk写算法了吗&#xff0c;一枚致力于学习算法和人工智能领域的小菜鸟。 &#x1f50e;个人主页&#xff1a;主页链接&#xff08;欢迎各位大佬光临指导&#xff09; ⭐️近…

web安全学习笔记(12)

记一下第十六节课的内容。 一、jQuery Ajax 我们要先下载jQuery。 首先我们转移到template目录下&#xff0c;准备把jQuery下载到这下面。 直接wget下来就可以了。 这样我们就下载好了jQuery&#xff0c;下面我们学习如何使用。 jQuery 调用 ajax 方法 格式&#xff1a;$.…

【前端面试3+1】16 TCP与UDP的区别、如何清除浮动、哪些原因造成阻塞页面渲染、【相同的树】

一、TCP与UDP的区别 TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;User Datagram Protocol&#xff09;是两种常用的网络传输协议&#xff0c;它们有以下几点区别&#xff1a; 1、连接性&#xff1a; TCP是面向连接的协议&#xff0c;通信双方在…

视频拍摄知识+AIGC数据预处理

视角 参考链接&#xff1a;https://www.polarpro.com/blogs/polarpro/filmmaking-101-types-of-camera-shots-and-angles Low Angle Shot 低角度拍摄、horizontal Shot 平视、Dutch Angle Shot 荷兰角斜拍、High Angle Shot 高角度拍摄、Bird’s-eye / Aerial Shot 鸟瞰 / 航…

最大公约数和最小公倍数(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//实现最大公约数函数&#xff1b; int max(int x, int y) {//初始化变量值&#xff1b;int judge 1;//运算&#xff1b;judge x %…

【智能算法】CEC2017测试集

目录 1.背景2.CEC2017测试集3.参考文献 1.背景 IEEE 国际进化计算大会&#xff08;IEEE Congress on Evolutionary Computation&#xff0c;IEEE CEC&#xff09;是进化计算领域中规模最大、影响最重要的会议之一。为了公平评估算法的优化性能&#xff0c;该会议在优化竞赛中提…

工作必备!快速了解多微信高效管理工具

在如今社交媒体和移动即时通信的时代&#xff0c;微信已成为人们工作和生活中不可或缺的一部分。而对于那些需要同时管理多个微信账号的用户来说&#xff0c;微信管理工具则是一项绝对必备的利器。 1、多微信同时登录 通过微信管理系统&#xff0c;我们可以在同一个界面内同时…