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

Django+Celery框架自动化定时任务开发

本章介绍使用DjCelery即Django+Celery框架开发定时任务功能,在Autotestplat平台上实现单一接口自动化测试脚本、业务场景接口自动化测试脚本、App自动化测试脚本、Web自动化测试脚本等任务的定时执行、调度、管理等,从而取代Jenkins上的定时执行脚本和发送邮件等功能。**

自动化测试逻辑流程图11.1所示。

11.1 环境搭建

1.安装

步骤1 安装Celery。pyramid_celery-3.0.0,

配置https://pypi.python.org/pypi/pyramid_celery/。

步骤2 安装django-clery。django-celery-3.2.2,

配置https://pypi.python.org/pypi/django- celery。 INSTALLED_APPS= []

加入2:

'djcelery', 运行 Python manage.py migrate

步骤 3 安装celery-with-redis-3.0,

地址为https://pypi.python.org/pypi/celery-with-redis/。

步骤 4 安装django-clery-beat。django-celery-beat-1.1.0,

https://pypi.python.org/pypi/ django_celery_beat。

步骤5 下载Redis-x64-3.2.100,

Redis-x64-3.2.100.zip github.com/MicrosoftAr…

2.配置

步骤1 在Settings.py中增加如下内容。

加入1:

import djcelery

djcelery.setup_loader() #加载djcelery

加入2:

#数据库调度

CELERYBEAT_SCHEDULER ='djcelery.schedulers.DatabaseScheduler'

加入3:

BROKER_URL = 'redis://127.0.0.1:6379/0'

BROKER_TRANSPORT = 'redis'

步骤2 在应用Apitest目录下新建celery.py文件1,加入如下内容。

from future import absolute_import

import os

import django

from celery import Celery

from django.conf import settings

os.environ.setdefault('DJANGO_SETTINGS_MODULE','autotest.settings')

django.setup()

app = Celery('autotest')

app.config_from_object('django.conf:settings')

app.autodiscover_tasks(lambda:settings.INSTALLED_APPS)

步骤3 新建tasks.py文件,加入如下内容。

--coding:utf-8 --

importrequests, time, sys, re

importurllib, zlib#,

importpymysql

importunittest

from traceimport CoverageResults

importjson

fromidlelib.rpc import response_queue

fromapitest.celery import app

from timeimport sleep

@app.task

def hello_world():

print('已运行')

步骤4 启动服务python manage.py runserver。

步骤5 解压缩后,运行CMD,切换到相应目录,输入启动Redis指令redis-server redis. windows.conf,成功后出现如图11.2所示界面。

▲图11.2

步骤6 启动指令python manage.py celery worker -l info。

步骤7 启动指令python manage.py celery beat。

11.2 前端功能实现

1.功能描述

完成实现单一接口测试用例、业务场景接口API测试用例、AppUI测试用例、WebUI测试用例的自动化定时任务。

2.程序清单

在autotest\apitest\templates目录下新建periodic_task.html文件,加入如下内容。

<html>

<head>

{% load bootstrap4 %}

{% bootstrap_css %}

{% bootstrap_javascript %}

<title>产品自动化测试平台</title>

<link rel="stylesheet"type="text/css" href="/static/admin/css/forms.css" />

<script type="text/javascript"src="/admin/jsi18n/"></script>

<script type="text/javascript"src="/static/admin/js/vendor/jquery/jquery.js"></script>

<script type="text/javascript"src="/static/admin/js/jquery.init.js"></script>

<script type="text/javascript"src="/static/admin/js/core.js"></script>

<script type="text/javascript"src="/static/admin/js/admin/RelatedObjectLookups.js"></script>

<script type="text/javascript"src="/static/admin/js/actions.js"></script>

<script type="text/javascript"src="/static/admin/js/urlify.js"></script>

<script type="text/javascript"src="/static/admin/js/prepopulate.js"></script>

<script type="text/javascript"src="/static/admin/js/vendor/xregexp/xregexp.js"></script>

<meta name="viewport"content="user-scalable=no, width=device-width, initial-scale=1.0,maximum-scale=1.0">

<link rel="stylesheet"type="text/css" href="/static/admin/css/responsive.css"/>

<meta name="robots"content="NONE,NOARCHIVE" />

