当前位置: 首页 > news >正文

Hyperf中的其它事项

Hyperf中的其它事项

关于 Hyperf 其它的内容我们就不多说了,毕竟框架这东西用得多了自然也就熟悉了。最重要的是——我的水平还不足以去深入地分析这个框架!

好吧,其它的功能大家可以去官方文档详细了解,毕竟国人自己做的框架,文档和服务支持还是非常方便的。今天,我们就来再简单讲讲其它的一些配置。

Nginx部署

第一个就是 Nginx 部署问题,Nginx 做为现在顶流级别的应用服务器,可以非常方便地实现 HTTP 服务、绑定域名、负载均衡等功能。在传统的 PHP-FPM 时代,我们只需要指定 FastCGI ,也就是那个 9000 端口或者 unixSocket 就可以了。其实也可以看出,fastcgi_pass 这个词本身就是通过什么什么来执行的意思。fastcgi_pass 就是通过 fastcgi 来执行 PHP-FPM 程序从而实现应用程序的代理。

Swoole 本身启动的就是一个服务应用,这种情况最方便的当然就是来一个反向代理搞定啦。

# 至少需要一个 Hyperf 节点,多个配置多行
upstream hyperf {# Hyperf HTTP Server 的 IP 及 端口server 127.0.0.1:9501;server 127.0.0.1:9502;
}
​
server {# 监听端口listen 80; # 绑定的域名,填写您的域名server_name www.testswoole.com;
​location / {# 将客户端的 Host 和 IP 信息一并转发到对应节点  proxy_set_header Host $http_host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;# 转发Cookie,设置 SameSiteproxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";# 执行代理访问真实服务器proxy_pass http://hyperf;}
}

这是官网的例子,配置都比较简单,也没什么多说的,不过需要注意的是,一定要配置 IP 的转发,要不你在程序中获得的 IP 就是 127.0.0.1 那个了。这个只要配置过反向代理的应该都清楚,也不多做解释了哦。

Supervisor部署

除了 Nginx 之外,还推荐一个工具,那就是 Supervisor 。它是一个进程管理工具,本身 PHP-FPM 就可以自动管理进程,同时我们之前也讲过在 Swoole 中如何管理进程。但是,Supervisor 相对来说更加强大一些,可以很方便地启动、监听和重启一个或多个进程。当一个进程意外被 kill 时,它就会自动再把它重启或者拉起新的进程,从而达到进程自动恢复的目的。

# 安装 epel 源,如果此前安装过,此步骤跳过
yum install -y epel-release
yum install -y supervisor  

CentOS 的话直接运行上面两个命令行就可以安装 Supervisor 了。如果你有使用过 宝塔面板 之类的面板工具的话,那就更方便了,里面可以直接选择安装 Supervisor 并且能够图形界面化的管理。

如果你不爱使用面板类的工具的话,那就还是跟着我一起向下配置吧。先来创建一个配置文件。

vim /etc/supervisord.d/hyperf.ini

在这个 .ini 文件中添加下面的内容。

# 新建一个应用并设置一个名称,这里设置为 hyperf
[program:hyperf]
# 设置命令在指定的目录内执行
directory=/home/www/6.框架/hyperf-skeleton/
# 这里为您要管理的项目的启动命令
command=php ./bin/hyperf.php start
# 以哪个用户来运行该进程
user=root
# supervisor 启动时自动该应用
autostart=true
# 进程退出后自动重启进程
autorestart=true
# 进程持续运行多久才认为是启动成功
startsecs=1
# 重试次数
startretries=3
# stderr 日志输出位置
stderr_logfile=/home/www/6.框架/hyperf-skeleton/runtime/stderr.log
# stdout 日志输出位置
stdout_logfile=/home/www/6.框架/hyperf-skeleton/runtime/stdout.log

注意上面路径相关的内容,要修改成你的项目路径哦。然后就可以启动 Supervisor 了。

supervisord -c /etc/supervisord.d/supervisord.conf

这一行的意思是以配置文件启动 Supervisor 主程序,同时之前我们配置过的 .ini 文件中的程序都会运行起来。

[root@localhost supervisord.d]# ps -ef | grep skeleton
root     32162 32161  0 07:25 ?        00:00:00 skeleton.Master
root     32170 32162  0 07:25 ?        00:00:00 skeleton.Manager
root     32172 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.1
root     32173 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.2
root     32174 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.3
root     32175 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.4
root     32176 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.5
root     32177 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.6
root     32178 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.7
root     32179 32170  0 07:25 ?        00:00:00 skeleton.TaskWorker.8
root     32180 32170  0 07:25 ?        00:00:00 skeleton.Worker.0

不对呀,我们程序名字怎么是 skeleton ,而且 gerp php 也看不到内容。没错,同时也不要慌张,进入项目目录,修改一下 .env 中 APP_NAME ,我就修改成了 MNLZ 。接着重启一下 Supervisor 应用服务。

[root@localhost hyperf-skeleton]# supervisorctl restart hyperf
hyperf: stopped
hyperf: started
[root@localhost hyperf-skeleton]# ps -ef | grep MNLZ
root     32213 32161  1 07:29 ?        00:00:00 MNLZ.Master
root     32221 32213  0 07:29 ?        00:00:00 MNLZ.Manager
root     32223 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.1
root     32224 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.2
root     32225 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.3
root     32226 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.4
root     32227 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.5
root     32228 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.6
root     32229 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.7
root     32230 32221  0 07:29 ?        00:00:00 MNLZ.TaskWorker.8
root     32231 32221  0 07:29 ?        00:00:00 MNLZ.Worker.0

现在你可以试试 kill 一下,不管是 TaskWorker 还是 Master ,kill 之后都会重新拉起新的进程。但是注意,如果 Manager 出现问题了,那可就拉不起任何子进程了。毕竟,Manager 是整个 Swoole 中的管理进程。还记得我们之前讲过的进程模式相关的内容吗?如果不记得了,可以回去看看哦 【Swoole系列3.2】Swoole异步进程服务系统【Swoole系列3.2】Swoole 异步进程服务系统 。

最后,为什么要使用 Supervisor 呢?之前其实我们也讲过,Swoole 中的一个异常或者错误就会导致进程被关闭,为了保证有足够的子进程来处理请求,Supervisor 就是非常好的选择,特别是预防 Master 进程的突然中断。下面的一些命令行命令大家也可以了解一下。

# 启动 hyperf 应用
supervisorctl start hyperf
# 重启 hyperf 应用
supervisorctl restart hyperf
# 停止 hyperf 应用
supervisorctl stop hyperf  
# 查看所有被管理项目运行状态
supervisorctl status
# 重新加载配置文件
supervisorctl update
# 重新启动所有程序
supervisorctl reload

Hyperf 核心生命周期

Hyperf 的生命周期其实分两个部分,在官方文档上也就两段说明。我带着大家去翻源码再看一下。

在 Hyperf 中,它没有像 Laravel 一样的 public/index.php 这样的请求入口文件。因为它是需要自己启动服务的,所以它的全部入口都是 bin/hyperf.php 这个命令行文件。

框架生命周期

当我们执行 php bin/hyperf.php start 的时候,实际上是 vendor/hyperf/server/src/Command/StartServer.php 这个文件中的命令被执行了。这一系列操作我们之前在学习 Laravel 命令行时也接触过。

protected function execute(InputInterface $input, OutputInterface $output){$this->checkEnvironment($output);$serverFactory = $this->container->get(ServerFactory::class)->setEventDispatcher($this->container->get(EventDispatcherInterface::class))->setLogger($this->container->get(StdoutLoggerInterface::class));$serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);if (! $serverConfig) {throw new InvalidArgumentException('At least one server should be defined.');}$serverFactory->configure($serverConfig);Coroutine::set(['hook_flags' => swoole_hook_flags()]);$serverFactory->start();return 0;}

