excel管理接口测试用例

闲话休扯,上需求:自动读取、执行excel里面的接口测试用例,测试完成后,返回错误结果并发送邮件通知。

分析:

1、设计excel表格
2、读取excel表格
3、拼接url,发送请求
4、汇总错误结果、发送邮件

开始实现:

1、设计excel接口用例表格,大概长这样:  

依次为:用例编号、接口名称、接口主host、接口路由、请求方式、请求参数类型、请求参数、断言

这次案例中用到的接口,其实就是如何优雅的进行接口测试使用的快递查询接口,一时半会儿没找到好用的,之前写的也找不到了,只好作罢。2、读取excel表格,获取每个用例的数据


import xlrd
import sys

def test_cases_in_excel(test_case_file):
    test_case_file = os.path.join(os.getcwd(), test_case_file)
    # 获取测试用例全路径 如:E:\Python\httprunner\interface_excel\testcases.xlsx
    print(test_case_file)
    if not os.path.exists(test_case_file):
        print("测试用例excel文件存在或路径有误!")
        # 找不到指定测试文件,就退出程序 os.system("exit")是用来退出cmd的
        sys.exit()
    # 读取excel文件
    test_case = xlrd.open_workbook(test_case_file)
    # 获取第一个sheet,下标从0开始
    table = test_case.sheet_by_index(0)
    # 记录错误用例
    error_cases = []
    # 一张表格读取下来,其实就像个二维数组,无非是读取第一行的第几列的值,由于下标是从0开始,第一行是标题,所以从第二行开始读取数据
    for i in range(1, table.nrows):
        num = str(int(table.cell(i, 0).value)).replace("\n", "").replace("\r", "")
        api_name = table.cell(i, 1).value.replace("\n", "").replace("\r", "")
        api_host = table.cell(i, 2).value.replace("\n", "").replace("\r", "")
        request_url = table.cell(i, 3).value.replace("\n", "").replace("\r", "")
        method = table.cell(i, 4).value.replace("\n", "").replace("\r", "")
        request_data_type = table.cell(i, 5).value.replace("\n", "").replace("\r", "")
        request_data = table.cell(i, 6).value.replace("\n", "").replace("\r", "")
        check_point = table.cell(i, 7).value.replace("\n", "").replace("\r", "")
        print(num, api_name, api_host, request_url, method, request_data_type, request_data, check_point)
        try:
            # 调用接口请求方法,后面会讲到
            status, resp = interface_test(num, api_name, api_host, request_url, method, 
                                            request_data_type, request_data, check_point)
            if status != 200 or check_point not in resp:
                # append只接收一个参数,所以要讲四个参数括在一起,当一个参数来传递
                # 请求失败,则向error_cases中增加一条记录
                error_cases.append((num + " " + api_name, str(status), api_host + request_url))
        except Exception as e:
            print(e)
            print("第{}个接口请求失败,请检查接口是否异常。".format(num))
            # 访问异常,也向error_cases中增加一条记录
            error_cases.append((num + " " + api_name, "请求失败", api_host + request_url))
    return error_cases

3、拼接url,判断请求方式(get/post),发送请求传入读取用例的各种参数,先判断请求方式,再拼接参数通过requests库来发送请求


import requests