</head>

<body role="document">

<!-- 导航栏-->

<nav class="navbar navbar-expand-smbg-dark navbar-dark fixed-top">

<div>

<ahref="#">&nbsp;</a>

<ul>

</ul>

<ul>

<li><astyle='color:white' href="#"></a></li>

<li><astyle='color:white' href="/logout/"></a></li>

</ul>

</div>

</nav>

<!-- 搜索栏-->

<divstyle="padding-top: 70px;">

<formmethod="get" action="/tasksearch/">

{% csrf_token %}

<input type="search"name="task" placeholder="名称" required>

<button type="submit">搜索</button>

<!-- 增加定时任务-->

<div style="float:right;width:73%">

<select name="PeriodicTask"id="PeriodicTask">

<option value="" selected>----定时任务----</option>

</select>

<a id="change_id_PeriodicTask"data-href-template="/admin/djcelery/periodictask/fk/change/?_to_field=id&amp;_popup=1"title="更改选中的定时任务">

<imgsrc="/static/admin/img/icon-changelink.svg" alt="修改"/>

</a>

<a style='color:light blue' id="add_id_PeriodicTask" href="/admin/djcelery/periodictask/add/?_to_field=id&amp;_popup=1"title="增加另一个测试用例">

<imgsrc="/static/admin/img/icon-addlink.svg" alt="增加"/>增加

</a>

</form>

</div>

<!-- 任务计划列表-->

<divstyle="padding-top: 20px;">

<div>

<table class="table table-striped">

<thead>

<tr>

<th>ID</th><th>任务名称</th><th>任务模块</th><th>时间计划</th><th>修改时间</th><th>开启</th><th>立即</th><th>编辑</th><th>删除</th>

</tr>

</thead>

<tbody>

{% for task in tasks %}{% for periodic inperiodics %}

<tr>

{% if task.interval_id != null andtask.interval_id == periodic.id %}

<td>{{ task.id }}</td>

<td>{{ task.name }}</td>

<td>{{ task.task }}</td>

<td><a style='color:green'>每{{ periodic.period }} {{ periodic.every}}次</a></td>

<td>{{ task.date_changed }}</td>

<td>{{ task.enabled }}</td>

<td>{% if task.id == 1 %}

<a href="../task_apis"target="mainFrame">运行</a>

{% elif task.id == 2 %}

<a href="../task_apitest"target="mainFrame">运行</a>

{% else %}

{% endif %}

</td>

<td><a style='color:light blue'class="related-widget-wrapper-link add-related"id="add_id_Apitest" href="../admin/djcelery/periodictask/{{task.id }}/change/?_to_field=id&_popup=1"><imgsrc="/static/admin/img/icon-changelink.svg"/></a></td>

<td><a style='color:light blue'class="related-widget-wrapper-link add-related" id="add_id_Apitest"href="../admin/djcelery/periodictask/{{ task.id}}/delete/?_to_field=id&_popup=1"><imgsrc="/static/admin/img/icon-deletelink.svg"/></a></td>

{% else %}

{% endif %}

{% for crontab in crontabs %}

{% if task.crontab_id != null and task.crontab_id ==crontab.id and task.interval_id == 1 %}

<td>{{ task.id }}</td>

<td>{{ task.name }}</td>

<td>{{ task.task }}</td>

<td><a style='color:green'>{{crontab.month_of_year }}年{{crontab.day_of_month }}月{{crontab.day_of_week }}日{{crontab.hour }}时{{ crontab.minute}}分</a></td>

<td>{{ task.date_changed }}</td>

<td>{{ task.enabled }}</td>

<td><a href="../task_apis"target="mainFrame">运行</a></td>

<td><a style='color:light blue'class="related-widget-wrapper-link add-related"id="add_id_Apitest" href="../admin/djcelery/periodictask/{{task.id }}/change/?_to_field=id&_popup=1"><imgsrc="/static/admin/img/icon-changelink.svg"/></a></td>

<td><a style='color:light blue'class="related-widget-wrapper-link add-related"id="add_id_Apitest" href="../admin/djcelery/periodictask/{{task.id }}/delete/?_to_field=id&_popup=1"><imgsrc="/static/admin/img/icon-deletelink.svg"/></a></td>