在这个对象的 execute() 中,我们就可以非常清楚地看到读取到了配置文件 config/autoload/server.php 中的内容,然后交给一个 $serverFactory 对象去启动。

$serverFactory 的 configure() 方法会根据配置文件信息,返回实际的原生 Swoole 服务对象。

public function configure(array $config)
{$this->config = new ServerConfig($config);$this->getServer()->init($this->config);
}
public function getServer(): ServerInterface
{if (! $this->server instanceof ServerInterface) {$serverName = $this->config->getType();$this->server = new $serverName($this->container,$this->getLogger(),$this->getEventDispatcher());}return $this->server;
}

ServerConfig 对象会根据我们的配置文件生成一个格式化的配置对象。然后在下面的 getServer() 方法中,根据 ServerConfig 对象的 getType() 返回值获得一个指定的 Server 对象。注意,这个 getType() 返回的不是我们配置文件中的那个 type 属性哦。

class ServerConfig implements Arrayable
{// ......public function __construct(array $config = []){// ......$this->setType($config['type'] ?? Server::class)->setMode($config['mode'] ?? 0)->setServers($servers)->setProcesses($config['processes'] ?? [])->setSettings($config['settings'] ?? [])->setCallbacks($config['callbacks'] ?? []);}// ......
}

注意看这里的 setType() 它要拿的是整个 server.php 配置文件中最外层的 type 属性,我们并没有定义这个值,所以返回的就是 vendor/hyperf/server/src/Server.php 这个对象。接下来我们顺着这个对象的 init() 方法向下摸,在 initServers() 方法中发现了一个 makeServer() 方法的调用,感觉离胜利不远了哦。