def interface_test(num, api_name, api_host, request_url, method, 
                    request_data_type, request_data, check_point):
    # 构造请求headers
    headers = {'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8',
                      'X-Requested-With' : 'XMLHttpRequest',
                      'Connection' : 'keep-alive',
                      'Referer' : 'http://' + api_host,
                      'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36'
                }
    # 判断请求方式,如果是GET,则调用get请求,POST调post请求,都不是,则抛出异常
    if method == "GET":
        r = requests.get(url=api_host+request_url, params=json.loads(request_data), headers=headers)
        # 获取请求状态码
        status = r.status_code
        # 获取返回值
        resp = r.text
        if status == 200:
            # 断言,判断设置的断言值,是否在返回值里面
            if check_point in str(r.text):
                print("第{}条用例'{}'执行成功,状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
                return status, resp
            else:
                print("第{}条用例'{}'执行失败!!!状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
                return status, resp
        else:
            print("第{}条用例'{}'执行失败!!!状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
            return status, resp
    elif method == "POST":
        # 跟GET里面差不多,就不一一注释了
        r = requests.post(url=api_host+request_url, params=json.loads(request_data), headers=headers)
        status = r.status_code
        resp = r.text
        if status == 200:
            if check_point in str(r.text):
                print("第{}条用例'{}'执行成功,状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
                return status, resp
            else:
                print("第{}条用例'{}'执行失败!!!状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
                return status, resp
        else:
            print("第{}条用例'{}'执行失败!!!状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
            return status, resp
    else:
        print("第{}条用例'{}'请求方式有误!!!请确认字段【Method】值是否正确,正确值为大写的GET或POST。".format(num, api_name))
        return 400, "请求方式有误"

4、汇总错误结果、发送邮件4.1、汇总错误结果,保存为简易html报告,并通过邮件发送到指定接收人


def main():
    # 执行所以测试用例,获取错误的用例
    error_cases = test_cases_in_excel("testcases.xlsx")
    # 如果有错误接口,则开始构造html报告
    if len(error_cases) > 0:
        html = '<html><body>接口自动化扫描,共有 ' + str(len(error_cases)) + ' 个异常接口,列表如下:' + '</p><table><tr><th style="width:100px;text-align:left">接口</th><th style="width:50px;text-align:left">状态</th><th style="width:200px;text-align:left">接口地址</th></tr>'
        for test in error_cases:
            html = html + '<tr><td style="text-align:left">' + test[0] + '</td><td style="text-align:left">' + test[1] + '</td><td style="text-align:left">' + test[2] + '</td></tr>'
        send_email(html)
        print(html)
        with open ("report.html", "w") as f:
            f.write(html)
    else:
        print("本次测试,所有用例全部通过")
        send_email("本次测试,所有用例全部通过")

4.2、构造邮件函数

先读取配置文件,新建config.yml配置文件,内容如下:

sender为发送邮件的邮箱,receiver为接收者着的邮箱,支持多个,smtpserver邮箱服务,username发送者邮箱少去后缀,password密码 

import yaml

def get_conf():
    with open ("config.yml", "r", encoding='utf-8') as f:
        cfg = f.read()
        dic = yaml.load(cfg)
        sender = dic['email']['sender']
        receiver = dic['email']['receiver']
        smtpserver = dic['email']['smtpserver']
        username = dic['email']['username']
        password = dic['email']['password']
        print(sender, receiver, smtpserver, username, password)
        return sender, receiver, smtpserver, username, password

然后构造发送邮件的函数

import smtplib
import time
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.header import Header

def send_email(text):
    today = time.strftime('%Y.%m.%d',time.localtime(time.time()))
    sender, receiver, smtpserver, username, password = get_conf()
    # subject为邮件主题 text为邮件正文
    subject = "[api_test]接口自动化测试结果通知 {}".format(today)
    msg = MIMEText(text, 'html', 'utf-8')
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = "".join(receiver)
    smtp = smtplib.SMTP()
    smtp.connect(smtpserver)
    smtp.login(username, password)
    smtp.sendmail(sender, receiver, msg.as_string())
    smtp.quit()

以上内容就将需求实现了,由于现在很晚了(懒),上面所以函数就对在一个py文件里面了,来运行下吧 

 

邮件一会儿就收到了

所有代码如下:


#!/usr/bin/env python
#-*- coding:utf-8 -*-

'''
需求:自动读取、执行excel里面的接口测试用例,测试完成后,返回错误结果并发送邮件通知。
一步一步捋清需求:
1、设计excel表格
2、读取excel表格
3、拼接url,发送请求
4、汇总错误结果、发送邮件
'''
import xlrd
import os
import requests
import json
import yaml
import smtplib
import time
import sys
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.header import Header


def test_cases_in_excel(test_case_file):
    test_case_file = os.path.join(os.getcwd(), test_case_file)
    # 获取测试用例全路径 如:E:\Python\httprunner\interface_excel\testcases.xlsx
    print(test_case_file)
    if not os.path.exists(test_case_file):
        print("测试用例excel文件存在或路径有误!")
        # 找不到指定测试文件,就退出程序 os.system("exit")是用来退出cmd的
        sys.exit()
    # 读取excel文件
    test_case = xlrd.open_workbook(test_case_file)
    # 获取第一个sheet,下标从0开始
    table = test_case.sheet_by_index(0)
    # 记录错误用例
    error_cases = []
    # 一张表格读取下来,其实就像个二维数组,无非是读取第一行的第几列的值,由于下标是从0开始,第一行是标题,所以从第二行开始读取数据
    for i in range(1, table.nrows):
        num = str(int(table.cell(i, 0).value)).replace("\n", "").replace("\r", "")
        api_name = table.cell(i, 1).value.replace("\n", "").replace("\r", "")
        api_host = table.cell(i, 2).value.replace("\n", "").replace("\r", "")
        request_url = table.cell(i, 3).value.replace("\n", "").replace("\r", "")
        method = table.cell(i, 4).value.replace("\n", "").replace("\r", "")
        request_data_type = table.cell(i, 5).value.replace("\n", "").replace("\r", "")
        request_data = table.cell(i, 6).value.replace("\n", "").replace("\r", "")
        check_point = table.cell(i, 7).value.replace("\n", "").replace("\r", "")
        print(num, api_name, api_host, request_url, method, request_data_type, request_data, check_point)
        try:
            # 调用接口请求方法,后面会讲到
            status, resp = interface_test(num, api_name, api_host, request_url, method, 
                                            request_data_type, request_data, check_point)
            if status != 200 or check_point not in resp:
                # append只接收一个参数,所以要讲四个参数括在一起,当一个参数来传递
                # 请求失败,则向error_cases中增加一条记录
                error_cases.append((num + " " + api_name, str(status), api_host + request_url))
        except Exception as e:
            print(e)
            print("第{}个接口请求失败,请检查接口是否异常。".format(num))
            # 访问异常,也向error_cases中增加一条记录
            error_cases.append((num + " " + api_name, "请求失败", api_host + request_url))
    return error_cases


def interface_test(num, api_name, api_host, request_url, method, 
                    request_data_type, request_data, check_point):
    # 构造请求headers
    headers = {'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8',
                      'X-Requested-With' : 'XMLHttpRequest',
                      'Connection' : 'keep-alive',
                      'Referer' : 'http://' + api_host,
                      'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36'
                }
    # 判断请求方式,如果是GET,则调用get请求,POST调post请求,都不是,则抛出异常
    if method == "GET":
        r = requests.get(url=api_host+request_url, params=json.loads(request_data), headers=headers)
        # 获取请求状态码
        status = r.status_code
        # 获取返回值
        resp = r.text
        if status == 200:
            # 断言,判断设置的断言值,是否在返回值里面
            if check_point in str(r.text):
                print("第{}条用例'{}'执行成功,状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
                return status, resp
            else:
                print("第{}条用例'{}'执行失败!!!状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
                return status, resp
        else:
            print("第{}条用例'{}'执行失败!!!状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
            return status, resp
    elif method == "POST":
        # 跟GET里面差不多,就不一一注释了
        r = requests.post(url=api_host+request_url, params=json.loads(request_data), headers=headers)
        status = r.status_code
        resp = r.text
        if status == 200:
            if check_point in str(r.text):
                print("第{}条用例'{}'执行成功,状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
                return status, resp
            else:
                print("第{}条用例'{}'执行失败!!!状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
                return status, resp
        else:
            print("第{}条用例'{}'执行失败!!!状态码为{},结果返回值为{}.".format(num, api_name, status, r.text))
            return status, resp
    else:
        print("第{}条用例'{}'请求方式有误!!!请确认字段【Method】值是否正确,正确值为大写的GET或POST。".format(num, api_name))
        return 400, "请求方式有误"


def main():
    # 执行所以测试用例,获取错误的用例
    error_cases = test_cases_in_excel("testcases.xlsx")
    # 如果有错误接口,则开始构造html报告
    if len(error_cases) > 0:
        # html = '<html><body>接口自动化扫描,共有 ' + str(len(error_cases)) + ' 个异常接口,列表如下:' + '</p><table><tr><th style="width:100px;text-align:left">接口</th><th style="width:50px;text-align:left">状态</th><th style="width:200px;text-align:left">接口地址</th><th   style="text-align:left">接口返回值</th></tr>'
        html = '<html><body>接口自动化扫描,共有 ' + str(len(error_cases)) + ' 个异常接口,列表如下:' + '</p><table><tr><th style="width:100px;text-align:left">接口</th><th style="width:50px;text-align:left">状态</th><th style="width:200px;text-align:left">接口地址</th></tr>'
        for test in error_cases:
            # html = html + '<tr><td style="text-align:left">' + test[0] + '</td><td style="text-align:left">' + test[1] + '</td><td style="text-align:left">' + test[2] + '</td><td style="text-align:left">' + test[3] + '</td></tr>'
            html = html + '<tr><td style="text-align:left">' + test[0] + '</td><td style="text-align:left">' + test[1] + '</td><td style="text-align:left">' + test[2] + '</td></tr>'
        send_email(html)
        print(html)
        with open ("report.html", "w") as f:
            f.write(html)
    else:
        print("本次测试,所有用例全部通过")
        send_email("本次测试,所有用例全部通过")


def get_conf():
    with open ("config.yml", "r", encoding='utf-8') as f:
        cfg = f.read()
        dic = yaml.load(cfg)
        # print(type(dic))
        # print(dic)
        sender = dic['email']['sender']
        receiver = dic['email']['receiver']
        smtpserver = dic['email']['smtpserver']
        username = dic['email']['username']
        password = dic['email']['password']
        print(sender, receiver, smtpserver, username, password)
        return sender, receiver, smtpserver, username, password


def send_email(text):
    today = time.strftime('%Y.%m.%d',time.localtime(time.time()))
    sender, receiver, smtpserver, username, password = get_conf()
    # subject为邮件主题 text为邮件正文
    subject = "[api_test]接口自动化测试结果通知 {}".format(today)
    msg = MIMEText(text, 'html', 'utf-8')
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = "".join(receiver)
    smtp = smtplib.SMTP()
    smtp.connect(smtpserver)
    smtp.login(username, password)
    smtp.sendmail(sender, receiver, msg.as_string())
    smtp.quit()


if __name__ == "__main__":
    # send_email("test")
    main()

思考:

需要改进的地方有很多:

1、增加日志:导入logging模块,代码里面的print一通copy即可,自己尝试哈

2、回写excel表格:xlrd既然可以读取excel文档,肯定可以写入的。可以新增一列,每次执行完用例,将结果写进去,自己去尝试哈

3、request data type没有做判断,这里偷懒了,因为只用了一个接口,而且大晚上在赶工,就没有做判断。可以参照判断请求方式(get/post)来写。

4、报告渣:1、可以尝试使用htmlreport库;2、也可以自己尝试使用一些前端框架生成,如bootstrap

5、未做持续集成:什么是持续集成?听起来高大上,说白了就是找个数据库或者其他玩意儿,将用例、执行结果等等,都存储起来。python有很多库,可以连接各种数据库(mysql、mongoDB),读取excel或者其他接口脚本文档,存入数据库;然后请求接口后,再从库里面读取出来。balabala......

6、无界面:没有界面,其实要不要都无所谓,毕竟只要维护一份excel表格即可。如果一定要的话,可以考虑使用django或者flask框架,构造web页面,将用例的导入导出、新增、编辑、发送请求,生成报告等等一系列操作,全部移交到前端。这就需要懂一点前端代码,如果有兴趣,你也可以尝试。

Python接口自动化测试零基础入门到精通(2024最新版)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

短视频IP运营流程架构SOP模板PPT

【干货资料持续更新&#xff0c;以防走丢】 短视频IP运营流程架构SOP模板PPT 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 抖音运营资料合集&#xff08;完整资料包含以下内容&#xff09; 目录 抖音15秒短视频剧本创作公式 在抖音这个短视频平台上&#…

QT报错记录

Ubuntu22.04安装Qt之后启动Qt Creator报错&#xff1a; Fron 6.5.0, xcb-cursor0 or libxcb-cursor0 is needed to load the Qt xcb platforn plugin. Could not load. This application failed to start because no Qt platforn plugin could be initialized. Reinstalling t…

OpenCV-Python(41):背景减除

目标 学习并掌握OpenCV中的背景减除方法 背景说明 在很多基础应用中背景检出都是一个非常重要的步骤。例如&#xff1a;顾客统计&#xff0c;使用一个静态摄像头来记录进入和离开房间的人数&#xff0c;或者是交通摄像头&#xff0c;需要提取交通工具的信息等。在所有的这些例…

QT day6

目录 思维导图 学生管理系统 思维导图 学生管理系统 ui界面 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QSqlDatabase> //数据库管理类 #include <QSqlQuery> //执行sql语句类 #include <QSqlRecord> //数据库记录类 …

【大模型 + 网络安全 】炒作内卷 or 革新升级?

一年前&#xff0c;ChatGPT问世&#xff0c;以强大的信息整合推理和语言对话能力惊艳全球&#xff0c;随后&#xff0c;以大语言模型LLM&#xff08;以下简称“大模型”&#xff09;为代表的AI技术应用全面席卷&#xff0c;赋能千行百业&#xff0c;重构业务流程&#xff0c;加…

Qt点击按钮在其附近弹出一个窗口

效果 FS_PopupWidget.h #ifndef FS_POPUPWIDGET_H #define FS_POPUPWIDGET_H#pragma once#include <QToolButton> #include <QWidgetAction> #include <QPointer>class QMenu;class FS_PopupWidget : public QToolButton {Q_OBJECTpublic:FS_PopupWidget(QW…

Android的setContentView流程

一.Activity里面的mWindow是啥 在ActivityThread的performLaunchActivity方法里面&#xff1a; private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {ActivityInfo aInfo r.activityInfo;if (r.packageInfo null) {r.packageInfo getP…

2024年甘肃省职业院校技能大赛信息安全管理与评估 样题一 模块二

竞赛需要完成三个阶段的任务&#xff0c;分别完成三个模块&#xff0c;总分共计 1000分。三个模块内容和分值分别是&#xff1a; 1.第一阶段&#xff1a;模块一 网络平台搭建与设备安全防护&#xff08;180 分钟&#xff0c;300 分&#xff09;。 2.第二阶段&#xff1a;模块二…

如何分析测试任务及需求(附分析流程)

测试分析 确认测试范围 根据测试项目的不同需求&#xff0c;有大致几类测试项目类型&#xff1a;商户/平台功能测试、支付方式接入测试、架构调整类测试、后台优化测试、性能测试、基本功能自动化测试。 测试项目需要按照文档要求进行测试需求分析&#xff0c;并给出对应的输出…

数据结构学习 jz66 构建乘积数组

关键词&#xff1a;数学 双指针 方法一&#xff1a;这个题目我一开始做不知道不能用除法。我做的&#xff1a;[ 用时: 12 m 12 s ] 用了除法 分类讨论 方法二&#xff1a;后来看了提示&#xff0c;双指针&#xff0c;两边各开始乘。 方法三&#xff1a;然后又看了答案可以节…

几款提高开发效率的Idea 插件

1、ignore 开发代码过程中经常会有一些需要提交到代码仓库的文件&#xff0c;比如java文件生成的.class、.jar 等&#xff0c;如果将编译后的文件都提交到代码库那么代码库会很大&#xff0c;关键是没有必要。 这款插件就可以很方便的解决某类文件或者某个文件夹不需要提交到…

OS进程管理

进程 文章目录 进程概念组成特征状态与转换组织方式链接方式索引方式 进程控制实现进程控制如何实现原语的“原子性” 进程通信(IPC)共享存储基于存储区共享基于数据结构的共享 消息传递直接通信方式间接通信方式 管道通信 线程实现方式用户级线程内核级线程 多线程模式状态与转…

文件销毁的方法与安全操作守则, 淼一护航文件安全最后一公里

文件销毁的目前大概分为三种&#xff0c;分别是&#xff1a; 一、做成纸浆填埋。把需要销毁处理的过期涉密文件放到工业浸泡池里面浸泡&#xff0c;放入自来水和一定比例的化学药物&#xff0c;文件经过5-8个小时的浸泡后变成了纸浆&#xff0c;上面记录的信息也随之被销毁。最…

智慧公厕:城市公共厕所环境卫生管理的智慧引擎

公共厕所是城市重要的环卫基础设施&#xff0c;也是城市建设不可或缺的组成部分。其整洁度、方便性和管理精细化&#xff0c;直接体现了城市管理水平和文明程度。为了满足越来越高的城市管理要求&#xff0c;智慧公厕应运而生。借助物联网技术、传感感知技术、云计算和大数据等…

2023年全球软件开发大会(QCon北京站2023)9月:核心内容与学习收获(附大会核心PPT下载)

随着科技的飞速发展&#xff0c;全球软件开发大会&#xff08;QCon&#xff09;作为行业领先的技术盛会&#xff0c;为世界各地的专业人士提供了交流与学习的平台。本次大会汇集了全球的软件开发者、架构师、项目经理等&#xff0c;共同探讨软件开发的最新趋势、技术与实践。本…

图书管理系统:从数据库设计到前端展示的实战经验分享

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

汽车线束的汽配企业MES管理系统解决方案

随着科技的飞速发展和环保需求的日益提升&#xff0c;新能源汽车在全球范围内崭露头角&#xff0c;成为未来出行的主导力量。在这股浪潮中&#xff0c;中国凭借其强大的研发实力和市场敏锐度&#xff0c;迅速崛起为新能源汽车领域的佼佼者。而作为汽车数字化控制与智能化应用的…

[LitCTF 2023] Web类题目分享

[LitCTF 2023] Web类题目做法及思路解析&#xff08;个人分享&#xff09; 题目平台地址&#xff1a;NSSCTF | 在线CTF平台 一、[LitCTF 2023]我Flag呢&#xff1f; 奇怪&#xff0c;放哪里了&#xff0c;怎么看不见呢&#xff1f;&#xff08;初级难度&#xff09; 1.访问…

软件测试|Python中如何提取列表中索引为奇数的元素

简介 在Python中&#xff0c;我们经常需要从列表中提取特定位置的元素。如果我们想要提取列表中索引为奇数的元素&#xff0c;可以使用一些简单的方法来实现这一目标。本文将介绍如何在Python中提取列表中索引为奇数的元素&#xff0c;并提供示例代码来帮助大家更好地理解这个…

写点东西《使用 Docker 构建本地开发环境:运行带有 PostgreSQL 和 Minio S3 的 Next.js 全栈应用程序》

写点东西《使用 Docker 构建本地开发环境&#xff1a;运行带有 PostgreSQL 和 Minio S3 的 Next.js 全栈应用程序》 [TOC](写点东西《使用 Docker 构建本地开发环境&#xff1a;运行带有 PostgreSQL 和 Minio S3 的 Next.js 全栈应用程序》) [](#introduction) 简介 先决条件 构…