{% else %}

{% endif %}

{% endfor %}{% endfor %}{% endfor %}

</tbody>

</table>

</div>

</div>

<span style="position:absolute;right:100px; bottom:30px;"> {# 把翻页功能固定显示在右下角#}

<div style="position:absolute; right:100px; width:100px; ">

<tr><th>总数</th><td>{{ taskcounts }}</td></tr> {# 前端读取定义的变量#}

</div>

<div>

&lt;ulclass="pagination" id="pager"&gt;{#上一页链接开始#}{%if tasks.has_previous %}{#  如果有上一页,则正常显示上一页链接#}&lt;li&gt;&lt;ahref="/periodic_task/?page={{ tasks.previous_page_number }}"&gt;上一页&lt;/a&gt;&lt;/li&gt;    {#  上一页标签 #}{%else %}&lt;li class="previous disabled"&gt;&lt;ahref="#"&gt;上一页&lt;/a&gt;&lt;/li&gt;{# 如果当前不存在上一页,则上一页的链接不可单击#}{%endif %}{# 上一页链接开始#}{%for num in tasks.paginator.page_range %}{% if num == currentPage %}&lt;li&gt;&lt;a href="/periodic_task/?page={{ num }}"&gt;{{ num}}&lt;/a&gt;&lt;/li&gt; {#显示当前页数链接#}{% else %}&lt;liclass="item"&gt;&lt;a href="/periodic_task/?page={{ num}}"&gt;{{ num }}&lt;/a&gt;&lt;/li&gt;{% endif %}{% endfor %}{# 下一页链接开始#}{% if tasks.has_next %} {#  如果有下一页,则正常显示下一页链接#}&lt;liclass="next"&gt;&lt;a href="/periodic_task/?page={{tasks.next_page_number }}"&gt;下一页&lt;/a&gt;&lt;/li&gt;{% else %}&lt;li&gt;&lt;a href="#"&gt;下一页&lt;/a&gt;&lt;/li&gt;{% endif %}{# 下一页链接结束#}&lt;/ul&gt;

</div>

</body>

</html>

功能描述:实现自动化测试任务调度执行,包括单一接口、扫描、流程接口、业务场景、Web搜索、自动化平台测试开发、App登录,CSDN定时任务注册,定时任务执行等功能。

程序清单:在apitest/views.py中加入如下内容。

from .tasks importhello_world

from .tasks importtest_readSQLcase

from djcelery.modelsimport PeriodicTask,CrontabSchedule,IntervalSchedule

任务计划

@login_required

defperiodic_task(request):

username = request.session.get('user', '')task_list = PeriodicTask.objects.all()task_count =PeriodicTask.objects.all().count()  #统计数periodic_list =IntervalSchedule.objects.all()  # 周期任务(如每隔1小时执行1次)crontab_list =CrontabSchedule.objects.all()    # 定时任务(如某年某月某日的某时,每# 天的某时)paginator = Paginator(task_list, 5)  #生成paginator对象,设置每页显示5条记录page = request.GET.get('page',1)  #获取当前的页码数,默认为第1页currentPage=int(page)  #把获取的当前页码数转换成整数类型try:task_list = paginator.page(page)#获取当前页码数的记录列表except PageNotAnInteger:task_list = paginator.page(1)#如果输入的页数不是整数,则显示第1页内容except EmptyPage:task_list =paginator.page(paginator.num_pages)#如果输入的页数不在系统的页数中,# 则显示最后一页内容return render(request,"periodic_task.html", {"user": username,"tasks":task_list,"taskcounts": task_count, "periodics":periodic_list,"crontabs": crontab_list })

搜索功能

@login_required

deftasksearch(request):

username = request.session.get('user', '')# 读取浏览器登录Sessionsearch_name =request.GET.get("task", "")task_list = PeriodicTask.objects.filter(task__icontains=search_name)periodic_list =IntervalSchedule.objects.all()  # 周期任务(如每隔1小时执行1次)crontab_list =CrontabSchedule.objects.all()    # 定时任务(如某年某月某日的某时,每# 天的某时)return render(request,'periodic_task.html',{"user": username,"tasks":task_list,"periodics":periodic_list,"crontabs": crontab_list })

在autotest/urls.py中加入:

path('periodic_task/',views.periodic_task),

path('tasksearch/', views.tasksearch),

在apitest/left.html中加入:

<tr> <td>

                &lt;li&gt;&lt;a  href="../periodic_task"target="mainFrame"&gt;&lt;iclass="glyphicon glyphicon-fire"&gt;&lt;/i&gt;任务计划       &lt;/a&gt;&lt;/li&gt;

&lt;tr&gt;&lt;td&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;

查看前端页面效果,如图11.3所示。

▲图11.3

行动吧,在路上总比一直观望的要好,未来的你肯定会感 谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入扣群: 320231853,里面有各种软件测试+开发资料和技术可以一起交流学习哦。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

相关文章:

Django+Celery框架自动化定时任务开发

本章介绍使用DjCelery即DjangoCelery框架开发定时任务功能&#xff0c;在Autotestplat平台上实现单一接口自动化测试脚本、业务场景接口自动化测试脚本、App自动化测试脚本、Web自动化测试脚本等任务的定时执行、调度、管理等&#xff0c;从而取代Jenkins上的定时执行脚本和发送…...

解决element-plus table组件 fixed=“right“(left)浮动后横向滚动文字穿透的问题

BUG 版本&#xff1a;element-plus 2.6.1 浏览器&#xff1a;360极速浏览器22.1 (Chromium内核) 组件&#xff1a;el-table组件 问题&#xff1a;在头部/尾部浮动加上斑马条纹后&#xff0c;横向滚动存在文字穿透的问题。具体如图&#xff1a; 白色背景行的文字&#xff0c…...

【opencv】示例-distrans.cpp 距离变换

stuff.jpg #include <opencv2/core/utility.hpp> // 包含OpenCV中的核心功能支持库 #include "opencv2/imgproc.hpp" // 包含OpenCV中的图像处理库 #include "opencv2/imgcodecs.hpp" // 包含OpenCV中的图像编解码库 #include "open…...

LVGL V8 代码细读——极致的链表使用

极致的链表的使用 序章碎碎念从redis源码里掏出来的adlist极致的精简的list.hlvgl对链表的巧思lv_ll尾记 序章碎碎念 对于链表&#xff0c;大家应该都不陌生。学过数据结构的&#xff0c;都知道它作为链式储存的基础&#xff0c;重要性不言而喻。 由于本人自动化专业&#xff…...

蓝桥杯第十二届c++大学B组详解

目录 1.空间 2.直线 3.路径 4.卡片 5.货物摆放 6.时间显示 7.砝码称重 8.杨辉三角 9.双向排序 10.括号序列 1.空间 题目解析&#xff1a;1Byte 8bit 1kb 1024B 1MB 1024kb; 先将256MB变成Byte 256 * 1024 * 1024; 再将32位 变成Byte就是 32 / 8 4&#xff1b;…...

Tubi 十岁啦!

Tubi 今年十岁了&#xff0c;这十年不可思议&#xff0c;充满奇迹&#xff01; 从硅谷一个名不见经传的创业小作坊&#xff0c;转变成为四分之一美国电视家庭提供免费流媒体服务的北美领先的平台&#xff1b; 从费尽心力终于签下第一笔内容合作协议&#xff0c;到现在与 450 …...

Qt C++ 实现文件监视源码

以下是使用Qt C++实现文件监视的一个简单示例代码: #include <QCoreApplication> #include <QFileSystemWatcher> #include <QDebug>int main(int argc, char *argv[...

蓝桥杯第十一届c++大学B组详解

目录 1.字符串排序 2.门牌制作 3.即约分数 4.蛇型填数 5.跑步锻炼 6.七段码 7.成绩统计 8.回文日期 9.字串分值和 10.平面切分 1.字符串排序 题目解析&#xff1a;这个题目真没搞懂。有会的大佬教我一下谢谢。 2.门牌制作 题目解析&#xff1a;出过超级多这类题目&am…...

大模型日报2024-04-10

大模型日报 2024-04-10 大模型资讯 微软研究者提出通过可视化思维提升大型语言模型的空间推理能力 摘要: 微软研究者近日提出了一种新方法&#xff0c;旨在通过可视化思维来增强大型语言模型&#xff08;LLMs&#xff09;的空间推理能力。尽管LLMs在语言理解和推理任务方面表现…...

redis修改协议改了,有哪些替代品?

Redis 是一款广泛使用的开源内存数据结构存储&#xff0c;它支持多种数据结构&#xff0c;如字符串、哈希表、列表、集合、有序集合等。然而&#xff0c;由于 Redis 最近更改了其开源许可证&#xff0c;一些用户和开发者可能正在寻找替代品。以下是一些 Redis 的替代品&#xf…...

《QT实用小工具·十六》IP地址输入框控件

1、概述 源码放在文章末尾 该项目为IP地址输入框控件&#xff0c;主要包含如下功能&#xff1a; 可设置IP地址&#xff0c;自动填入框。 可清空IP地址。 支持按下小圆点自动切换。 支持退格键自动切换。 支持IP地址过滤。 可设置背景色、边框颜色、边框圆角角度。 下面…...

windows 系统下 mysql 数据库的下载与安装(包括升级安装)

windows 系统下 mysql 数据库的下载与安装&#xff08;包括升级安装&#xff09; 一、mysql 介绍&#xff1a; MySQL 是一个关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。 MySQL 是最流行的关系型数据库管理系统之一&#xf…...

Redis Stack十部曲之三:理解Redis Stack中的数据类型

文章目录 前言String字符串作为计数器限制 List限制列表阻塞列表自动创建和删除聚合类型键限制 Set限制 Hash限制 Sorted Set范围操作字典操作更新分数 JSON路径限制 BitMapBitfieldProbabilisticHyperLogLogBloom filterCuckoo filtert-digestTop-KCount-min sketchConfigurat…...

OneForAll安装使用

OneForAll简介 OneForAll是一款功能强大的子域收集工具 原项目地址&#xff1a;GitHub - shmilylty/OneForAll: OneForAll是一款功能强大的子域收集工具 gitee项目地址&#xff1a;OneForAll: OneForAll是一款功能强大的子域收集工具 # 安装Python Windows系统安装python参…...

【现代C++】线程支持库

现代C&#xff08;C11及其之后的版本&#xff09;引入了标准的线程支持库&#xff0c;使得多线程编程变得更加简单和可移植。这个库提供了线程管理、互斥量、条件变量和其他同步原语。 1. std::thread - 基本线程 std::thread允许创建执行特定任务的线程。 #include <ios…...

游戏引擎架构01__引擎架构图

根据游戏引擎架构预设的引擎架构来构建运行时引擎架构 ​...

[Java、Android面试]_15_Android为什么使用Binder?

Android为什么使用Binder&#xff1f;用 Linux原有的IPC不行吗&#xff1f; 本人今年参加了很多面试&#xff0c;也有幸拿到了一些大厂的offer&#xff0c;整理了众多面试资料&#xff0c;后续还会分享众多面试资料。 整理成了面试系列&#xff0c;由于时间有限&#xff0c;每天…...

Python+Selenium+Unittest 之Unittest3(TestSuite()和TextTestRunner())

目录 1&#xff1a;addTest() 2、addTests() 3&#xff1a;discover() 上一篇说了Unittest的一个基本的执行顺序&#xff0c;那如果我们想要调整用例的执行先后顺序的话&#xff0c;可以用TestSuite()和TextTestRunner()了&#xff0c;可以这么理解&#xff0c;比如一个班级…...

3D桌面端可视化引擎HOOPS Visualize如何实现3D应用快速开发?

HOOPS Visualize是一个开发平台&#xff0c;可实现高性能、跨平台3D工程应用程序的快速开发。一些主要功能包括&#xff1a; 高性能、以工程为中心的可视化&#xff0c;使用高度优化的OpenGL或DirectX驱动程序来充分利用可用的图形硬件线程安全的C和C#接口&#xff0c;内部利用…...

Vue探索之Vue2.x源码分析(二)

一.Virtual Dom 虚拟DOM是一种轻量级的抽象&#xff0c;它允许我们在Javascript中创建、更新和删除DOM元素。它是React等现代Javascript框架的核心概念之一。 Vue的虚拟dom是一种抽象层的概念&#xff0c;它使得Vue可以高效地更新Dom。虚拟Dom是通过Javascript对象来表示DOM结…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...