protected function makeServer(int $type, string $host, int $port, int $mode, int $sockType)
{switch ($type) {case ServerInterface::SERVER_HTTP:return new SwooleHttpServer($host, $port, $mode, $sockType);case ServerInterface::SERVER_WEBSOCKET:return new SwooleWebSocketServer($host, $port, $mode, $sockType);case ServerInterface::SERVER_BASE:return new SwooleServer($host, $port, $mode, $sockType);}throw new RuntimeException('Server type is invalid.');
}

我擦,不对呀,SwooleHttpServer 又是什么鬼,再进去看看,我们就选第一个 SwooleHttpServer 。点过去之后总算真象大白了,SwooleHttpServer 是一个命名空间别名,真实的就是 Swoole 下面的各种服务器。

// vendor/swoole/ide-helper/src/swoole/Swoole/Http/Server.php
class Server extends \Swoole\Server
{
}

这一条线大家摸清楚了吧,这就是我们说的第一个生命周期,也就是整个框架运行起 Swoole 应用服务的生命周期。

请求与协程生命周期

另一个就是请求与协程的生命周期,这里我就搬官方原话了,其实这部分内容我们之前讲过。

Swoole 在处理每个连接时,会默认创建一个协程去处理,主要体现在 onRequest、onReceive、onConnect 事件,所以可以理解为每个请求都是一个协程,由于创建协程也是个常规操作,所以一个请求协程里面可能会包含很多个协程,同一个进程内协程之间是内存共享的,但调度顺序是非顺序的,且协程间本质上是相互独立的没有父子关系,所以对每个协程的状态处理都需要通过 协程上下文 来管理。

总结

到这里,我们整个 Hyperf 框架的学习就结束了,同时,整个 Swoole 系列也就告一段落了。这里先不煽情了,毕竟后面还有一篇大总结,大家有收获吗?不管怎么样,一步一步跟着我走下来,相信多少都会有一点感悟和成长。更重要的,如果有机会,不如尝试在实战中运用一下,这才是真正成长的最佳机会。

测试代码:

https://github.com/zhangyue0503/swoole/tree/main/6.%E6%A1%86%E6%9E%B6/hyperf-skeleton

参考文档:

Hyperf

Hyperf

Hyperf

相关文章:

Hyperf中的其它事项

Hyperf中的其它事项 关于 Hyperf 其它的内容我们就不多说了,毕竟框架这东西用得多了自然也就熟悉了。最重要的是——我的水平还不足以去深入地分析这个框架! 好吧,其它的功能大家可以去官方文档详细了解,毕竟国人自己做的框架&a…...

