Mendelevium
Drug Design
Field Knowledge
Biology
Physics
Machine Learning & AI
Active Learning
Boltz-2
Interpretability
Mol2Image
Representations
Molecular Dynamics
Free Energy Calculation
Modeling Tools
QM
Nano Polymers
Software & Tools
Techniques
about
Home
Contact
Copyright © 2025 Xufan Gao | Academic Research Blog
Home
> Techniques
A Bunch of Biophysics is Loading ...
Techniques
Ubuntu Virtual Memory (Swap) Setup Tutorial: Enhance System Performance
在 Ubuntu 中增加虚拟内存(Swap)教程 在 Ubuntu 系统中增加虚拟内存(即交换空间,Swap)可以有效提升系统在内存不足时的性能。以下是详细的操作步骤: 一、检查当前交换空间 首先,您需要检查当前系统的交换空间情况。打开终端并运行以下命令: sudo swapon --show 如果命令没有输出,说明当前系统没有启用交换空间。如果有输出,则会显示现有交换文件或分区的信息(例如 /swapfile)。 二、创建新的交换文件 方法一:使用 fallocate 命令(推荐) 运行以下命令创建一个新的交换文件: sudo fallocate -l 4G /swapfile_new -l 4G:指定交换文件大小为 4GB。您可以根据需求调整大小,例如使用 8G 表示 8GB。 /swapfile_new:新交换文件的路径。您可以自定义文件名,但需确保后续步骤中路径一致。 方法二:使用 dd 命令(若 fallocate 不可用) 如果 fallocate 命令不可用,可以使用 dd 命令创建交换文件: sudo dd if=/dev/zero of=/swapfile_new bs=1G count=4 bs=1G:每次写入 1GB 数据。 count=4:写入 4 次,生成 4GB 文件。 三、设置交换文件的权限 为了安全起见,设置交换文件的权限,使其仅限 root 用户访问: sudo chmod 600 /swapfile_new 四、格式化交换文件 将创建的文件标记为交换空间: sudo mkswap /swapfile_new 五、启用交换文件 运行以下命令启用新创建的交换文件: sudo swapon /swapfile_new 六、验证交换空间 检查新增的交换空间是否生效: sudo swapon --show 您还可以查看内存使用情况以确认交换空间的变化: free -h 七、配置开机自动挂载 为了使交换文件在系统重启后仍然有效,需要将其添加到 /etc/fstab 文件中: 打开 /etc/fstab 文件进行编辑: sudo nano /etc/fstab 在文件末尾添加以下内容: /swapfile_new none swap sw 0 0 保存并退出编辑器(在 nano 中,按 Ctrl+O 保存,按 Ctrl+X 退出)。 注意事项 调整交换文件大小:根据系统需求和使用场景调整交换文件的大小。一般建议交换文件大小为物理内存的 1-2 倍,但具体大小取决于您的应用场景。 权限管理:确保交换文件的权限设置正确,避免非授权访问。 性能考量:虽然增加交换空间可以缓解内存不足的问题,但过度依赖交换空间可能会降低系统性能,因为磁盘 I/O 速度远低于内存。 通过以上步骤,您可以成功增加 Ubuntu 系统的虚拟内存(Swap),从而提升系统的整体性能和稳定性。 希望这份教程对您有所帮助!如果您在操作过程中遇到任何问题,欢迎随时提问。 Pandoc 生成 PDF 时字体问题解决方案教程 一、问题概述 在使用 Pandoc 将 Markdown 文件生成 PDF 时,如果指定使用 Times New Roman 字体,可能会遇到错误。这是因为 Times New Roman 是 Windows 系统的默认字体,在 Linux 或 macOS 上默认未安装。此外,对于中文支持,也需要确保系统中存在相应的中文字体。 二、检查字体是否安装 在 Linux 系统中 打开终端,运行以下命令查看系统中已安装的字体: fc-list :lang=zh # 查看中文字体 fc-list | grep "Times New Roman" # 查找 Times New Roman 字体 如果没有输出,说明系统中未安装该字体。 在 macOS 系统中 使用 Font Book 应用程序检查字体是否安装。 在 Windows 系统中 打开“字体”文件夹(通常在 C:\Windows\Fonts),查找“Times New Roman”字体。 三、安装所需字体 安装 Times New Roman 字体 对于 Ubuntu/Debian 系统: 运行以下命令安装 Microsoft 核心字体,其中包含 Times New Roman: sudo apt-get update sudo apt-get install ttf-mscorefonts-installer 在安装过程中,可能需要接受许可协议。安装完成后,运行以下命令刷新字体缓存: sudo fc-cache -fv 对于 CentOS/RHEL 系统: 使用以下命令安装字体: sudo yum install curl curl-devel sudo rpm -Uvh http://li.nux.ro/download/fedora/epel/5/i386/epel-release-5-4.noarch.rpm sudo yum install ttf-mscorefonts-installer 对于 macOS 系统: 从官方渠道下载并安装 Microsoft Office for Mac,它会附带安装 Times New Roman 字体。或者,您可以手动下载字体文件并安装。 安装中文支持字体 如果您需要在 PDF 中显示中文,还需要安装中文字体。例如,在 Ubuntu/Debian 系统上,可以安装 texlive-lang-chinese 包: sudo apt install texlive-lang-chinese 该包包含中文支持的宏包(如 ctex),是 Debian 官方维护的包,具有良好的兼容性。 四、配置 Pandoc 使用正确字体 在 Pandoc 命令中指定字体时,确保使用的字体名称与系统中实际存在的字体名称完全匹配。例如: pandoc input.md -o output.pdf --pdf-engine=xelatex --css style.css -V mainfont="Times New Roman" -V CJKmainfont="AR PL UMing CN" mainfont:指定西文字体。 CJKmainfont:指定中文字体。 五、生成 PDF 的 Python 函数示例 以下是一个使用 Pandoc 生成 PDF 的 Python 函数示例,确保路径和字体名称正确: import subprocess import logging from pathlib import Path log = logging.getLogger(__name__) def generate_pdf_with_pandoc(md_path: Path, css_path: Path, output_pdf_path: Path) -> bool: """ 使用 Pandoc 和 XeLaTeX 生成 PDF 文件。 参数: md_path: 输入的 Markdown 文件路径。 css_path: CSS 文件路径(可选)。 output_pdf_path: 输出的 PDF 文件路径。 返回: PDF 生成成功返回 True,失败返回 False。 """ log.info(f"Attempting PDF generation with Pandoc for {md_path}.") pandoc_cmd = [ 'pandoc', str(md_path), '-o', str(output_pdf_path), '--pdf-engine=xelatex', '--css', str(css_path), '-V', 'mainfont=Times New Roman', '-V', 'CJKmainfont=AR PL UMing CN' ] result = subprocess.run(pandoc_cmd, capture_output=True, text=True, encoding='utf-8') if result.returncode != 0: log.error(f"Pandoc failed. Stderr: {result.stderr}") return False log.info(f"Successfully generated PDF with Pandoc at {output_pdf_path}") return True 六、验证和测试 验证字体安装: 运行 fc-list 命令,检查是否列出了 Times New Roman 和中文字体。 确保字体名称与 Pandoc 命令中指定的名称完全一致。 测试 PDF 生成: 使用上述 Python 函数或直接运行 Pandoc 命令生成 PDF。 打开生成的 PDF 文件,检查字体显示是否正确。 七、总结 通过以上步骤,您可以解决 Pandoc 在生成 PDF 时找不到指定字体的问题。确保系统中安装了所需的字体,并在 Pandoc 命令中正确指定字体名称。对于中文支持,安装 texlive-lang-chinese 包是一个推荐的解决方案。希望这份教程能帮助您顺利完成 PDF 生成任务。 如果您在操作过程中遇到任何问题或需要进一步的帮助,欢迎随时提问。
Techniques
· 2025-06-21
Python Project Management: Managing Multiple Python Applications
多Python项目的管理 问题一:“还有没有类似的更容易配置的?” 当然有!如果您觉得 Supervisor 的 .conf 文件语法稍显繁琐,那么有一个在业界同样广受欢迎且以易用性著称的工具非常适合您:PM2。 PM2 (Process Manager 2) 是一个主要为 Node.js 设计,但对 Python 等语言支持也极其出色的进程管理器。 优点: 命令行极其简单直观:使用 pm2 start, pm2 stop, pm2 list, pm2 logs 等命令就能完成所有操作。 配置文件更友好:使用 JSON 或 JS 格式的配置文件(通常叫 ecosystem.config.js),比 INI 格式更灵活,也更贴近现代开发习惯。 内置负载均衡:可以轻松地以“集群模式”启动您的应用,自动利用服务器的所有CPU核心。 内置监控:pm2 monit 命令提供了一个漂亮的终端仪表盘,实时监控 CPU 和内存占用。 如何使用 PM2 管理您的所有服务: 安装 PM2 (通常需要 Node.js 环境): # 首先安装 Node.js 和 npm sudo apt-get install nodejs npm # 然后通过 npm 安装 PM2 sudo npm install pm2 -g 创建 ecosystem.config.js 配置文件: 在您的工作根目录 /root/work/ 下,创建一个名为 ecosystem.config.js 的文件,内容如下。这个文件等同于 Supervisor 的所有 .conf 文件的总和,但更清晰。 // /root/work/ecosystem.config.js module.exports = { /** * Application configuration section * http://pm2.keymetrics.io/docs/usage/application-declaration/ */ apps: [ { name: "literature-analysis", script: "gunicorn", args: "--workers 4 --bind 0.0.0.0:5000 --timeout 120 app:app", cwd: "./literature-analysis/", interpreter: "/opt/miniconda3/envs/download/bin/python", watch: false, autorestart: true, log_date_format: "YYYY-MM-DD HH:mm:ss", error_file: "./log/literature-analysis.err.log", out_file: "./log/literature-analysis.out.log", }, { name: "tiantuan-communication", script: "gunicorn", args: "--workers 4 --bind 0.0.0.0:5001 --timeout 120 app:app", cwd: "./Tiantuan-Communication/", interpreter: "/opt/miniconda3/envs/Tiantuan/bin/python", watch: false, autorestart: true, log_date_format: "YYYY-MM-DD HH:mm:ss", error_file: "./log/tiantuan-communication.err.log", out_file: "./log/tiantuan-communication.out.log", }, { name: "literature-review", script: "gunicorn", args: "--workers 4 --bind 0.0.0.0:5002 --timeout 120 app:app", cwd: "./literature_review/", interpreter: "/opt/miniconda3/envs/Tiantuan/bin/python", watch: false, autorestart: true, log_date_format: "YYYY-MM-DD HH:mm:ss", error_file: "./log/literature-review.err.log", out_file: "./log/literature-review.out.log", }, { name: "ppt-generator", script: "gunicorn", args: "--workers 4 --bind 0.0.0.0:5003 --timeout 120 app:app", cwd: "./ppt_generator/", interpreter: "/opt/miniconda3/envs/Tiantuan/bin/python", watch: false, autorestart: true, log_date_format: "YYYY-MM-DD HH:mm:ss", error_file: "./log/ppt-generator.err.log", out_file: "./log/ppt-generator.out.log", }, { name: "personal-rag", script: "gunicorn", args: "--workers 2 --bind 0.0.0.0:6001 --timeout 300 app:app", // RAG 可能需要更长的超时和更少的 worker cwd: "./Personal-RAG/", interpreter: "/opt/miniconda3/envs/Tiantuan/bin/python", watch: false, autorestart: true, log_date_format: "YYYY-MM-DD HH:mm:ss", error_file: "./log/personal-rag.err.log", out_file: "./log/personal-rag.out.log", // 为这个特定的服务注入环境变量 env: { "HF_HOME": "/root/.cache/huggingface", "HF_HUB_CACHE": "/root/.cache/huggingface/hub" } }, { name: "profile-visualization", script: "gunicorn", args: "--workers 4 --bind 0.0.0.0:6002 --timeout 120 app:app", cwd: "./Profile_Visualization/", interpreter: "/opt/miniconda3/envs/Tiantuan/bin/python", watch: false, autorestart: true, log_date_format: "YYYY-MM-DD HH:mm:ss", error_file: "./log/profile-visualization.err.log", out_file: "./log/profile-visualization.out.log", } ] }; 一键启动所有服务: pm2 start ecosystem.config.js 常用命令: pm2 list # 查看所有服务状态 pm2 logs literature-review # 查看单个服务的日志 pm2 restart all # 重启所有服务 pm2 stop personal-rag # 停止单个服务 结论:对于追求命令行效率和简洁配置的用户来说,PM2 是 Supervisor 一个极佳的、更易上手的替代品。 对于您在 ecosystem.config.js 中设置的 out_file,答案是:不会被覆盖 (overwrite),而是会被追加 (append)。 当您使用 pm2 restart <服务名> 或 pm2 restart all 重启服务时,PM2 的默认行为是将新的标准输出日志(stdout)添加到现有 out_file 文件的末尾。 这样做的好处是,您可以保留服务历次运行的完整日志历史,方便排查问题。 如果您确实希望在每次重启时都得到一个全新的、干净的日志文件,您需要手动操作,例如: 先停止服务:pm2 stop literature-review 手动删除日志文件:rm ./log/literature-review.out.log 再启动服务:pm2 start literature-review 或者,您也可以使用 PM2 的日志管理命令来清空日志:pm2 flush literature-review。 问题二:“宝塔面板是否适合很多Python项目的管理?” 这是一个非常好的问题。答案是:宝塔面板(BT Panel)非常适合快速部署和初级管理,但不一定适合复杂、专业化的项目管理。 宝塔面板是一个带图形化界面(Web UI)的服务器管理软件,它的核心优势在于极大地简化了服务器运维。 优点 (为什么它很吸引人): 图形化操作:您可以通过点击鼠标来部署项目、配置Nginx反向代理、管理数据库、查看负载,几乎无需编写命令行。 一站式体验:集成了网站环境(Nginx/Apache)、数据库(MySQL)、FTP、安全管理等,非常方便。 快速部署:它有一个“Python项目管理器”插件,您只需选择项目目录、启动文件和端口,点击“部署”,它就会在后台帮您配置好 Gunicorn 和进程守护(通常是 systemd)。 缺点 (专业开发者需要考虑的权衡): “黑盒”效应:宝塔为您自动完成了所有配置,但您也因此不清楚底层的 systemd 单元文件或 gunicorn 的具体启动参数是什么。一旦出现问题,排查起来会非常困难,因为您不了解它的内部工作原理。 灵活性和定制性差:您只能使用它提供的选项。如果您需要非常精细的配置(比如特定的 Gunicorn 参数、复杂的环境变量注入),宝塔面板可能无法满足。 增加了不必要的复杂度和安全风险:宝塔本身是一个庞大的软件,它为服务器增加了一个额外的软件层,这本身就可能带来性能开销和潜在的安全漏洞。 可移植性差:您的所有部署都依赖于宝塔面板。如果有一天您想迁移到一台没有安装宝塔的服务器,您几乎需要从头开始重新配置所有服务,因为您无法简单地“导出”宝塔为您生成的底层配置。 适用场景: 对 Linux 命令行不太熟悉,希望通过图形界面管理服务器的用户。 需要快速搭建和部署标准Web应用(如网站、博客)的场景。 个人项目或小型团队,且对配置的精细化控制要求不高的项目。 结论:如果您追求的是极致的简单快捷,并且不介意牺牲一些底层的控制力和透明度,那么宝塔面板是完全可以用来管理您的多个Python项目的。但如果您追求的是专业、透明、可移植和可高度定制的部署方案,那么直接使用 Supervisor、PM2 或 Docker 会是更好的选择。 总结与最终建议 工具 易用性 灵活性/定制性 稳定性/健壮性 可移植性 学习曲线 Supervisor 中等 高 高 中等 中等 PM2 高 高 高 中等 低 宝塔面板 极高 (GUI) 低 中等(依赖面板) 低 极低 Docker Compose 中等 极高 极高 极高 高 给您的建议: 如果您喜欢命令行的直接与高效,但觉得 Supervisor 配置文件略显麻烦,PM2 是您的最佳选择。它在易用性和功能强大之间取得了完美的平衡。 如果您希望完全通过图形界面操作,快速看到结果,不关心底层实现,宝塔面板可以胜任。 如果您着眼于未来的职业发展和项目的长期可维护性,我依然强烈推荐您投入时间学习 Docker Compose,它将为您打开通往现代化应用部署的大门。
Techniques
· 2025-06-20
Complete Guide to Python Multi-Project Management Tools: From Beginner to Expert
Python多项目管理工具完全指南:从入门到选择 当你的服务器上运行着多个Python项目时,如何优雅地管理它们就成了一个重要问题。本文将介绍4种主流的Python项目管理工具,帮助你选择最适合自己的解决方案。 一、为什么需要进程管理工具? 假设你有5个Python Web应用需要在服务器上持续运行: 每个应用都需要在系统重启后自动启动 某个应用崩溃时需要自动重启 需要方便地查看日志和监控状态 想要统一管理所有应用 手动管理这些进程是噩梦般的体验。这时,你就需要专业的进程管理工具。 二、四大主流工具详解 1. Supervisor - 经典稳定的老牌选手 Supervisor 是Python社区的经典进程管理工具,以稳定性著称。 主要特点 ✅ 成熟稳定:经过多年验证,bug极少 ✅ 功能完整:支持进程分组、事件通知、Web界面等 ❌ 配置较繁琐:需要编写INI格式的配置文件 快速上手 # 安装 sudo apt-get install supervisor # Ubuntu/Debian sudo yum install supervisor # CentOS/RHEL # 为每个项目创建配置文件 sudo nano /etc/supervisor/conf.d/myproject.conf 配置文件示例: [program:myproject] command=/path/to/venv/bin/gunicorn --workers 4 --bind 0.0.0.0:5000 app:app directory=/path/to/project user=www-data autostart=true autorestart=true stdout_logfile=/var/log/myproject.log stderr_logfile=/var/log/myproject.error.log 常用命令 sudo supervisorctl status # 查看所有进程状态 sudo supervisorctl restart myproject # 重启特定项目 sudo supervisorctl tail -f myproject # 实时查看日志 2. PM2 - 现代化的全能选手 ⭐推荐 PM2 原本是为Node.js设计的,但对Python支持也非常出色,是目前最易用的进程管理器。 主要特点 ✅ 极其易用:命令简洁直观 ✅ 配置灵活:支持JSON/JS格式配置 ✅ 功能丰富:内置监控、负载均衡 ❌ 需要Node.js环境 快速上手 # 安装(需要先安装Node.js) npm install pm2 -g # 直接启动一个Python应用 pm2 start app.py --interpreter python3 # 或使用配置文件管理多个项目 创建 ecosystem.config.js 配置文件: module.exports = { apps: [ { name: "web-app-1", script: "gunicorn", args: "--workers 4 --bind 0.0.0.0:5000 app:app", cwd: "./project1/", interpreter: "/usr/bin/python3", autorestart: true, log_date_format: "YYYY-MM-DD HH:mm:ss", }, { name: "web-app-2", script: "gunicorn", args: "--workers 2 --bind 0.0.0.0:5001 app:app", cwd: "./project2/", interpreter: "/usr/bin/python3", autorestart: true, env: { "DATABASE_URL": "postgresql://localhost/mydb" } } ] }; 常用命令 pm2 start ecosystem.config.js # 启动所有项目 pm2 list # 查看运行状态 pm2 logs web-app-1 # 查看特定项目日志 pm2 monit # 实时监控(超酷的界面!) 💡 提示:PM2的日志是追加模式,不会在重启时覆盖。使用 pm2 flush 可以清空日志。 3. 宝塔面板 - 图形化的便捷选择 宝塔面板提供了完整的Web界面,让服务器管理变得像操作Windows一样简单。 主要特点 ✅ 图形界面:完全不需要命令行 ✅ 一站式管理:集成了Web服务器、数据库等 ✅ 极易上手:点点鼠标就能部署 ❌ 灵活性差:高度定制化需求难以满足 ❌ “黑盒”操作:不了解底层实现,出问题难排查 使用体验 安装宝塔面板后,通过浏览器访问管理界面 安装”Python项目管理器”插件 点击”添加项目”,选择项目目录和启动文件 设置端口号,点击”启动”即可 ⚠️ 注意:宝塔面板适合新手快速上手,但不适合需要精细控制的专业项目。 4. Docker Compose - 专业团队的首选 Docker Compose 代表了现代化的容器化部署方案。 主要特点 ✅ 完全隔离:每个项目运行在独立容器中 ✅ 环境一致:开发、测试、生产环境完全相同 ✅ 易于迁移:一个命令就能在新服务器上重现整个环境 ❌ 学习曲线陡峭:需要理解容器化概念 配置示例 创建 docker-compose.yml: version: '3.8' services: web-app-1: build: ./project1 ports: - "5000:5000" environment: - DATABASE_URL=postgresql://db/myapp restart: always web-app-2: build: ./project2 ports: - "5001:5001" volumes: - ./data:/app/data restart: always 三、如何选择? 📊 工具对比表 特性 Supervisor PM2 宝塔面板 Docker Compose 易用性 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐ 灵活性 ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐ 稳定性 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ 学习成本 中等 低 极低 高 适合人群 传统运维 大多数开发者 新手 专业团队 🎯 选择建议 如果你是新手,想快速看到效果: 首选 宝塔面板(图形界面) 次选 PM2(命令简单) 如果你是个人开发者,追求效率: 强烈推荐 PM2 ⭐⭐⭐⭐⭐ 如果你在传统企业环境: 选择 Supervisor(运维团队熟悉) 如果你在现代化团队,追求最佳实践: 选择 Docker Compose 四、快速决策指南 问自己以下问题: 你熟悉命令行吗? 不熟悉 → 宝塔面板 熟悉 → 继续下一题 你需要极致的灵活性吗? 需要 → Docker Compose 一般需求 → 继续下一题 你喜欢简洁的命令和现代化的工具吗? 是的 → PM2(最佳选择) 更喜欢传统稳定 → Supervisor 五、总结 对于大多数Python开发者来说,PM2是最平衡的选择:既简单易用,又功能强大。但记住,没有绝对最好的工具,只有最适合你当前需求的工具。 建议的学习路径: 🚀 先用 PM2 快速上手 📚 有时间时学习 Docker,这是未来趋势 🎯 根据实际需求,灵活选择合适的工具 最后提醒:无论选择哪个工具,都要记得定期备份配置文件和项目数据! 希望这篇指南能帮助你找到最适合的Python项目管理方案。如果觉得有帮助,欢迎分享给更多需要的朋友!
Techniques
· 2025-06-20
Molecular Dynamics Clustering Analysis and Heatmap Visualization
clustering First you should prepare a clus_result.dat file containing the frame IDs in each cluster, like this (the first number in each cluster is the centroid): cluster 1: 3722 3946 0 1 4 10 23 33 36 41 45 46 47 51 54 59 61 62 63 66 67 69 76 80 84 85 ...... cluster 2: 489 1886 2 3 5 8 9 11 13 14 16 17 18 19 20 21 22 24 25 27 30 31 32 34 35 37 38 39 40 42 43 44 48 49.... .... This is created in VMD through # http://github.com/anjibabuIITK/CLUSTER-ANALYSIS-USING-VMD-TCL set number 9 ;# number of clusters, others are tagged 'other' set rcutoff 1.5 ;# RMSD cutoff. unit: angstrom set step_size 1 set nframes [molinfo top get numframes] set inf 0 set nf $nframes set totframes [expr $nf - 1 ] set selA [atomselect top "fragment 1 and resid 149 to 156 and backbone"] ;# select the ligand set lists [measure cluster $selA num $number cutoff $rcutoff first $inf last $totframes step $step_size distfunc rmsd weight mass] set file [open "clus_result.dat" w] for {set i 1} {$i <= [llength $lists]} {incr i} { set lst [lindex $lists [expr $i-1]] puts $file [format "cluster %d: %d" $i [llength $lst]] puts $file $lst puts $file "" } close $file # save the coordinates of centroid structures set c01 [lindex [lindex $lists 0] 0] set sel [atomselect top all frame $c01] set real_frame [expr $c01+1] $sel writegro c01_${real_frame}.gro puts [format "write the centroid of 1st cluster: frame %d" $real_frame] set c02 [lindex [lindex $lists 1] 0] set sel [atomselect top all frame $c02] set real_frame [expr $c02+1] $sel writegro c02_${real_frame}.gro puts [format "write the centroid of 2nd cluster: frame %d" $real_frame] Then you plot this with Python: import matplotlib.pyplot as plt import numpy as np import os def read_vmd_clus_result(file): data = [] with open(file, 'r') as f: while f.readline().strip().startswith('cluster'): line = f.readline().strip() data.append([int(fr) for fr in line.split()]) _ = f.readline() # empty return data def get_id_with_time(data): # data: output from read_vmd_clus_result() # return: a list of tuples, (frame_id, cluster_id) # cluster_id starts from 1 id_with_time = [] for i in range(len(data)): cl = data[i] id_with_time += [(fr, i + 1) for fr in cl] id_with_time.sort(key=lambda x: x[0]) return id_with_time font_le = {'family': 'Times New Roman', 'weight': 'demibold', 'size': 16} font_la = {'family': 'Times New Roman', 'fontname': 'Times New Roman', 'weight': 'demibold', 'size': 24} font_tc = {'family': 'Times New Roman', 'fontname': 'Times New Roman', 'weight': 'demibold', 'size': 20} font_ti = {'family': 'Times New Roman', 'fontname': 'Times New Roman', 'weight': 'demibold', 'size': 28} font_hu = {'family': 'Times New Roman', 'fontname': 'Times New Roman', 'weight': 'demibold', 'size': 36} # a framework of the plot def plot_common(xlabel, ylabel, thickness=2, title=None, size=(8,6), xpad=6, ypad=0, title_pad=0, ticks_size=16, tight=False, ax_color='black'): fig, ax = plt.subplots(figsize=size) # fix the xlabel overflow problem for axis in ['top', 'bottom', 'left', 'right']: ax.spines[axis].set_linewidth(thickness) ax.tick_params(width=thickness) ax.tick_params(axis='y', colors=ax_color, labelcolor=ax_color) # plt.xticks(font='Arial', size=16, weight='bold') # plt.yticks(font='Arial', size=16, weight='bold') plt.xticks(font='Times New Roman', size=ticks_size, weight='demibold') plt.yticks(font='Times New Roman', size=ticks_size, weight='demibold') plt.xlabel(xlabel, fontdict=font_la, labelpad=xpad) plt.ylabel(ylabel, fontdict=font_la, labelpad=ypad, color=ax_color) if title is not None: plt.title(title, fontdict=font_ti, pad=title_pad) if tight: plt.tight_layout() return fig, ax def plot_clustering_id_with_time(idxs, nsperframe, biggest=10, path=None, point=False, size=(8,6), ssize=1): # plot the frame_id with cluster_id. Marking the selected centroid frame (point) with a star. # nsperframe: convert frame_id to nanosecond # biggest: biggest cluster_id shown. Other frames are tagged 'other'. plot_common(xlabel='Time (ns)', ylabel='Cluster ID', size=size) biggest = min(biggest, int(max(idxs))) plt.yticks(np.arange(biggest+1), labels=np.arange(biggest).tolist()+['Other']) x = np.arange(len(idxs))*nsperframe y = [min(i, biggest) for i in idxs] plt.scatter(x, y, s=ssize) if point: plt.scatter(point*nsperframe, idxs[point], marker='*', s=50, color='r') print("The number of clusters: {0:d}".format(len(set(idxs)))) print("The biggest cluster lasted for {0:.1f} ns ({1:.1%})".format(np.sum(idxs==1)*nsperframe, np.sum(idxs==1)/len(idxs))) print("The second cluster lasted for {0:.1f} ns ({1:.1%})".format(np.sum(idxs==2)*nsperframe, np.sum(idxs==2)/len(idxs))) print("The unclustered frames (>=no. {2:d}) occupies {0:.1f} ns or {1:.1%}".format(np.sum(idxs>=biggest)*nsperframe, np.sum(idxs>=biggest)/len(idxs), biggest)) print("The centroid of the biggest cluster is at {0:.1f} ns.".format(point*nsperframe)) if path is not None: plt.savefig(os.path.join(path,'cluster.png')) plt.show() path = 'xxxxxxxxx/clus_result.dat' data = read_vmd_clus_result(path) id_with_time = get_id_with_time(data) plot_clustering_id_with_time(np.array(id_with_time)[:, 1], 0.5, path=os.path.dirname(path), point=data[0][0], ssize=1.25) FEP single mutation heatmap Read data: ddG = read_single() # not provided here. customize yourselves. it's just a dictionary of mutation: ddG. # you must follow the format of E1A, E10A, etc. ddG = { 'E1A': -0.783225000000002, 'V2A': 0.379990000000001, 'T3A': -0.7186525, 'E4A': 2.6721, ..... } Then import pandas as pd from matplotlib import pyplot as plt import numpy as np import copy import seaborn as sns # also requires the above plot_common def get_matrix(ddG): columns = sorted(list(set([key[:-1] for key in ddG.keys()])), key=lambda x: int(x[1:])) rows = ['A', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'] df = pd.DataFrame(index=rows, columns=columns) df.iloc[:,:] = np.NAN # process into a matrix for key, value in ddG.items(): col, row = key[:-1], key[-1] df.loc[row, col] = value return df def heatmap_single(df): # Convert all entries to numeric values, replacing non-numeric entries with NaN df = df.apply(pd.to_numeric, errors='coerce') # NOTE: you can adjust the color here. The effect may vary with your ddG data range. cmap = sns.diverging_palette(h_neg=0, h_pos=240, n=15, as_cmap=True) # Mask for NaN values mask = df.isnull() # heatmap. no text means not done. the text color is adaptive to the background color fig, ax = plot_common('Residue Position', 'Mutant', size=(8, 10), xpad=6, ypad=0, ticks_size=14, tight=False) sns.heatmap(df, cmap=cmap, center=0, annot=True, mask=mask, fmt='.2f', cbar_kws={'label': '\u0394\u0394G (kcal/mol)', 'format': '%.2f'}, linewidths=0.5, linecolor='grey', annot_kws={'fontfamily': 'Arial'}) # Update colorbar font size cbar = ax.collections[0].colorbar cbar.ax.tick_params(labelsize=14) cbar.set_label('\u0394\u0394G (kcal/mol)', fontsize=18, weight='demibold', family='Arial') plt.show() df = get_matrix(ddG) heatmap_single(copy.deepcopy(df))
Techniques
· 2025-06-05
PostgreSQL Beginner Tutorial: From Installation to Daily Management
PostgreSQL 新手教程:从安装到日常管理 本教程旨在为初学者提供一个清晰、易懂的 PostgreSQL 上手指南,内容涵盖安装、初始化、用户创建、数据库操作以及常见问题解决。 1. 📦 安装 PostgreSQL 在开始之前,建议先更新您的系统包列表。以下命令适用于基于 RHEL/CentOS 的系统(使用 yum): sudo yum update -y sudo yum install postgresql postgresql-server postgresql-contrib -y postgresql: 包含客户端程序。 postgresql-server: 包含 PostgreSQL 服务器本身。 postgresql-contrib: 包含一些额外的贡献模块和工具。 安装过程中,如果系统提示有关依赖项的更新,请确认并继续安装。完成后,您可能会看到类似以下的输出,表明相关软件包已成功安装或更新: Dependency Updated: postgresql-devel.x86_64 0:9.2.24-9.el7_9 postgresql-docs.x86_64 0:9.2.24-9.el7_9 postgresql-libs.x86_64 0:9.2.24-9.el7_9 postgresql-server.x86_64 0:9.2.24-9.el7_9 Complete! 注意:对于其他 Linux 发行版(如 Debian/Ubuntu),安装命令会有所不同(例如 sudo apt-get install postgresql postgresql-contrib)。 2. 🚀 初始化数据库集群 这是非常关键的一步!PostgreSQL 安装完成后,并不会自动创建一个可用的数据库环境。您需要首先初始化一个“数据库集群”。一个数据库集群是一组数据库的集合,由单个 PostgreSQL 服务器实例管理。 sudo postgresql-setup initdb 此命令会在默认位置(通常是 /var/lib/pgsql/data)创建数据库集群所需的数据目录结构。 它还会生成一些核心的配置文件,例如 postgresql.conf(主配置文件,控制服务器行为)和 pg_hba.conf(客户端认证配置文件,控制哪些用户可以从哪些主机连接以及如何认证)。 如果这个步骤被跳过或者数据目录未正确初始化,后续启动 PostgreSQL 服务将会失败。 3. ▶️ 启动 PostgreSQL 服务 初始化数据库集群后,就可以启动 PostgreSQL 服务了: sudo systemctl start postgresql 为了让 PostgreSQL 在系统重启后自动启动(推荐做法),可以设置开机自启: sudo systemctl enable postgresql 4. 🩺 验证服务状态 启动服务后,检查其运行状态以确保一切正常: systemctl status postgresql.service 如果服务正常运行,您应该在输出中看到 Active: active (running) 字样以及服务的启动时间等信息。 5. 🔑 连接到 PostgreSQL (理解登录机制) 当您首次安装并初始化 PostgreSQL 后,系统会自动创建一个名为 postgres 的操作系统用户和一个同名的 PostgreSQL 超级用户(数据库角色)。 默认的登录方式(Peer Authentication): 在很多默认配置中,PostgreSQL 使用“peer”认证方式进行本地连接。这意味着,如果您的操作系统用户名与您尝试连接的 PostgreSQL 用户名相同,并且您有权访问数据库套接字文件,那么 PostgreSQL 会信任操作系统已经验证了您的身份,从而允许您无需密码直接登录。 这就是为什么我们使用以下命令以 postgres 操作系统用户的身份来连接 postgres 数据库超级用户: sudo -u postgres psql sudo -u postgres: 这部分命令的含义是“以操作系统用户 postgres 的身份执行后续命令”。 psql: 这是 PostgreSQL 的命令行交互式客户端工具。 执行此命令后,您可能会看到类似以下的提示: could not change directory to "/root" psql (9.2.24) Type "help" for help. postgres=# could not change directory to "/root": 这个提示通常可以忽略。它是因为您通过 sudo 切换到 postgres 用户,但 postgres 用户可能没有权限访问您当前所在的目录(例如 /root)。这不影响 psql 的连接和操作。 postgres=#: 这个提示符表明您已成功以 postgres 超级用户身份连接到了 PostgreSQL。postgres= 左边的 postgres 通常表示您当前连接的默认数据库名(也叫 postgres),= 表示您是超级用户 (# 也是超级用户的标志,不同版本或配置可能略有不同)。 关于指定数据库登录: 当您运行 psql 时,如果您不指定要连接的数据库,它会尝试连接一个与您当前操作系统用户名同名的数据库。由于我们使用了 sudo -u postgres psql,它会尝试以数据库用户 postgres 连接名为 postgres 的数据库。这个 postgres 数据库是一个在初始化时自动创建的默认管理用数据库。 简单来说,用户登录数据库时,PostgreSQL 会根据 pg_hba.conf 文件中的规则来验证您的身份。 psql 命令本身可以带有参数来指定用户名、数据库名、主机等,例如: psql -U <数据库用户名> -d <数据库名> -h <主机地址> 如果您不写这些参数,psql 会使用一些默认值(通常是当前操作系统用户名作为数据库用户名和数据库名)。 6. ✨ postgres 超级用户的特权和功能 在 PostgreSQL 中,postgres 用户(或任何被赋予 SUPERUSER 属性的角色)拥有数据库集群内的最高权限。这些特权和功能包括但不限于: 不受限制的访问权限:可以访问集群中的所有数据库和所有对象(表、视图、函数等),无视常规的权限检查。 创建和管理数据库:可以创建新的数据库 (CREATEDB 属性的体现)。 创建和管理角色(用户和组):可以创建、修改、删除其他用户和用户组 (CREATEROLE 属性的体现)。 修改配置参数:可以更改 PostgreSQL 服务器的配置参数(postgresql.conf 中的设置)。 执行特权操作:例如加载C语言函数、执行某些诊断或维护命令、绕过某些安全限制等。 复制权限:可以启动和管理流复制 (REPLICATION 属性的体现)。 绕过行级安全策略 (Row-Level Security)。 正是因为超级用户权限过大,通常不建议在日常应用程序中使用超级用户账户。 最佳实践是为应用程序创建具有完成其任务所需最小权限的普通用户。 7. 👤 创建新用户 在 psql 提示符 (postgres=#) 下,您可以使用 postgres 超级用户的权限来创建新的数据库用户(在 PostgreSQL 中称为“角色”)。 CREATE USER wangpeng WITH CREATEDB PASSWORD 'your_strong_password'; CREATE USER wangpeng: 创建一个名为 wangpeng 的新用户。 WITH CREATEDB: 授予该用户创建新数据库的权限。这是一个常见的权限,但不等同于超级用户。其他可选权限包括 SUPERUSER, CREATEROLE, LOGIN (默认就有), REPLICATION 等。 PASSWORD 'your_strong_password': 为新用户设置一个密码。请务必将其替换为一个强密码。 如果不指定 PASSWORD,用户将无法通过密码认证登录(可能需要其他认证方式)。 8. 📋 查看用户列表 要查看当前数据库集群中存在的所有角色(用户和组)及其属性,可以在 psql 中使用 \du 元命令: \du 输出示例: List of roles Role name | Attributes | Member of -----------+------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication | {} wangpeng | Create DB | {} Role name: 用户名。 Attributes: 该用户拥有的权限属性。 Member of: 该用户所属的用户组(这里为空)。 9. 🚪 退出 psql 当您完成在 psql 中的操作后,可以使用 \q 元命令退出: \q 或者直接按 Ctrl+D。 10. 🔑 普通用户修改自己的密码 一个已经创建的、具有登录权限的普通用户(例如我们前面创建的 wangpeng),可以在连接到数据库后修改自己的密码。 首先,该用户需要能够登录。假设 pg_hba.conf 已配置为允许密码认证(例如 md5),用户 wangpeng 可以这样登录(可能需要先退出 postgres 用户的 psql 会话): psql -U wangpeng -d postgres -h localhost -W (这里连接到 postgres 数据库只是为了执行 ALTER USER 命令,也可以连接到该用户有权限的其他数据库) 然后,在 psql 提示符下(此时应该是 wangpeng=> 或类似),执行: ALTER USER wangpeng WITH PASSWORD 'new_strong_password'; 这样,用户 wangpeng 就成功修改了自己的密码。 注意:普通用户只能修改自己的密码,不能修改其他用户的密码。只有超级用户或具有 CREATEROLE 权限的用户才能修改其他用户的密码或属性。 PostgreSQL 新手教程:创建和管理数据库 1. 创建数据库 一旦您以具有 CREATEDB 权限的用户(例如 postgres 超级用户或我们之前创建的 wangpeng)登录到 psql,就可以创建新的数据库了: -- 假设以 wangpeng 用户登录后执行 CREATE DATABASE mydatabase; 2. 为数据库创建专属普通用户 (推荐) 通常,为每个应用程序或主要功能创建一个专用的数据库用户是一个好习惯,而不是直接使用像 wangpeng 这样可能具有创建数据库权限的用户。 -- 假设仍以 postgres 或 wangpeng (有 CREATEROLE 潜质,或 postgres 执行) 登录 CREATE USER myapp_user WITH PASSWORD 'another_strong_password'; 这里我们创建了一个名为 myapp_user 的用户,它默认只具有 LOGIN 权限。 3. 授予用户对特定数据库的权限 新创建的 myapp_user 默认情况下对我们刚创建的 mydatabase 没有任何操作权限(除了连接,如果认证方式允许)。我们需要明确授予它权限: -- 授予 myapp_user 对 mydatabase 数据库的所有基本权限 GRANT ALL PRIVILEGES ON DATABASE mydatabase TO myapp_user; -- (可选) 如果希望 myapp_user 能够在该数据库中创建表等对象, -- 可能还需要更改该数据库中默认 schema (如 public) 的权限, -- 或者为该用户创建一个专属的 schema 并授予权限。 -- 例如,允许在新数据库的 public schema 中创建对象: -- \c mydatabase -- 首先连接到目标数据库 -- GRANT CREATE ON SCHEMA public TO myapp_user; -- GRANT USAGE ON SCHEMA public TO myapp_user; -- 允许使用 public schema GRANT ALL PRIVILEGES ON DATABASE mydatabase TO myapp_user;: 这授予了用户在 mydatabase 上的连接权限,以及在该数据库内创建 schema 的权限(如果默认权限允许)。但不包括在该数据库内创建表、序列等对象的权限,也不包括对已有对象的访问权限。对具体对象(表、视图、序列、函数等)的权限需要单独授予 (例如 GRANT SELECT, INSERT ON my_table TO myapp_user;)。 4. 使用新用户登录特定数据库 现在,myapp_user 可以尝试登录 mydatabase 了: # 在操作系统的命令行中执行 psql -U myapp_user -d mydatabase -h localhost -W -U myapp_user: 指定要使用的数据库用户名。 -d mydatabase: 指定要连接的数据库名。 -h localhost: 指定数据库服务器的主机地址(如果是本地服务器,此参数有时可以省略,取决于配置)。 -W: 强制提示输入密码。 成功登录后,提示符会变为类似 mydatabase=> (如果 myapp_user 不是超级用户)。 完整流程示例(概览) 以下是一个简化的完整流程,展示了从首次安装后的一系列操作: # (在操作系统命令行中) # 1. 初始化数据库(仅限首次安装 PostgreSQL 后执行一次) sudo postgresql-setup initdb # 2. 启动 PostgreSQL 服务 sudo systemctl start postgresql sudo systemctl enable postgresql # 设置开机自启 # 3. 切换到 postgres 操作系统用户并进入 psql sudo -u postgres psql # --- 以下命令在 psql (postgres=#) 提示符下执行 --- # 4. 创建一个新角色(用户)并赋予创建数据库的权限 CREATE USER db_admin WITH CREATEDB PASSWORD 'admin_password123'; # 5. (可选) db_admin 用户退出并重新登录,或继续以 postgres 操作 -- \q -- 退出 postgres 的 psql 会话 -- (如果退出了,则重新以 db_admin 登录) -- psql -U db_admin -d postgres -W (输入 admin_password123) -- 6. 创建一个新的数据库 (假设由 db_admin 创建) CREATE DATABASE new_application_db; # 7. 为应用程序创建一个权限更受限的用户 CREATE USER app_user WITH PASSWORD 'app_password456'; # 8. 授予 app_user 连接到新数据库的权限 GRANT CONNECT ON DATABASE new_application_db TO app_user; # 9. 切换到新数据库以授予更细致的权限 \c new_application_db # 10. 授予 app_user 在 public schema 中创建对象和使用 schema 的权限 GRANT CREATE ON SCHEMA public TO app_user; GRANT USAGE ON SCHEMA public TO app_user; -- (如果需要,还可以授予对特定表的 SELECT, INSERT, UPDATE, DELETE 权限) -- 例如: GRANT SELECT, INSERT ON TABLE my_table TO app_user; # 11. 退出 psql \q # --- psql 操作结束 --- # (可选) 12. 修改认证方式以允许密码登录 (如果默认是 peer) # 编辑 /var/lib/pgsql/data/pg_hba.conf 文件 # sudo vi /var/lib/pgsql/data/pg_hba.conf # 将相关 'local' 或 'host' 行的认证方法从 'peer' 或 'ident' 改为 'md5' 或 'scram-sha-256' # 例如: # local all all md5 # host all all 127.0.0.1/32 md5 # host all all ::1/128 md5 # (如果修改了 pg_hba.conf) 13. 重启 PostgreSQL 服务使配置生效 # sudo systemctl restart postgresql # 14. 以新创建的 app_user 登录到其专属数据库 # psql -U app_user -d new_application_db -h localhost -W # (输入 app_password456) 常见问题及解决方案 问题:PostgreSQL 服务启动失败 现象:执行 sudo systemctl start postgresql 时提示失败,日志中可能出现 Failed at step EXEC_START pre spawning, Unit entered failed state. 主要原因 数据目录未初始化:/var/lib/pgsql/data (或您系统上的默认数据目录) 不存在或为空。这是最常见的原因。 服务配置错误或权限问题。 解决方法 确保已初始化数据库集群:sudo postgresql-setup initdb 再次尝试启动服务:sudo systemctl start postgresql 检查服务状态和日志获取更详细错误信息:systemctl status postgresql.service 和 journalctl -xeu postgresql.service 问题:无法连接到 PostgreSQL (psql: could not connect to server: No such file or directory) 现象:执行 psql 时提示上述错误。 主要原因 PostgreSQL 服务未启动。 psql 尝试连接的 Unix 域套接字文件不存在(通常因为服务未运行或配置错误)。 解决方法 确保服务已启动:sudo systemctl status postgresql,如果未运行则 sudo systemctl start postgresql。 检查数据目录是否已正确初始化(应包含 PG_VERSION, global, pg_hba.conf 等文件):ls /var/lib/pgsql/data。 问题:认证失败(例如 Peer authentication failed for user "username") 现象:尝试使用特定用户和密码登录时,即使密码正确,也提示认证失败。 主要原因:pg_hba.conf 文件中配置的认证方法不允许您尝试的连接类型或用户使用密码认证。例如,对于本地连接,默认可能配置为 peer 或 ident 认证,而不是 md5 或 scram-sha-256 (密码认证)。 解决方法 修改 pg_hba.conf 文件 sudo vi /var/lib/pgsql/data/pg_hba.conf 找到与您的连接类型( local 表示 Unix 域套接字连接, host 表示 TCP/IP 连接)、数据库、用户匹配的行,将其最后的认证方法修改为 md5 (较旧,但兼容性好) 或 scram-sha-256 (更安全,推荐用于新版本)。 例如,要允许所有本地用户通过密码连接所有数据库: # TYPE DATABASE USER ADDRESS METHOD local all all md5 host all all 127.0.0.1/32 md5 host all all ::1/128 md5 注意:修改 pg_hba.conf 时要小心,不正确的配置可能导致无法连接。 all all 是一种比较宽松的配置,生产环境应根据需要进行限制。 重启 PostgreSQL 服务使配置生效: sudo systemctl restart postgresql 问题:登录后提示 “FATAL: database “username” does not exist” 现象:使用某个用户(例如 newuser)通过 psql -U newuser 登录时,如果该用户对应的同名数据库 (newuser) 不存在,会报此错误。 原因:psql 在未指定 -d <数据库名> 时,默认尝试连接与用户名同名的数据库。 解决方法 在登录时明确指定一个存在的数据库:psql -U newuser -d postgres (连接到默认的 postgres 数据库) 或 psql -U newuser -d mydatabase (连接到您已创建的 mydatabase)。 或者,如果您确实需要一个与该用户同名的数据库,请先以有权限的用户,如 postgres 登录并创建它: -- 以 postgres 用户或其他有权限用户执行 CREATE DATABASE newuser OWNER newuser; -- 创建数据库并将所有权赋给 newuser 附录:常用 PostgreSQL (psql) 命令速查 操作 PostgreSQL (psql) 命令/ 命令 安装 PostgreSQL sudo yum install postgresql postgresql-server postgresql-contrib -y 初始化数据库集群 sudo postgresql-setup initdb 启动服务 sudo systemctl start postgresql 查看服务状态 systemctl status postgresql.service 以postgres用户进入psql sudo -u postgres psql 列出所有数据库 \l 或 \list 连接到特定数据库 \c database_name 或 \connect database_name 列出当前数据库中的表 \dt 查看表结构 \d table_name 列出所有用户(角色) \du 列出所有schema \dn 显示当前连接信息 \conninfo 执行上一条命令 \g 编辑上一条命令 \e 显示帮助信息 \? (psql元命令帮助) 或 help; (SQL命令帮助) 退出 psql \q SQL 命令示例 创建用户 CREATE USER username WITH PASSWORD 'password'; 修改用户密码 ALTER USER username WITH PASSWORD 'new_password'; 创建数据库 CREATE DATABASE dbname; 删除数据库 DROP DATABASE dbname; 授予权限 GRANT ALL PRIVILEGES ON DATABASE dbname TO username; 授予表权限 GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE tablename TO username;
Techniques
· 2025-06-04
Pytest Deep Dive Tutorial: Beginner-Friendly Guide to Python Testing
Pytest 深度入门教程 (初学者友好版) pytest 是一个功能丰富、易于使用且非常流行的 Python 测试框架。与 Python 内置的 unittest 模块相比,pytest 的语法更简洁、更灵活,并且拥有庞大的插件生态系统,能够极大地提升你的测试效率和体验。 想象一下,你是一位大厨,需要确保每一道菜品都符合标准。测试代码就像是品尝和检验菜品的过程,而 pytest 就是一套能帮你高效完成这个过程的顶级厨具和标准化流程。 为什么选择 Pytest? 简单易学,上手快: 你不需要学习复杂的类结构,直接使用标准的 Python 函数来编写测试。 断言(检查条件是否为真)直接使用 Python 内置的 assert 语句,非常直观。 强大的断言功能: pytest 对 assert 语句进行了智能处理。当断言失败时,它会提供非常详细的上下文信息,告诉你哪里出了错,以及相关变量的当前值,极大地帮助调试。 自动发现测试: 你只需要遵循简单的命名约定,pytest 就能自动找到你的测试文件和测试函数,无需手动注册。 丰富的插件生态系统: 拥有大量开箱即用的插件,例如: pytest-cov: 用于生成测试覆盖率报告。 pytest-xdist: 用于并行执行测试,加快测试速度。 pytest-django, pytest-flask: 用于集成主流Web框架。 还有更多用于报告、Mocking 等功能的插件。 优雅的 Fixtures (测试固件/夹具): 这是 pytest 的核心特性之一。Fixtures 提供了一种模块化、可重用的方式来管理测试的准备工作(setup)和清理工作(teardown)。你可以把它们看作是测试函数运行前需要准备好的“原材料”或“环境”。 灵活的参数化测试 (Parametrization): 可以非常方便地为同一个测试函数提供多组不同的输入数据和预期输出,避免编写大量重复的测试逻辑。 清晰的测试报告: 默认提供简洁明了的测试报告,通过插件还可以生成更详细的HTML报告。 安装 Pytest 安装 pytest 非常简单,只需要使用 pip: pip install pytest 安装完成后,你就可以在你的项目中使用 pytest 了。 你的第一个 Pytest 测试 pytest 通过遵循特定的命名约定来自动发现测试: 测试文件: 通常命名为 test_*.py (例如 test_calculator.py) 或 *_test.py (例如 calculator_test.py)。 测试函数: 在测试文件中,以 test_ 开头的函数会被识别为测试函数 (例如 def test_addition():)。 测试类 (可选): 如果你喜欢将相关的测试组织在类中,类名应以 Test 开头 (例如 class TestCalculator:),类中的测试方法同样以 test_ 开头。pytest 不需要测试类继承任何特定的基类。 让我们创建一个名为 test_example.py 的文件,并编写一个简单的测试: # test_example.py # 这是我们要测试的函数 def inc(x): return x + 1 # 这是我们的第一个测试函数 def test_increment_positive_number(): # "Arrange" (准备) - 定义输入和预期输出 input_value = 3 expected_value = 4 # "Act" (执行) - 调用被测试的函数 result = inc(input_value) # "Assert" (断言) - 检查结果是否符合预期 assert result == expected_value def test_increment_zero(): assert inc(0) == 1 def test_increment_negative_number(): assert inc(-5) == -4 代码解释: 我们定义了一个简单的函数 inc(x),它将输入值加1。 test_increment_positive_number 是一个测试函数。它遵循了“Arrange-Act-Assert”(AAA)模式: Arrange: 设置测试所需的初始条件和输入。 Act: 执行被测试的代码。 Assert: 验证结果是否与预期相符。 我们直接使用 assert 关键字来声明我们的期望。如果 inc(3) 的结果不等于 4,assert 语句会抛出 AssertionError,pytest 会捕获这个错误并将测试标记为失败。 运行你的测试 打开你的终端或命令行工具,导航到包含 test_example.py 文件的目录,然后简单地运行以下命令: pytest 发生了什么? pytest 会从当前目录开始,递归地查找所有符合命名约定的测试文件 (test_*.py 或 *_test.py)。 在找到的测试文件中,它会查找所有符合命名约定的测试函数 (test_*) 或测试类 (Test*) 中的测试方法。 然后,它会逐个执行这些测试。 最后,它会汇总结果并显示出来。 预期输出 (默认模式): ============================= test session starts ============================== platform ... -- Python ... plugins: ... collected 3 items test_example.py ... [100%] ============================== 3 passed in X.XXs =============================== collected 3 items: pytest 找到了3个测试函数。 test_example.py ...: 每个点 (.) 代表一个通过的测试。如果所有测试都通过,你会看到一串点。 3 passed in X.XXs: 总结信息,告诉你有多少测试通过以及花费的时间。 如果某个测试失败了,比如我们故意修改 test_increment_zero: # test_example.py # ... (其他代码不变) ... def test_increment_zero(): assert inc(0) == 2 # 故意写错,应该是 1 再次运行 pytest,输出会变成: ============================= test session starts ============================== platform ... -- Python ... plugins: ... collected 3 items test_example.py .F. [100%] =================================== FAILURES =================================== ___________________________ test_increment_zero ____________________________ def test_increment_zero(): > assert inc(0) == 2 # 故意写错,应该是 1 E assert 1 == 2 E + where 1 = inc(0) test_example.py:14: AssertionError =========================== short test summary info ============================ FAILED test_example.py::test_increment_zero - assert 1 == 2 ========================= 1 failed, 2 passed in X.XXs ========================== 注意看 FAILURES 部分,pytest 非常清晰地指出了: 哪个测试函数失败了 (test_increment_zero)。 失败的 assert 语句是什么 (assert inc(0) == 2)。 断言失败时的具体值比较 (assert 1 == 2),并且它还告诉我们 1 是 inc(0) 的结果。这种详细的错误报告是 pytest 的一大优势。 理解 -v (详细) 和 -q (静默) 参数 pytest 提供了不同的命令行选项来控制输出的详细程度。 pytest (无参数 - 默认模式): 如上所示,对每个通过的测试显示一个点 (.)。 失败的测试显示 F。 如果测试代码本身有错误(不是断言失败,而是比如语法错误或未捕获的异常),会显示 E。 最后会有一个总结,如果存在失败或错误,会有详细的失败信息。 pytest -v (verbose - 详细模式): 这个选项会为每个测试函数显示其完整的名称以及测试结果 (PASSED, FAILED, ERROR)。 当你有很多测试,并且想清楚地看到每个测试的执行状态时,这个模式非常有用。 pytest -v 如果所有测试都通过,输出示例: ============================= test session starts ============================== platform ... -- Python ... plugins: ... collected 3 items test_example.py::test_increment_positive_number PASSED [ 33%] test_example.py::test_increment_zero PASSED [ 66%] test_example.py::test_increment_negative_number PASSED [100%] ============================== 3 passed in X.XXs =============================== pytest -q (quiet - 静默模式): 这个选项会大幅减少输出信息。 如果所有测试都通过,它通常只输出最后的总结行,甚至可能什么都不输出(除了最终的退出码)。 只有在测试失败或出错时,它才会输出相关的错误信息和总结。 这个模式非常适合在持续集成 (CI) 系统中使用,因为你通常只关心是否有问题发生。 pytest -q 如果所有测试都通过,输出示例可能仅仅是: ============================== 3 passed in X.XXs =============================== 或者,如果CI环境配置为在成功时不输出,你可能什么都看不到。 如果你之前运行 pytest -q 没有看到任何关于测试通过的点的输出,那恰恰说明你的所有测试都成功通过了! -q 的设计目标就是在一切顺利时保持安静。 何时使用哪个参数? 日常开发,快速检查:pytest 想看每个测试的名称和状态,或者调试时:pytest -v 在自动化脚本或CI环境中,只关心失败:pytest -q 使用 assert 进行强大的断言 pytest 最棒的一点就是它允许你直接使用 Python 内置的 assert 语句。当 assert 后面的条件为 False 时,会引发 AssertionError。pytest 会捕获这个错误,将测试标记为失败,并提供非常丰富的调试信息,包括表达式中各个部分的值。 让我们看更多断言的例子。创建一个新文件 test_assertions.py: # test_assertions.py import pytest # 需要导入 pytest 来使用 pytest.raises # 要测试的函数 def get_user_info(user_id): if user_id == 1: return {"name": "Alice", "age": 30, "active": True} elif user_id == 2: return {"name": "Bob", "age": 24, "active": False} else: return None def divide(a, b): if b == 0: raise ValueError("Cannot divide by zero") # 注意:这里我们抛出 ValueError return a / b # 测试函数 def test_user_alice(): alice = get_user_info(1) assert alice is not None assert alice["name"] == "Alice" assert alice["age"] > 25 assert alice["active"] is True # 明确检查布尔值 def test_user_bob_inactive(): bob = get_user_info(2) assert bob["name"].startswith("B") assert not bob["active"] # 另一种检查 False 的方式 assert "email" not in bob # 检查字典中是否不包含某个键 def test_unknown_user(): unknown = get_user_info(99) assert unknown is None def test_division_normal(): assert divide(10, 2) == 5.0 assert divide(7, 2) == 3.5 def test_division_by_zero_custom_error(): # 测试函数是否按预期抛出了特定的异常 # pytest.raises 作为一个上下文管理器使用 with pytest.raises(ValueError) as excinfo: # 捕获 ValueError divide(10, 0) # 可选:检查异常信息是否符合预期 assert "Cannot divide by zero" in str(excinfo.value) def test_list_operations(): my_list = [10, 20, 30, 40] assert 20 in my_list assert 50 not in my_list assert len(my_list) == 4 # Pytest 的断言内省对于比较序列非常有用 # 如果下面这个断言失败了: assert my_list == [10, 20, 35, 40] # Pytest 会告诉你具体哪个元素不同 assert my_list == [10, 20, 30, 40] def test_string_properties(): text = "Pytest is awesome!" assert "awesome" in text assert text.lower() == "pytest is awesome!" assert text.endswith("!") assert len(text.split()) == 3 运行这些测试: pytest test_assertions.py -v 关键点: 丰富的比较信息: 如果 assert alice["name"] == "Bob" 失败了 (因为实际上是 “Alice”),pytest 会告诉你 assert "Alice" == "Bob",让你清楚地看到实际值和期望值的差异。 测试异常 (pytest.raises): 当你期望某段代码抛出特定类型的异常时,使用 pytest.raises。它会捕获预期的异常,如果代码没有抛出该异常,或者抛出了不同类型的异常,测试就会失败。excinfo 对象包含了关于捕获到的异常的详细信息。 涵盖多种数据类型: 你可以用 assert 来检查数字、字符串、列表、字典、布尔值等几乎所有 Python 对象。 参数化测试 (@pytest.mark.parametrize) 当你需要用不同的输入和期望输出来测试同一个函数逻辑时,参数化测试非常有用。它可以避免你编写大量结构相似的测试函数。 你已经在你的 test_single_and_batch 测试中使用了它,这是一个很好的实践! 让我们创建一个 test_parametrize_examples.py 文件: # test_parametrize_examples.py import pytest # 要测试的函数 defis_palindrome(text): if not isinstance(text, str): raise TypeError("Input must be a string") return text.lower() == text.lower()[::-1] # 使用 parametrize @pytest.mark.parametrize("test_input, expected_output", [ ("madam", True), ("racecar", True), ("hello", False), ("Aibohphobia", True), # 测试大小写不敏感 ("", True), # 测试空字符串 (" ", True), # 测试单个空格 ("No lemon, no melon.", False) # 包含标点和空格,按当前函数逻辑会失败 ]) def test_is_palindrome_various_inputs(test_input, expected_output): assertis_palindrome(test_input) == expected_output # 另一个例子:测试数据类型检查 @pytest.mark.parametrize("invalid_input", [ 123, ["list"], None, {"a": 1} ]) def test_is_palindrome_invalid_type(invalid_input): with pytest.raises(TypeError) as excinfo: is_palindrome(invalid_input) assert "Input must be a string" in str(excinfo.value) # 你也可以给每个参数组合起一个ID,方便在报告中识别 @pytest.mark.parametrize( "a, b, expected_sum", [ pytest.param(1, 2, 3, id="positive_nums"), pytest.param(-1, -2, -3, id="negative_nums"), pytest.param(-1, 1, 0, id="mixed_nums"), pytest.param(0, 0, 0, id="zeros") ] ) def test_addition(a, b, expected_sum): assert a + b == expected_sum 运行: pytest test_parametrize_examples.py -v 你会看到 test_is_palindrome_various_inputs 为每一组参数都运行了一次。如果其中一组失败,报告会明确指出是哪一组参数导致了失败。test_addition 的输出会使用你提供的 id 来标识每个测试用例。 参数化的好处: 代码简洁: 避免了为每个场景编写单独的测试函数。 可读性高: 测试数据和预期结果清晰地组织在一起。 易于扩展: 添加新的测试场景只需要在参数列表中增加一行。 覆盖更全: 方便测试各种边界条件和特殊情况。 Fixtures (测试固件/夹具) - 优雅的测试准备与清理 Fixtures 是 pytest 中一个非常强大和核心的概念。它们用于: 提供测试所需的上下文或数据: 比如一个数据库连接、一个临时文件、一个已登录的用户对象等。 管理测试的准备 (setup) 和清理 (teardown) 过程: 确保测试在一致的环境中运行,并在测试结束后释放资源。 你可以把 fixture 想象成戏剧表演中的“道具”或“场景布置”。每个需要特定道具的“场景”(测试函数)都可以声明它需要哪些道具,pytest 会在场景开始前准备好这些道具,并在场景结束后清理它们。 定义 Fixture: Fixture 本身也是一个 Python 函数,使用 @pytest.fixture 装饰器来标记。 使用 Fixture: 测试函数如果需要某个 fixture,只需将其名称作为参数声明即可。pytest 会自动查找并执行对应的 fixture 函数,并将其返回值(如果有的话)传递给测试函数。 1. 基础 Fixture 示例 让我们创建一个 test_fixtures_basic.py 文件: # test_fixtures_basic.py import pytest import tempfile # 用于创建临时文件/目录 import os import shutil # 用于删除目录 # 定义一个 fixture,它会创建一个简单的字典数据 @pytest.fixture def sample_user_data(): print("\n(Fixture: Creating sample_user_data...)") # 方便观察fixture何时执行 data = {"username": "testuser", "email": "test@example.com", "is_active": True} return data # 测试函数使用这个 fixture def test_user_username(sample_user_data): print("\n(Test: Running test_user_username...)") assert sample_user_data["username"] == "testuser" def test_user_is_active(sample_user_data): print("\n(Test: Running test_user_is_active...)") assert sample_user_data["is_active"] is True # 另一个 fixture,演示 setup 和 teardown (使用 yield) @pytest.fixture def managed_tmp_dir(): dir_name = tempfile.mkdtemp(prefix="pytest_managed_") # Setup: 创建临时目录 print(f"\n(Fixture: Created temp directory: {dir_name})") yield dir_name # fixture 的值在这里提供给测试函数 # Teardown: 测试函数执行完毕后,这里的代码会执行 print(f"\n(Fixture: Cleaning up temp directory: {dir_name})") shutil.rmtree(dir_name) # 清理临时目录 def test_create_file_in_managed_dir(managed_tmp_dir): print(f"\n(Test: Running test_create_file_in_managed_dir with {managed_tmp_dir})") file_path = os.path.join(managed_tmp_dir, "test_file.txt") with open(file_path, "w") as f: f.write("Hello from fixture test!") assert os.path.exists(file_path) 运行 pytest -v -s test_fixtures_basic.py ( -s 选项可以让你看到 print 语句的输出,方便观察 fixture 的执行流程)。 你会注意到: sample_user_data fixture 在每个需要它的测试函数(test_user_username 和 test_user_is_active)运行之前都会被调用一次。 managed_tmp_dir fixture 在 test_create_file_in_managed_dir 运行前创建了目录,测试结束后该目录被清理。yield 语句是实现这种 setup/teardown 模式的关键。在 yield 之前是 setup 代码,之后是 teardown 代码。 2. Fixture 作用域 (Scope) Fixture 可以有不同的作用域,决定了 fixture 函数执行的频率以及其返回值的生命周期: function (默认): 每个测试函数执行一次。这是最常见的,确保每个测试都有一个干净、独立的 fixture 实例。 class: 每个测试类执行一次。该类中所有测试方法共享同一个 fixture 实例。 module: 每个模块(测试文件)执行一次。该模块中所有测试函数/方法共享同一个 fixture 实例。 session: 整个测试会话(即一次 pytest 运行)执行一次。所有测试共享同一个 fixture 实例。这对于昂贵的 setup 操作(如启动一个外部服务)非常有用。 通过在 @pytest.fixture 装饰器中指定 scope 参数来设置作用域: # test_fixture_scopes.py import pytest # Session-scoped fixture: 在整个测试会话中只执行一次 @pytest.fixture(scope="session") def db_connection(): print("\n(SESSION Fixture: Connecting to database...)") connection = "fake_db_connection_string" # 模拟数据库连接 yield connection print("\n(SESSION Fixture: Closing database connection...)") # Module-scoped fixture: 在这个模块中只执行一次 @pytest.fixture(scope="module") def module_resource(db_connection): # Fixtures 可以依赖其他 fixtures print(f"\n(MODULE Fixture: Setting up module resource using {db_connection}...)") resource = {"id": "module_res_123", "db": db_connection} yield resource print("\n(MODULE Fixture: Tearing down module resource...)") class TestUserOperations: # Class-scoped fixture: 对这个类只执行一次 @pytest.fixture(scope="class") def user_service(self, module_resource): # 注意类方法中的 fixture 需要 self print(f"\n(CLASS Fixture: Initializing UserSerice with {module_resource['id']}...)") service = f"UserService_instance_for_{module_resource['id']}" yield service print("\n(CLASS Fixture: Shutting down UserService...)") # Function-scoped fixture (默认) @pytest.fixture def new_user_payload(self): print("\n(FUNCTION Fixture: Creating new_user_payload...)") return {"username": "temp_user", "role": "guest"} def test_get_user(self, user_service, db_connection): # 使用 class 和 session fixture print(f"\n(Test: test_get_user using {user_service} and {db_connection})") assert user_service is not None assert "fake_db" in db_connection def test_create_user(self, user_service, new_user_payload, module_resource): # 使用 class, function, module fixture print(f"\n(Test: test_create_user using {user_service}, payload: {new_user_payload}, module_res: {module_resource['id']})") assert new_user_payload["username"] == "temp_user" assert module_resource is not None def test_another_module_level_test(module_resource, db_connection): print(f"\n(Test: test_another_module_level_test using {module_resource['id']} and {db_connection})") assert "module_res" in module_resource["id"] 运行 pytest -v -s test_fixture_scopes.py。仔细观察 print 语句的输出顺序和次数,你就能理解不同作用域的 fixture 是如何工作的。 选择合适的作用域很重要: 如果 fixture 的创建和销毁成本很高,或者你希望在多个测试之间共享状态(要小心!),可以使用更广的作用域(class, module, session)。 为了测试的独立性和避免副作用,function 作用域通常是首选。 3. 内置 Fixtures pytest 提供了一些非常有用的内置 fixtures,例如: tmp_path (function scope): 提供一个临时的目录路径 (pathlib.Path 对象),测试结束后会自动清理。 tmp_path_factory (session scope): 一个工厂 fixture,可以用来创建多个临时目录。 capsys, capfd: 用于捕获测试期间打印到 stdout/stderr 的内容。 monkeypatch: 用于安全地修改或替换模块、类或对象的属性,测试结束后自动恢复。 request: 一个特殊的 fixture,提供了关于当前正在执行的测试请求的信息。 你在之前的教程中已经用到了 tmp_path: # test_fixture.py (部分回顾) @pytest.fixture def tmp_file(tmp_path): # tmp_path 是内置 fixture file_path = tmp_path / "my_temp_file.txt" file_path.write_text("test content") return file_path 4. conftest.py: 共享 Fixtures 如果你的多个测试文件都需要使用相同的 fixtures,你可以将它们定义在一个名为 conftest.py 的文件中。pytest 会自动发现并加载 conftest.py 文件中的 fixtures,使其在同一目录及其子目录下的所有测试文件中可用,无需显式导入。 项目结构示例: my_project/ ├── conftest.py # 共享的 fixtures 在这里定义 ├── package_a/ │ └── test_module_a.py └── package_b/ └── test_module_b.py ```conftest.py` 中的内容: ```python # my_project/conftest.py import pytest @pytest.fixture(scope="session") def global_config(): print("\n(CONFTEST: Loading global config...)") return {"api_url": "http://example.com/api", "timeout": 30} 在 test_module_a.py 中可以直接使用 global_config: # my_project/package_a/test_module_a.py def test_api_url(global_config): # 无需导入,可以直接使用 assert "example.com" in global_config["api_url"] ```conftest.py` 是组织和共享 fixtures 的标准方式,能让你的测试代码更整洁。 ## 使用标记 (Markers) 管理测试 `pytest` 允许你使用“标记 (markers)”来给测试函数或类添加元数据。这些标记可以用于: * 跳过某些测试。 * 在特定条件下跳过测试。 * 将测试标记为预期失败 (xfail)。 * 对测试进行分类,方便选择性地运行。 ### 1. 内置标记 * **`@pytest.mark.skip(reason="...")`**: 无条件跳过该测试。 * **`@pytest.mark.skipif(condition, reason="...")`**: 当 `condition` 为真时跳过该测试。 * **`@pytest.mark.xfail(condition, reason="...", strict=False)`**: 标记测试为“预期失败”。如果测试实际通过了(而你标记为 xfail),默认情况下会报告为 `XPASS`。如果测试如预期般失败了,会报告为 `XFAIL`。如果设置 `strict=True`,那么 `XPASS` 会被视为测试失败。这对于标记那些已知有 bug 但暂时不修复的测试很有用。 * **`@pytest.mark.parametrize(...)`**: 我们已经学习过了,用于参数化测试。 ```python # test_markers.py import pytest import sys def get_python_version(): return sys.version_info @pytest.mark.skip(reason="这个功能尚未实现") def test_new_feature(): assert False IS_WINDOWS = sys.platform == "win32" @pytest.mark.skipif(IS_WINDOWS, reason="此测试仅在非 Windows 系统上运行") def test_linux_specific_path(): path = "/usr/local/bin" assert path.startswith("/") @pytest.mark.skipif(get_python_version() < (3, 8), reason="需要 Python 3.8 或更高版本") def test_feature_for_python38_plus(): # 一些只在 Python 3.8+ 中可用的特性 assert True @pytest.mark.xfail(reason="已知bug #123,除数为零") def test_division_bug(): assert 1 / 0 == 1 # 这会抛出 ZeroDivisionError @pytest.mark.xfail(get_python_version() < (3, 10), reason="此功能在旧版Python中可能表现不同") def test_potentially_flaky_on_old_python(): # 假设这个测试在 Python < 3.10 时可能通过也可能失败 if get_python_version() < (3, 10): assert 1 == 1 # 在旧版 Python 中,我们预期它可能失败 (xfail) else: assert 1 == 1 # 在新版 Python 中,我们预期它通过 2. 自定义标记与运行特定标记的测试 你可以定义自己的标记,以便对测试进行逻辑分组。在 pytest.ini 或 pyproject.toml 文件中注册自定义标记是个好习惯,以避免拼写错误和警告。 pytest.ini 示例: [pytest] markers = slow: 标记运行缓慢的测试 smoke: 标记为冒烟测试,用于快速检查核心功能 integration: 标记为集成测试 在测试中使用自定义标记: # test_custom_markers.py import pytest import time @pytest.mark.slow def test_very_slow_operation(): time.sleep(2) # 模拟一个耗时操作 assert True @pytest.mark.smoke def test_quick_check(): assert 1 + 1 == 2 @pytest.mark.integration @pytest.mark.smoke # 一个测试可以有多个标记 def test_api_login(): # 模拟 API 登录 assert True 运行特定标记的测试: 使用 -m 命令行选项: pytest -m smoke # 只运行标记为 smoke 的测试 pytest -m "not slow" # 运行所有未标记为 slow 的测试 pytest -m "smoke and integration" # 运行同时标记为 smoke 和 integration 的测试 pytest -m "smoke or slow" # 运行标记为 smoke 或 slow 的测试 组织测试:测试类 虽然 pytest 不需要你把测试写在类里,但对于组织一组相关的测试,使用类是一个不错的选择。 类名必须以 Test 开头。 类中的测试方法名必须以 test_ 开头。 不需要继承任何特定的基类 (如 unittest.TestCase)。 # test_calculator_class.py class Calculator: def add(self, a, b): return a + b def subtract(self, a, b): return a - b def multiply(self, a, b): return a * b def divide(self, a, b): if b == 0: raise ValueError("Cannot divide by zero") return a / b class TestCalculator: # 你可以在类级别使用 fixture,它会对该类的所有测试方法生效 # (如果 fixture scope 是 'class' 或更广) # 例如,可以在这里创建一个 Calculator 实例供所有测试使用 def test_addition(self): # 注意方法需要 self 参数 calc = Calculator() assert calc.add(2, 3) == 5 assert calc.add(-1, 1) == 0 def test_subtraction(self): calc = Calculator() assert calc.subtract(5, 3) == 2 # ... 其他测试方法 ... 配置文件 (pytest.ini 或 pyproject.toml) 你可以通过在项目根目录创建 pytest.ini 文件或在 pyproject.toml 中添加 [tool.pytest.ini_options] 部分,来自定义 pytest 的行为。 pytest.ini 示例: [pytest] # 改变测试文件的发现模式 python_files = test_*.py check_*.py example_*.py # 改变测试函数/方法的发现模式 python_functions = test_* check_* example_* # 改变测试类的发现模式 python_classes = Test* Check* Example* # 默认添加的命令行选项 addopts = -v --cov=. --cov-report=html # 注册自定义标记 (避免警告) markers = slow: marks tests as slow to run serial: marks tests that cannot be run in parallel # 忽略某些目录 norecursedirs = .git venv build *.egg-info 这只是冰山一角,pytest 的配置选项非常丰富。 总结与后续学习 恭喜你!通过这个扩展教程,你已经掌握了 pytest 的许多核心概念和实用技巧: 编写和运行基础测试。 理解不同的输出模式 (-v, -q)。 使用强大的 assert 语句进行断言和异常测试。 通过 @pytest.mark.parametrize 实现参数化测试,提高测试覆盖率和代码复用。 掌握了 Fixture 的核心用法,包括定义、使用、作用域 (function, class, module, session)、带 yield 的 setup/teardown 模式,以及如何通过 conftest.py 共享 fixtures。 了解了如何使用标记 (@pytest.mark.*) 来管理和选择性地运行测试。 知道了如何将测试组织在类中。 对 pytest 的配置文件有了初步认识。 接下来你可以探索: 更高级的 Fixture 用法: 如 autouse fixtures,fixture 的参数化,使用 fixture 返回工厂函数等。 插件的使用: pytest-cov: 测试覆盖率。 pytest-xdist: 并行测试。 pytest-mock: 方便地使用 mocking。 针对你使用的框架(如 Django, Flask, FastAPI)的 pytest 插件。 生成 HTML 测试报告: 使用 pytest-html 插件。 pytest 官方文档: 这是最权威和最全面的学习资源 (https://docs.pytest.org/)。 编写测试是保证代码质量、提升开发信心的关键环节。pytest 以其简洁和强大,让编写测试不再是一件苦差事,反而可以成为一种乐趣。希望这篇教程能帮助你轻松入门并爱上 pytest!
Techniques
· 2025-05-28
Deploy PostgreSQL Database and MinIO Object Storage: Complete Server Setup Guide
Ubuntu 22.04 服务器部署 PostgreSQL 数据库、MinIO 对象存储以及一个通过 Nginx 反向代理访问的 Docker化 Django 后端应用完整教程 目标: 部署 PostgreSQL 数据库、MinIO 对象存储以及一个通过 Nginx 反向代理访问的 Docker化 Django 后端应用。 服务器 IP 定义 (请在脚本和配置中替换为您真实的服务器 IP): SERVER_IP="123.45.6.78" (示例 IP,请务必修改) 第 1 步:系统初始化与基础依赖安装 首先,更新您的服务器并安装一些必要的工具。 # 更新系统包列表并升级现有包 sudo apt update && sudo apt upgrade -y # 安装基础工具:ca-certificates, curl, gnupg, lsb-release 用于添加 Docker源,nginx 用于反向代理,git 用于拉取代码 sudo apt install -y ca-certificates curl gnupg lsb-release nginx git 第 2 步:安装 Docker CE 和 Docker Compose 我们将使用 Docker 来容器化 MinIO 和 Django 应用。 # 1. 添加 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # 2. 设置 Docker APT 软件源 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 3. 安装 Docker CE (社区版), CLI, Containerd, 和 Docker Compose 插件 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 4. (可选)配置国内 Docker 镜像加速器,以提高拉取镜像的速度 # 请根据您选择的云服务商或镜像源替换下面的地址 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<EOF { "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://hub-mirror.c.163.com", "https://registry.docker-cn.com" ] } EOF sudo systemctl daemon-reload sudo systemctl restart docker # 5. 将当前用户添加到 docker 组,这样执行 docker 命令时无需 sudo(需要重新登录或执行 newgrp docker 生效) sudo usermod -aG docker $USER echo "请重新登录或执行 'newgrp docker' 使 docker 组权限生效。" # 6. 验证 Docker 是否安装成功 docker --version docker compose version 第 3 步:安装 PostgreSQL 并配置远程访问 Django 应用将使用 PostgreSQL作为数据库。 # 1. 安装 PostgreSQL 和相关工具 sudo apt install -y postgresql postgresql-contrib # 2. 修改 PostgreSQL 配置以允许远程连接 # 编辑 postgresql.conf 文件,将 listen_addresses 从 'localhost' 改为 '*' # 注意:您的 PostgreSQL 版本可能不同,请相应调整路径(例如 /etc/postgresql/16/main/) # 您可以通过 `pg_lsclusters` 查看版本和路径 sudo sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/g" /etc/postgresql/$(pg_lsclusters | awk 'NR==2 {print $1}')/main/postgresql.conf # 3. 修改 pg_hba.conf 文件以允许来自任何 IP 地址的 md5 密码认证连接 # 同样,注意 PostgreSQL 版本路径 echo "host all all 0.0.0.0/0 md5" | sudo tee -a /etc/postgresql/$(pg_lsclusters | awk 'NR==2 {print $1}')/main/pg_hba.conf # 4. 重启 PostgreSQL 服务使配置生效 sudo systemctl restart postgresql # 5. 设置 PostgreSQL 的 postgres 用户密码(重要!) # 将 'YourSecurePostgresPassword!' 替换为您自己的强密码 sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'YourSecurePostgresPassword!';" 可视化操作与安全组说明 (PostgreSQL) 云服务器安全组: 在您的云服务提供商(如阿里云、腾讯云、AWS)的控制台中,找到您的服务器实例对应的安全组(或防火墙规则)。 添加入站规则,允许来自您需要访问数据库的 IP 地址(或者为了开发方便暂时允许 0.0.0.0/0,但生产环境不推荐)访问 PostgreSQL 的默认端口 5432/TCP。 数据库客户端连接: 您可以使用图形化数据库管理工具(如 pgAdmin, DBeaver, Navicat 等)从您的本地计算机连接到服务器上的 PostgreSQL。 连接信息: 主机/服务器地址: YOUR_SERVER_IP (例如 123.45.6.78) 端口: 5432 数据库: 默认可以是 postgres 用户名: postgres 密码: 您在上面第 5 步设置的 YourSecurePostgresPassword! 创建专用数据库和用户 (推荐): 虽然您可以使用 postgres 超级用户,但更安全的做法是为您的 Django 应用创建一个专用的数据库和用户。登录后,在 SQL 工具中执行: CREATE DATABASE myproject_db; CREATE USER myproject_user WITH PASSWORD 'MyProjectSecurePassword!'; GRANT ALL PRIVILEGES ON DATABASE myproject_db TO myproject_user; ALTER ROLE myproject_user CREATEDB; -- 可选,如果需要用户创建数据库 之后在 Django 的 settings.py 中使用这些新的凭据。 第 4 步:部署 MinIO 对象存储 (使用 Docker) MinIO 将用于存储 Django 应用的媒体文件和静态文件。 wget https://dl.min.io/client/mc/release/linux-amd64/mc chmod +x mc sudo mv mc /usr/local/bin/ # 1. 创建 MinIO 数据存储目录 sudo mkdir -p /minio/data sudo chmod -R 777 /minio/data # 临时给予宽松权限,生产环境应更精细控制 # 2. 使用 Docker 启动 MinIO 容器 # 将 'YourMinioAdminUser' 和 'YourMinioAdminPassword!' 替换为您自己的凭据 # 确保密码足够复杂(至少8位,包含大小写、数字、特殊字符) docker run -d \ --name minio \ -p 9000:9000 \ -p 9001:9001 \ -v /minio/data:/data \ -e "MINIO_ROOT_USER=admin" \ -e "MINIO_ROOT_PASSWORD=YourSecureMinioPassword!" \ quay.io/minio/minio:latest \ server /data --console-address ":9001" # 2. 设置别名(注意用单引号包裹密码) mc alias set myminio http://123.45.6.78:9000 admin 'YourSecureMinioPassword!' # 2. 修改密码 mc admin user password myminio admin 'NewSecurePass123!' 可视化操作与安全组说明 (MinIO) 云服务器安全组: 开放 MinIO API 端口: 9000/TCP 开放 MinIO 控制台端口: 9001/TCP 访问 MinIO 控制台: 在浏览器中打开 http://YOUR_SERVER_IP:9001 (例如 http://123.45.6.78:9001)。 使用您在 docker run 命令中设置的 MINIO_ROOT_USER (例如 admin) 和 MINIO_ROOT_PASSWORD (例如 YourSecureMinioPassword!) 登录。 创建存储桶 (Buckets): 登录 MinIO 控制台后,点击 “Buckets” -> “Create Bucket”。 创建两个存储桶: media (用于存储用户上传的文件) static (用于存储 Django 的静态文件) 重要: 为这两个存储桶设置访问策略 (Access Policy)。对于公开访问的静态文件和媒体文件,您可能需要将策略设置为 public 或 readonly (根据需求)。点击存储桶旁边的 “Manage” -> “Access Policy”,选择 “Add Policy”,然后选择 readonly 或 download (对于 public,可以直接在创建时设置)。更精细的权限控制请参考 MinIO 文档。 第 5 步:部署 Django 后端应用 (使用 Docker) 5.1 准备 Django 项目代码 # 1. 克隆您的 Django 项目代码 (替换为您的仓库地址) # git clone https://github.com/your-username/your-backend-repo.git # cd your-backend-repo # 假设您已将代码上传到服务器的某个目录,例如 /srv/django-app # cd /srv/django-app # 2. 配置 Django settings.py (或通过环境变量传递) # 确保您的 settings.py 文件中数据库和 MinIO 配置正确。 # 数据库示例 (使用您在 PostgreSQL 步骤中创建的用户和数据库): # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.postgresql', # 'NAME': 'myproject_db', # 'USER': 'myproject_user', # 'PASSWORD': 'MyProjectSecurePassword!', # 'HOST': 'YOUR_SERVER_IP', # Django 容器需要能访问到宿主机的 PostgreSQL # 'PORT': '5432', # } # } # # MinIO (django-storages) 示例: # DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' # STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' # AWS_ACCESS_KEY_ID = 'admin' # MinIO Root User # AWS_SECRET_ACCESS_KEY = 'YourSecureMinioPassword!' # MinIO Root Password # AWS_STORAGE_BUCKET_NAME = 'media' # 默认文件存储桶 # AWS_S3_ENDPOINT_URL = 'http://YOUR_SERVER_IP:9000' # MinIO API 地址 # AWS_S3_OBJECT_PARAMETERS = { 'CacheControl': 'max-age=86400', } # AWS_DEFAULT_ACL = None # 或 'public-read' 根据需求 # AWS_S3_USE_SSL = False # 如果 MinIO 没有配置 SSL # AWS_S3_VERIFY = True # 如果 MinIO 使用自签名证书,可能需要设为 False 或提供证书路径 # AWS_S3_REGION_NAME = 'us-east-1' # MinIO 不需要区域,但 boto3 可能需要 # AWS_S3_SIGNATURE_VERSION = 's3v4' # STATIC_URL = f'{AWS_S3_ENDPOINT_URL}/static/' # 如果使用 MinIO 存储静态文件 # MEDIA_URL = f'{AWS_S3_ENDPOINT_URL}/media/' # # **重要**: 确保 Django 容器可以访问到 PostgreSQL 和 MinIO。 # 如果 PostgreSQL 和 MinIO 也在 Docker 中运行,并且在同一个 Docker 网络中, # 可以使用容器名作为 HOST (例如 'minio' 而不是 'YOUR_SERVER_IP')。 # 如果 PostgreSQL 在宿主机上运行,Django 容器可以使用宿主机的 IP 或 `host.docker.internal` (某些 Docker 版本)。 5.2 创建 Dockerfile 在您的 Django 项目根目录下创建一个名为 Dockerfile 的文件: # Dockerfile FROM python:3.10-slim # 设置工作目录 WORKDIR /app # 设置 pip 国内镜像源 (可选, 加快构建速度) RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple # 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制项目代码到工作目录 COPY . . # 暴露 Django 应用运行的端口 EXPOSE 8000 # 运行 Django 开发服务器 (生产环境推荐使用 Gunicorn 或 uWSGI) # CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] # 使用 Gunicorn (确保 gunicorn 在 requirements.txt 中) CMD ["gunicorn", "your_project_name.wsgi:application", "--bind", "0.0.0.0:8000"] # 将 your_project_name 替换为您的 Django 项目的实际名称 (wsgi.py 所在的目录名) 5.3 构建并运行 Django Docker 镜像 在包含 Dockerfile 的 Django 项目根目录下执行: # 构建 Docker 镜像 (将 django-backend 替换为您的镜像名) docker build -t django-backend . # 运行 Django 容器 # 确保旧的同名容器已停止并移除: # docker stop django-app && docker rm django-app docker run -d \ -p 8000:8000 \ -e DJANGO_SETTINGS_MODULE=your_project_name.settings \ --name django-app \ django-backend # 将 your_project_name.settings 替换为您的 Django settings 文件路径 第 6 步:配置 Nginx 反向代理 Nginx 将作为前端服务器,接收外部请求并将其转发到 Django 应用。 # 1. 创建 Nginx 配置文件 # 将 YOUR_SERVER_IP 替换为您的服务器公网 IP 或域名 sudo bash -c "cat <<EOF > /etc/nginx/sites-available/django_proxy server { listen 80; server_name YOUR_SERVER_IP; # 例如 123.45.6.78 或 example.com location / { proxy_pass http://127.0.0.1:8000; # Django 应用运行的地址和端口 proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; } location /static/ { # 如果 Django 自己处理静态文件 (DEBUG=True) alias /srv/django-app/staticfiles/; # 替换为你的 Django 项目静态文件收集目录 } location /media/ { # 如果 Django 自己处理媒体文件 (DEBUG=True) alias /srv/django-app/media/; # 替换为你的 Django 项目媒体文件目录 } } EOF" # 2. 创建符号链接以启用该配置 # 先删除可能存在的默认配置的符号链接(如果它占用了 default_server) # sudo rm /etc/nginx/sites-enabled/default sudo ln -s /etc/nginx/sites-available/django_proxy /etc/nginx/sites-enabled/django_proxy # 3. 测试 Nginx 配置 sudo nginx -t # 4. 重启 Nginx 服务 sudo systemctl restart nginx 可视化操作与安全组说明 (Nginx & Django) 云服务器安全组: 确保 HTTP 端口 80/TCP 已对公网开放。 如果未来配置 HTTPS,也需要开放 443/TCP。 访问您的应用: 在浏览器中输入 http://YOUR_SERVER_IP (例如 http://123.45.6.78)。 如果一切配置正确,您应该能看到您的 Django 应用首页。 如果部署了 Swagger,可以尝试访问 http://YOUR_SERVER_IP:8000/api/swagger/ (路径取决于您的 Django URL 配置)。 第 7 步:自动化部署脚本 将以下脚本保存为 deploy_django_stack.sh,赋予执行权限 (chmod +x deploy_django_stack.sh),然后运行它。 请务必在使用前仔细阅读脚本内容,并根据您的实际情况修改占位符和配置! #!/bin/bash # --- 配置变量 (请务必根据您的实际情况修改!) --- SERVER_IP="123.45.6.78" # 您的服务器公网 IP POSTGRES_PASSWORD="YourSecurePostgresPassword!" MINIO_ROOT_USER="admin" MINIO_ROOT_PASSWORD="YourSecureMinioPassword!" DJANGO_PROJECT_NAME="backend" # 您 Django 项目中包含 wsgi.py 的目录名 # DJANGO_REPO_URL="https://github.com/your-username/your-backend-repo.git" # 您的 Django 代码仓库地址 # DJANGO_PROJECT_DIR="/srv/django-app" # Django 项目将被克隆/放置到的目录 echo "--- 服务器部署脚本 ---" echo "服务器 IP 将被设置为: $SERVER_IP" echo "PostgreSQL 'postgres' 用户密码将设置为: $POSTGRES_PASSWORD" echo "MinIO 管理员用户: $MINIO_ROOT_USER" echo "MinIO 管理员密码: $MINIO_ROOT_PASSWORD" echo "Django 项目名 (用于 Gunicorn): $DJANGO_PROJECT_NAME" # echo "Django 代码仓库: $DJANGO_REPO_URL" # echo "Django 项目目录: $DJANGO_PROJECT_DIR" read -p "确认以上信息正确并开始部署吗?(yes/no): " confirmation if [ "$confirmation" != "yes" ]; then echo "部署已取消。" exit 1 fi echo "--- 1. 系统初始化与依赖安装 ---" sudo apt update && sudo apt upgrade -y sudo apt install -y ca-certificates curl gnupg lsb-release nginx git echo "--- 2. 安装 Docker CE 和 Docker Compose ---" sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<EOF { "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://hub-mirror.c.163.com", "https://registry.docker-cn.com" ] } EOF sudo systemctl daemon-reload sudo systemctl restart docker sudo usermod -aG docker $USER echo "Docker 安装完成。请重新登录或执行 'newgrp docker' 以应用 docker 组权限。" echo "按 Enter 继续..." read echo "--- 3. 安装 PostgreSQL 并配置 ---" sudo apt install -y postgresql postgresql-contrib PG_VERSION=$(pg_lsclusters | awk 'NR==2 {print $1}') if [ -z "$PG_VERSION" ]; then echo "错误:无法检测到 PostgreSQL 版本。请手动配置。" exit 1 fi echo "检测到 PostgreSQL 版本: $PG_VERSION" sudo sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/g" /etc/postgresql/$PG_VERSION/main/postgresql.conf sudo sh -c "echo 'host all all 0.0.0.0/0 md5' >> /etc/postgresql/$PG_VERSION/main/pg_hba.conf" sudo systemctl restart postgresql sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD '$POSTGRES_PASSWORD';" echo "PostgreSQL 安装和配置完成。" echo "--- 4. 部署 MinIO 对象存储 ---" sudo mkdir -p /minio/data sudo chmod -R 777 /minio/data # 确保 Docker 有权限写入 docker stop minio || true && docker rm minio || true # 确保旧容器被移除 docker run -d \ --name minio \ -p 9000:9000 \ -p 9001:9001 \ -v /minio/data:/data \ -e "MINIO_ROOT_USER=${MINIO_ROOT_USER}" \ -e "MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}" \ quay.io/minio/minio:latest \ server /data --console-address ":9001" echo "MinIO 部署完成。请访问 http://$SERVER_IP:9001 并使用以下凭据登录:" echo "用户名: $MINIO_ROOT_USER" echo "密码: $MINIO_ROOT_PASSWORD" echo "登录后,请手动创建 'media' 和 'static' 存储桶,并根据需要设置其访问策略为公开可读。" echo "按 Enter 继续..." read echo "--- 5. 部署 Django 后端应用 ---" # echo "克隆 Django 项目代码从 $DJANGO_REPO_URL 到 $DJANGO_PROJECT_DIR..." # sudo mkdir -p $DJANGO_PROJECT_DIR # sudo chown $USER:$USER $DJANGO_PROJECT_DIR # 确保当前用户有权限 # git clone $DJANGO_REPO_URL $DJANGO_PROJECT_DIR # cd $DJANGO_PROJECT_DIR echo "假设 Django 项目代码已位于当前目录或指定目录。" echo "请确保您的 Django 项目 (例如: ./backend/settings.py) 已配置好数据库和 MinIO 连接。" echo "数据库主机应指向 '$SERVER_IP' (如果 PostgreSQL 在宿主机上运行)。" echo "MinIO Endpoint URL 应指向 'http://$SERVER_IP:9000'。" echo "按 Enter 继续以创建 Dockerfile 并构建镜像..." read # 创建 Dockerfile cat <<EOF > Dockerfile FROM python:3.10-slim ENV PYTHONUNBUFFERED 1 WORKDIR /app RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 # 将 your_project_name 替换为您的 Django 项目名 (wsgi.py 所在的目录名) CMD ["gunicorn", "${DJANGO_PROJECT_NAME}.wsgi:application", "--bind", "0.0.0.0:8000"] EOF echo "Dockerfile 已创建。" echo "构建 Django Docker 镜像 (django-backend)..." docker build -t django-backend . echo "运行 Django Docker 容器 (django-app)..." docker stop django-app || true && docker rm django-app || true # 确保旧容器被移除 docker run -d \ -p 8000:8000 \ -e DJANGO_SETTINGS_MODULE=${DJANGO_PROJECT_NAME}.settings \ --name django-app \ django-backend echo "Django 应用容器已启动。" echo "按 Enter 继续配置 Nginx..." read echo "--- 6. 配置 Nginx 反向代理 ---" # Nginx 已在步骤1安装 sudo bash -c "cat <<EOF > /etc/nginx/sites-available/django_proxy server { listen 80; server_name $SERVER_IP; client_max_body_size 100M; # 允许上传大文件 location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; } # 如果您希望 Nginx 直接处理静态文件和媒体文件 (生产环境推荐) # 请确保 Django 的 collectstatic 已将文件收集到 Nginx 可访问的路径 # location /static/ { # alias /path/to/your/django_project/staticfiles/; # 替换为实际路径 # } # location /media/ { # alias /path/to/your/django_project/mediafiles/; # 替换为实际路径 # } } EOF" # 确保旧的 default 站点(如果存在且冲突)被禁用 if [ -L /etc/nginx/sites-enabled/default ]; then sudo rm /etc/nginx/sites-enabled/default fi # 强制创建或更新符号链接 sudo ln -sf /etc/nginx/sites-available/django_proxy /etc/nginx/sites-enabled/django_proxy echo "测试 Nginx 配置..." sudo nginx -t if [ \$? -ne 0 ]; then echo "Nginx 配置测试失败!请检查 /etc/nginx/sites-available/django_proxy 文件。" exit 1 fi echo "重启 Nginx 服务..." sudo systemctl restart nginx echo "Nginx 配置完成。" echo "--- 部署完成!---" echo "请确保您的云服务器安全组已开放以下端口:" echo " - PostgreSQL: 5432/TCP (如果需要远程访问数据库)" echo " - MinIO API: 9000/TCP" echo " - MinIO 控制台: 9001/TCP" echo " - HTTP (Nginx): 80/TCP" echo "" echo "您现在应该可以通过 http://$SERVER_IP 访问您的 Django 应用。" echo "MinIO 控制台: http://$SERVER_IP:9001" echo "如果 Django 应用需要数据库迁移,请在容器内执行:" echo " docker exec -it django-app python manage.py makemigrations your_app_name" echo " docker exec -it django-app python manage.py migrate" echo "如果需要创建超级用户:" echo " docker exec -it django-app python manage.py createsuperuser" 8. 用户数据迁移策略 (可选) 当系统用户量增长,或者需要从旧系统迁移数据时,需要考虑数据迁移策略。 新用户注册与数据处理: Django Auth: Django 自带的 django.contrib.auth 系统能很好地处理新用户注册、密码哈希存储、登录认证等。当新用户通过您的 API (例如 /api/register/) 注册时,会在 auth_user 表(或您自定义的用户模型表)中创建一条记录。 关联用户信息 (UserInfo): 如果您有一个单独的 UserInfo 模型通过 OneToOneField 或 ForeignKey 关联到主用户模型,确保在用户注册成功后,或用户首次编辑个人资料时,创建或更新对应的 UserInfo 记录。user_id 将作为关联两个表的键。 从旧系统迁移数据 (如果适用): 数据导出: 从旧系统导出用户数据,通常为 CSV、JSON 或 SQL dump 格式。 数据清洗与转换: 清洗数据,使其符合新系统的数据模型。特别注意密码的处理,如果旧系统密码哈希算法与 Django 不兼容,用户可能需要在首次登录新系统时重置密码。 数据导入: Django 管理命令: 编写自定义的 Django management command (python manage.py your_custom_command),使用 Django ORM 来创建用户和关联信息。这是推荐的方式,因为它会处理所有模型逻辑和信号。 直接 SQL: 对于非常大的数据集,直接使用 SQL 导入到 PostgreSQL 可能更快,但需要非常小心,确保数据完整性和关联正确,并且后续可能需要手动处理 Django 的 contenttypes 等。 第三方库: 例如 django-import-export 库可以帮助处理复杂的数据导入导出。 数据完整性与验证: 在导入过程中,使用 Django 模型的 full_clean() 方法或表单验证来确保数据符合新模型的约束。 利用数据库的约束(如 UNIQUE, NOT NULL)来保证数据质量。 处理静态文件和媒体文件 (如头像): 如果旧系统有用户上传的文件,需要将这些文件迁移到新的存储位置(例如 MinIO)。 更新数据库中指向这些文件的路径或 URL。 如果文件名或路径结构发生变化,需要编写脚本来批量更新。 扩展性考虑: 数据库: PostgreSQL 本身具有良好的扩展性。对于非常大的负载,可以考虑读写分离、分区等策略。 Django 应用: 使用 Gunicorn 或 uWSGI 配合多个 worker 进程可以处理更多并发请求。Nginx 作为反向代理可以进行负载均衡。 缓存: 对常用数据和计算结果使用缓存(如 Redis、Memcached)可以显著提高性能。 备份与恢复: 数据库: 定期使用 pg_dump 备份 PostgreSQL 数据库。制定恢复计划。 MinIO: 定期备份 MinIO 的数据卷 (/minio/data)。MinIO 也支持自身的复制和纠删码功能来提高数据可靠性。 应用代码和配置: 使用版本控制 (如 Git) 管理代码,并备份 Docker 镜像和相关配置文件。 9. MinIO 和 Django (Docker) Debug 指南 在部署和运行 MinIO 及 Docker化的 Django 应用时,可能会遇到一些常见问题。以下是一些调试步骤和技巧,结合了您之前遇到的情况: 9.1 MinIO Client (mc) 相关问题 mc: Segmentation fault: 原因: mc 二进制文件损坏、与系统架构不兼容(例如在 ARM 服务器上运行了 AMD64 版本)或下载不完整。 解决方案: 彻底卸载旧/损坏的 mc: sudo rm -f /usr/local/bin/mc which mc # 确认已删除,应无输出 下载正确的官方版本 (假设为 linux-amd64): wget https://dl.min.io/client/mc/release/linux-amd64/mc chmod +x mc sudo mv mc /usr/local/bin/ 验证: mc --version 检查文件类型: file /usr/local/bin/mc (应显示 ELF 64-bit LSB executable, x86-64,...) mc alias set ...: The request signature we calculated does not match…: 原因: Access Key / Secret Key 错误: 您提供的 admin 和 SecurePassword123! (或您实际使用的密码) 与 MinIO 服务启动时配置的 MINIO_ROOT_USER / MINIO_ROOT_PASSWORD 不匹配。 Endpoint URL 错误: URL 格式不正确 (例如多了斜杠 http://123.45.6.78/:9000) 或地址/端口错误。正确应为 http://YOUR_SERVER_IP:9000。 MinIO 服务未运行或端口 9000 未正确映射/防火墙未开放。 解决方案: 确认 MinIO 容器正在运行且端口 9000 已映射: docker ps | grep minio 仔细核对 mc alias set 命令中的 Access Key (用户名), Secret Key (密码), 和 Endpoint URL。 确保密码中没有特殊字符导致命令行解析问题,或者用单引号包裹密码:mc alias set myminio http://123.45.6.78:9000 admin 'YourSecureMinioPassword!' mc admin user password ...: password is not a recognized command: 原因: mc 版本过旧,不支持该子命令。 命令语法错误。 解决方案: 升级 mc: 确保您使用的是最新版本的 mc (参考上面安装步骤)。 正确语法: mc admin user password ALIAS USERNAME NEW_PASSWORD 例如: mc admin user password myminio admin 'NewSecurePassword123!' (确保 myminio 别名已成功设置)。 9.2 Docker 相关问题 docker run ...: address already in use (e.g., for port 8000): 原因: 您尝试映射到宿主机的端口 (例如 8000) 已经被其他进程占用。 解决方案: 查找并停止占用端口的进程: sudo lsof -i :8000 # 或 sudo netstat -tulnp | grep 8000 # 找到 PID 后,使用 sudo kill <PID> 或 sudo kill -9 <PID> 如果占用者是另一个 Docker 容器,先停止并移除它: docker ps -a # 查看所有容器,找到占用端口的容器 docker stop <container_id_or_name> docker rm <container_id_or_name> 或者,更改您当前要运行的容器的端口映射,例如将 Django 映射到宿主机的 8001 端口: docker run -p 8001:8000 ... (同时需要更新 Nginx 配置中的 proxy_pass 指向 http://127.0.0.1:8001;) docker run ...: Conflict. The container name “/django-app” is already in use…: 原因: 已存在一个同名的 Docker 容器 (即使它已停止)。 解决方案: 删除旧的同名容器: docker rm django-app (如果容器已停止) 或 docker stop django-app && docker rm django-app (如果正在运行)。 或者,为新容器指定一个不同的名称:docker run --name django-app-v2 ... docker run ...: invalid reference format或django-backend: command not found`: 原因: Docker 无法找到您指定的镜像 django-backend。 镜像名称拼写错误或大小写不匹配 (Docker 镜像名通常是小写)。 镜像尚未成功构建,或者构建时使用了不同的标签。 解决方案: 确认镜像是否存在且名称正确: docker images | grep django-backend 如果不存在,请确保在 Django 项目根目录 (包含 Dockerfile) 下重新构建: docker build -t django-backend . 确保 docker run 命令中使用的镜像名称与 docker images 中显示的完全一致。 9.3 Django (Docker 内部) 调试 检查 Django 容器状态和日志: docker ps -a | grep django-app # 查看容器是否正在运行 docker logs django-app # 查看 Django 容器的实时日志(非常重要!) 日志会显示 Gunicorn/Django 的启动信息、任何 Python 错误、数据库连接问题等。 进入 Django 容器内部进行调试: docker exec -it django-app bash # 进入容器的 shell 进入容器后,您可以: 检查文件是否存在,路径是否正确。 手动运行 python manage.py check 查看是否有配置问题。 尝试连接数据库:python manage.py dbshell (如果安装了 psql 客户端)。 查看环境变量是否正确设置。 数据库连接问题: 错误: 日志中可能出现 OperationalError: could not connect to server 或类似错误。 检查: PostgreSQL 服务是否在宿主机或另一容器中运行。 Django settings.py 中的 DATABASES 配置(HOST, PORT, NAME, USER, PASSWORD)是否正确。 如果 PostgreSQL 在宿主机,Django 容器是否能访问到宿主机的 IP 和端口。可能需要在 docker run 时使用 --network="host" (不推荐,除非必要) 或确保 Docker 网络配置允许。更常见的是使用服务器的公网 IP (确保 PostgreSQL 监听 * 且防火墙允许)。 PostgreSQL 的 pg_hba.conf 是否允许来自 Docker 容器 IP 地址范围的连接。 9.4 Nginx 调试 nginx: [emerg] invalid number of arguments in "proxy_set_header" directive...: 原因: proxy_set_header 指令语法错误,通常是参数数量不对或变量名错误。 示例错误: proxy_set_header X-Forwarded-For $remote_addr; 正确示例: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 解决方案: 仔细检查 Nginx 配置文件 (/etc/nginx/sites-available/django_proxy) 中的所有 proxy_set_header 指令,确保它们都遵循 proxy_set_header <字段名> <值>; 的格式。 注意变量: Nginx 内置变量如 $host, $remote_addr, $proxy_add_x_forwarded_for 不需要额外的转义符。 Nginx 502 Bad Gateway: 原因: Nginx 无法连接到 proxy_pass 指令中指定的后端 Django 应用。 解决方案: 确认 Django 容器运行: docker ps | grep django-app。 检查 Django 容器日志: docker logs django-app,查看是否有启动错误。 确认 Django 监听端口: 确保 Django 应用 (Gunicorn) 在容器内部监听 0.0.0.0:8000。 确认 Nginx proxy_pass 地址: 通常是 http://127.0.0.1:8000; (因为 Django 容器的 8000 端口映射到了宿主机的 8000 端口,Nginx 从宿主机访问此端口)。 网络测试: 在服务器上尝试 curl http://127.0.0.1:8000,看是否能访问到 Django 应用。 Django 404 for /swagger/ (但 /api/swagger/ works): 原因: 访问的 URL 路径与 Django urls.py 中定义的路由不匹配。 解决方案: 确保您在浏览器中访问的是 Django urls.py 中为 Swagger UI 定义的正确路径,例如 http://YOUR_SERVER_IP/api/swagger/。 9.5 通用调试技巧 逐步验证: 从底层服务(PostgreSQL, MinIO)开始,确保它们独立运行时正常,然后再验证 Django 应用,最后是 Nginx。 简化配置: 如果遇到复杂问题,尝试暂时简化配置(例如,移除 Nginx,直接暴露 Django 容器端口进行测试)以缩小问题范围。 查看所有日志: 同时关注 PostgreSQL, MinIO, Django (Gunicorn), Nginx 的日志,它们通常会提供关键的错误信息。 网络工具: 使用 ping, curl, telnet, netstat, ss 等工具检查网络连通性和端口监听情况。 # 检查端口是否被监听 sudo netstat -tulnp | grep 5432 # PostgreSQL sudo netstat -tulnp | grep 9000 # MinIO API sudo netstat -tulnp | grep 9001 # MinIO Console sudo netstat -tulnp | grep 8000 # Django App / Nginx sudo netstat -tulnp | grep 80 # Nginx 通过这些步骤,您应该能够定位并解决部署过程中遇到的大部分问题。
Techniques
· 2025-05-26
<
>
Touch background to close