【技术选型】Elasticsearch 和Solr那个香?

我们为什么在这里?我存在的目的是什么?我应该运动还是休息并节省能量?早起上班或晚起并整夜工作?我应该将炸薯条和番茄酱或蛋黄酱一起吃吗? 这些都是古老的问题,可能有也可能没有答案。其中一些是非常困难或…...

4面美团测试工程师,因为这个小细节,直接让我前功尽弃.....

说一下我面试别人时候的思路 反过来理解,就是面试时候应该注意哪些东西;用加粗部分标注了 一般面试分为这么几个部分: 一、自我介绍 这部分一般人喜欢讲很多,其实没必要。大约5分钟内说清楚自己的职业经历,自己的核…...

数据恢复软件EasyRecovery16下载安装步骤教程

EasyRecovery16是一款专业好用的数据恢复软件,软件提供了向导式的操作向导,可以有效地恢复电脑或者移动存储设备中丢失的各种文件,包括删除的文件、格式化丢失的文件和清空回收站的数据!千呼万唤始出来,大家期盼许久的EasyRecover…...

Springboot 自定义缓存配置 CacheManager 及redis集成

目录 前言 集成 maven依赖 CacheManagerConfig配置 redis配置 使用 Springboot 集成使用缓存 Cacheable CacheEvict 前言 现有项目中经常遇到的缓存集成问题,Springboot提供了统一的接口抽象与缓存管理器,可集成多种缓存类型,如 Co…...

JS 中七个改变原数组的方法

目录 一、push 二、pop 三、unshift 四、shift 五、splice 六、sort 七、reverse 一、push 在数组的尾部添加元素,并返回新的长度。 let arr [1] arr.push(2) console.log(arr) // [1, 2] 二、pop 删除数组最后面一个元素、并返回删除的元素。 let arr [1, …...

【笔试强训选择题】Day7.习题(错题)解析

作者简介:大家好,我是未央; 博客首页:未央.303 系列专栏:笔试强训选择题 每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!! 文章目录…...

Vue电商项目--axios二次封装

postman测试接口 刚刚经过postman工具测试,发现接口果然发生了改变。 新的接口为http://gmall-h5-api.atguigu.cn 如果服务器返回的数据code字段200,代表服务器返回数据成功 整个项目,接口前缀都有/api字样 axios二次封装 XmlHttpRequ…...

人生四维度

人生四维度 不是有钱了就成功,你知道;人生的成功不止一种,你也知道。但成功还有哪种?你知道吗? 如果把人生的体验展开,我们可以得到四个维度,高度、深度、宽度和温度。 财富、权力、影响力 构…...

Python 调用 MessageBeep 播放系统音效

Python 调用 MessageBeep 播放 Windows 系统提示声音 Windows API 函数 "MessageBeep" 介绍 "Windows API MessageBeep"是一个用于发出系统提示音效的函数。它可以向用户发出一种预定义的声音,以指示事件的发生或某个条件的满足。例如&#xf…...

废物,我TMD一个985却斗不过专科生(大厂自动化测试2年被裁)

前言 看到标题,可能很多读者朋友恐怕又要骂我了,985这个特殊的字眼也确实异常晃眼,实际上现在985,211也越来越多,它能代表你能够进入到更高的平台,拿到“高级工厂”的入场券,但并不意味着你会成…...

p70 内网安全-域横向内网漫游 Socks 代理隧道技术(NPS、FRP、CFS 三层内网漫游)

数据来源 本文仅用于信息安全学习,请遵守相关法律法规,严禁用于非法途径。若观众因此作出任何危害网络安全的行为,后果自负,与本人无关。 ​ 必要基础知识点: 内外网简单知识内网 1 和内网 2 通信问题正向反向协议通…...

第三十二章 Unity Mecanim动画系统(上)

在上一章节中,我们介绍了Unity的旧版动画系统,本章节来介绍新版的Mecanim动画系统。新版的Mecanim动画系统实际是对旧版动画系统的升级。新版的Mecanim动画系统仍然是建立在动画片段的基础上的,只不过它给我们提供了一个可视化的窗口来编辑动…...

第二章 集合

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目…...

这一篇Databinding应该可以帮助迅速上手吧

Databinding使用篇(迅速上手) 使用前需要在模块级别的build.gradle里面的android闭包里添加: dataBinding{enabled true}接着在layout文件中按下Alt 回车, 将布局转换成data binding layout即可,此时编译就会生成对…...

【PHP在线定制商城网站源码V3.0】开源的DIY在线定制商城系统+在线礼品定制

源码下载:https://download.csdn.net/download/m0_66047725/87637177 PHP在线定制商城网站源码,免费开源、免费下载。本商城基于mycncart开发。安装成功后即可浏览,你可以在后台->安装扩展功能上传安装插件,在代码调整中点击刷…...

cout源码浅析

目录 cout源码浅析 那么对于没有定义在这之中的要怎么办呢? 实际使用 结语 首先来看我从cplusplus中截取的这张图: 注意最下面这一行字。cout其实是ostream的一个标准对象object。而上面则演示了一些继承关系。 好的,理解了之后&#xf…...

发送Ajax get请求详解

发送AJAX get请求&#xff0c;前端代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>ajax get请求</title> </head> <body> <script type"text/java…...

SQL语句

创建及删除数据库和表 CREATE DATABASE 数据库名; CREATE DATABASE school; 创建新的表 CREATE TABLE 表名(字段1 数据类型,字段2 数据类型[,...] [,PRIMARY KEY (主键名)]); #主键一般选择能代表唯一性的字段&#xff0c;不允许取空值(NULL)&#xff0c;值也不允许重复&…...

Mysql 学习(八)单表查询方法二

复杂查询 上一节说了5种访问类型的查询&#xff0c;这一节就来说说关于这些比较复杂的查询 情况一&#xff1a;多个二级索引查询 sql&#xff1a;SELECT * FROM index_value_table WHERE value1 abc AND value2 > 1000;搜索条件&#xff1a; value1 等于 abcvalue2 大于…...

安卓系统下的截屏和录屏

可以抓取手机屏幕画面&#xff08;屏幕截图&#xff09;&#xff0c;也可以录制屏幕画面视频。拍摄屏幕后&#xff0c;可以查看、编辑和分享所拍的图片或视频。 抓取屏幕截图 打开要抓取的屏幕。视手机情况执行下列一个操作&#xff0c;3种方法看你手机有效的&#xff1a; 同…...

行为型模式-中介者模式

中介者模式 概述 一般来说&#xff0c;同事类之间的关系是比较复杂的&#xff0c;多个同事类之间互相关联时&#xff0c;他们之间的关系会呈现为复杂的网状结构&#xff0c;这是一种过度耦合的架构&#xff0c;即不利于类的复用&#xff0c;也不稳定。例如在下左图中&#xf…...

辅助驾驶功能开发-功能规范篇(16)-2-领航辅助系统NAP-功能ODD定义

1.系统定义 智能驾驶系统包含行车场景功能和泊车场景功能,行车场景功能包括安全ADAS功能、基础ADAS功能和高阶ADAS功能三大类,本文档定义高阶ADAS功能中的导航辅助驾驶功能用例。 1.1.高阶ADAS功能列表 功能需求ID 功能分类 功能名称...

PMP/高项 06-项目成本管理

项目成本管理 概念 项目成本管理 项目成本管理又被称为项目造价管理&#xff0c;是有关项目成本和项目价值两个方面的管理&#xff0c;是为保障以最小的成本实现最大的项目价值而开展的项目专项管理工作。 确保在批准的项目预算内完成项目 成本管理内容 规划成本管理 制定项目…...

XXL-JOB中间件【实现分布式任务调度】

目录 1&#xff1a;XXL-JOB介绍 2&#xff1a;搭建XXL-JOB 2.1&#xff1a;调度中心 2.2&#xff1a;执行器 2.3&#xff1a;执行任务 3&#xff1a;分片广播 1&#xff1a;XXL-JOB介绍 XXL-JOB是一个轻量级分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学…...

Vue3+Element Plus环境搭建和一键切换明暗主题的配置

Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。而Element Plus是一款基于Vue3面向设计师和开发者的组件库。 最终效果&#xff1a; 环境搭建 已安装 16.0 或更高版本的 Node.js&#xff0c;终端&#xff1a; npm init vuelatest这一…...

Leetcode326. 3 的幂

Every day a leetcode 题目来源&#xff1a;326. 3 的幂 相似题目&#xff1a;342. 4的幂 解法1&#xff1a;递归 代码&#xff1a; /** lc appleetcode.cn id326 langcpp** [326] 3 的幂*/// lc codestart class Solution { public:bool isPowerOfThree(int n){if (n <…...

【运动规划算法项目实战】如何在栅格地图中实现Dijkstra算法

文章目录 简介一、算法介绍1.1 Dijkstra算法流程1.2 Dijkstra算法伪代码二、代码实现2.1 ROS实现2.2 RVIZ演示三、总结简介 Dijkstra算法是一种用于图中单源最短路径的贪心算法。在计算机科学和网络设计中广泛应用。该算法从起点开始,通过优先选择距离起点最近的未标记节点来…...

【算法】一文彻底搞懂ZAB算法

文章目录 什么是ZAB 算法&#xff1f;深入ZAB算法1. 消息广播两阶段提交ZAB消息广播过程 2. 崩溃恢复选举参数选举流程 ZAB算法需要解决的两大问题1. 已经被处理的消息不能丢2. 被丢弃的消息不能再次出现 最近需要设计一个分布式系统&#xff0c;需要一个中间件来存储共享的信息…...

【软考高级】2022年系统分析师综合知识

1.( )是从系统的应用领域而不是从系统用户的特定需要中得出的&#xff0c;它们可以是新的功能性需求&#xff0c;或者是对已有功能性需求的约束&#xff0c;或者是陈述特定的计算必须遵守的要求。 A.功能性需求 B. 用户需求 C.产品需求 D.领域需求 2.对于安全关键系…...

好女人生活常识网站建设/windows7优化大师官方下载

文章目录参考资料1.介绍2. 泛型和子类继承3. 通配符(Wildcards)4. 泛型方法参考资料 https://www.bilibili.com/video/BV1Kb411W75N?p562 1.介绍 不使用泛型下的用法&#xff1a; List myIntList new ArrayList();// 1 myIntList.add(new Integer(0));// 2 Integer x (Int…...

58同城保定网站建设/企业站seo价格

问题 Image和Label数据成对写入TFRecord文件&#xff0c;按理训练过程中读取的Image和Label也应该是一一对应的&#xff0c;但有的时候发现Image和Label并不能匹配。如&#xff1a; 将以下数据写入TFrecord中&#xff1a; Image 1 —— Label 1 Image 2 —— Label 2 Image …...

泰安网站制作/常见的营销手段

http://edu.csdn.net/course/detail/2798?locationNum13&fps1...

福建省建设工程注册管理中心网站/网站推广排名教程

1.在C程序中调用被C编译器编译后的函数&#xff0c;为什么要加extern "C"&#xff1f; 答案&#xff1a;C语言支持函数重载&#xff0c;C语言不支持函数重载。 函数被C编译后在库中的名字与C语言的不同。 假设某个函数的原型为void foo(int x, int y)。 该函数被C编译…...

wordpress 访客/广州全网推广

目录 一.静态代理 二.动态代理&#xff1a; 一.静态代理 1&#xff09;静态代理角色分析&#xff1a; a&#xff1a;抽象角色&#xff1a;一般使用接口 或者 抽象类来实现。 b&#xff1a;真实角色&#xff1a;被代…...

河南省住房和城乡建设部网站首页/双11各大电商平台销售数据

前言 因为之前电脑安装了office2019&#xff0c;后面需要安装Visio&#xff0c;下载安装时报错30204-44,查看发现之前安装的office版本是即点即用版&#xff0c;可能这两者不兼容。网上搜索教程等&#xff0c;最后发现一个工具&#xff1a;Office Tool Plus&#xff0c;可以方便…...