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

基于python开发的邮箱合并群发工具

智能邮件群发系统

一个基于Python和PyQt5开发的智能邮件群发工具,支持Word模板和Excel数据源的自动匹配,具有现代化UI界面和友好的用户体验。
Github项目地址:https://github.com/liugang926/Auto-mail-sent.git
dist目录有编译好的exe程序,可直接使用。

功能特点

  • 支持Word文档作为邮件模板
  • 支持Excel表格作为收件人数据源
  • 智能识别并自动匹配变量
  • 自动识别姓名和邮箱列
  • 实时邮件预览功能
  • 未匹配变量智能提示
  • 可配置发送时间间隔
  • 发送进度实时显示
  • 支持中断发送任务
  • 邮箱配置测试功能
  • 现代化UI界面设计
    程序效果图

系统要求

  • Python 3.7+
  • Windows/Linux/MacOS
  • Microsoft Visual C++ 14.0 或更高版本

快速开始

  1. 克隆项目
git clone [https://github.com/liugang926/Auto-mail-sent.git]
cd email-sender
  1. 创建虚拟环境(推荐)
python -m venv venv
# Windows
venv\Scripts\activate
# Linux/Mac
source venv/bin/activate
  1. 安装依赖
pip install -r requirements.txt
  1. 运行程序
python main.py

项目结构

email_sender/
│
├── main.py                # 主程序入口
├── ui.py                  # UI界面实现
├── email_processor.py     # 邮件处理模块
├── word_reader.py         # Word文档读取
├── excel_reader.py        # Excel文件读取
├── config.ini            # 配置文件
└── requirements.txt      # 依赖包列表

主要模块功能

  • main.py: 程序入口,初始化应用
  • ui.py: 实现图形界面和用户交互
  • email_processor.py: 处理邮件发送逻辑
  • word_reader.py: 处理Word模板读取
  • excel_reader.py: 处理Excel数据读取

打包说明

环境准备

  1. 安装PyInstaller
pip install pyinstaller
  1. 确保所需资源文件存在:
  • config.ini(邮箱配置文件)
  • email.png(程序图标)
  • README.md(说明文档)

打包步骤

  1. 运行打包脚本
python setup.py
  1. 打包过程说明:
  • 清理旧的构建文件
  • 创建版本信息
  • 构建可执行文件
  • 复制必要资源
  • 清理临时文件
  1. 打包完成后,在dist目录下可以找到:
  • 邮件群发工具.exe(主程序)
  • config.ini(配置文件)
  • README.md(说明文档)
  • email.png(程序图标)

打包注意事项

  • 确保所有依赖包已正确安装
  • 确保资源文件完整
  • 需要管理员权限运行打包脚本
  • 打包过程可能需要几分钟时间

配置说明

邮箱配置 (config.ini)

[EMAIL]
sender_name = 发件人姓名
sender_email = your_email@example.com
smtp_server = smtp.example.com
smtp_port = 587
smtp_password = your_password
use_ssl = True

常见邮箱服务器设置

Gmail
smtp_server = smtp.gmail.com
smtp_port = 587
use_ssl = True

注意:需要开启两步验证并使用应用专用密码

QQ邮箱
smtp_server = smtp.qq.com
smtp_port = 465
use_ssl = True

注意:密码需要使用授权码

163邮箱
smtp_server = smtp.163.com
smtp_port = 465
use_ssl = True

Word模板变量

模板中支持以下变量:

  • {name}: 收件人姓名
  • {email}: 收件人邮箱

使用说明

1. 文件准备

Word模板要求
  • 使用 {变量名} 格式插入变量
  • 变量名需要与Excel表格的列名完全一致
  • 支持任意数量的变量

示例:

使用指南

  1. 准备工作

    • 创建Word邮件模板
    • 准备Excel收件人数据
    • 配置config.ini文件
  2. 启动程序

    python main.py
    
  3. 操作步骤

    • 选择Word模板文件
    • 选择Excel数据文件
    • 选择姓名和邮箱列
    • 填写邮件主题
    • 设置发送间隔
    • 测试邮箱配置
    • 生成预览确认
    • 开始发送

注意事项

  1. 发送前检查事项:

    • 确保网络连接正常
    • 验证邮箱配置正确
    • 检查模板格式无误
    • 确认收件人数据完整
  2. 发送建议:

    • 首次使用建议先测试配置
    • 大量发送时适当增加间隔
    • 定期检查发送状态
    • 注意邮件服务商限制

常见问题解决

1. 打包相关

  • Q: 打包失败,提示缺少依赖

    • A: 检查requirements.txt中的包是否都已安装
    • A: 尝试重新安装PyInstaller
  • Q: 运行exe文件报错

    • A: 确保所有资源文件在正确位置
    • A: 检查是否缺少Visual C++运行库

2. 发送相关

  • Q: 无法连接SMTP服务器

    • A: 检查网络连接
    • A: 验证服务器地址和端口
    • A: 确认SSL设置是否正确
  • Q: 认证失败

    • A: 检查账号密码
    • A: 确认是否需要使用授权码
    • A: 验证邮箱服务是否开启SMTP

技术支持

如遇问题,请按以下步骤处理:

  1. 检查配置文件设置
  2. 查看程序运行日志
  3. 确认网络连接状态
  4. 提交Issue或联系技术支持

版本历史

  • v1.0.0
    • 基础邮件发送功能
    • Word模板和Excel数据支持
    • 现代化UI界面
    • 邮箱配置测试
    • 打包功能支持
    • 邮件和姓名以及内容的其他变量自动匹配

许可说明

本项目仅供学习和参考使用。在使用本工具时,请遵守:

  1. 相关法律法规
  2. 邮件服务商的使用规范
  3. 用户隐私保护规定
### config.ini```bash
[EMAIL]
sender_name = 发件人姓名
sender_email = your_email@example.com
smtp_server = smtp.example.com
smtp_port = 587
smtp_password = your_password
use_ssl = True 

main.py

import sys
from PyQt5.QtWidgets import QApplication
from ui import MainWindow
import resources_rc  # 导入编译后的资源文件if __name__ == "__main__":app = QApplication(sys.argv)window = MainWindow()window.show()sys.exit(app.exec_()) 

ui.py

import os
import sys
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit, QFileDialog, QSpinBox, QTextEdit, QProgressBar, QComboBox,QGroupBox, QFormLayout, QMessageBox, QDialog,QListWidget)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
from PyQt5.QtGui import QFont, QPixmap, QIcon
from qt_material import apply_stylesheet
from email_processor import EmailSender
from word_reader import WordReader
from excel_reader import ExcelReader
import pandas as pd
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from datetime import datetime
from PyQt5.QtWidgets import QApplicationclass BlurredWidget(QWidget):"""实现毛玻璃效果的基础Widget"""def __init__(self, parent=None):super().__init__(parent)self.setAttribute(Qt.WA_TranslucentBackground)self.setStyleSheet("""QWidget {background-color: rgba(255, 255, 255, 180);border-radius: 10px;}""")class EmailPreviewWidget(QWidget):"""邮件预览窗口"""def __init__(self, parent=None):super().__init__(parent)layout = QVBoxLayout(self)self.subject_label = QLabel("主题: ")self.to_label = QLabel("收件人: ")self.content = QTextEdit()self.content.setReadOnly(True)layout.addWidget(self.subject_label)layout.addWidget(self.to_label)layout.addWidget(self.content)def update_preview(self, subject, to_name, to_email, content):self.subject_label.setText(f"主题: {subject}")self.to_label.setText(f"收件人: {to_name} <{to_email}>")self.content.setHtml(content)class EmailSenderThread(QThread):"""邮件发送线程"""progress_updated = pyqtSignal(int)email_sent = pyqtSignal(str, str)finished = pyqtSignal()error = pyqtSignal(str)def __init__(self, email_sender, data, template, variable_columns, subject, interval):super().__init__()self.email_sender = email_senderself.data = dataself.template = templateself.variable_columns = variable_columnsself.subject = subjectself.interval = intervalself.is_running = Truedef run(self):total = len(self.data)for i, row in enumerate(self.data):if not self.is_running:breaktry:# 替换所有变量content = self.templatefor col in self.variable_columns:content = content.replace(f"{{{col}}}", str(row[col]))self.email_sender.send_email(row['email'], self.subject, content)self.email_sent.emit(row['name'], row['email'])# 更新进度progress = int((i + 1) / total * 100)self.progress_updated.emit(progress)# 按指定间隔暂停self.msleep(self.interval * 1000)except Exception as e:self.error.emit(f"发送给 {row['name']} <{row['email']}> 失败: {str(e)}")self.finished.emit()def stop(self):self.is_running = Falseclass MainWindow(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("智能邮件群发系统")self.setMinimumSize(900, 700)# 设置窗口图标icon = QIcon(":/icons/email.png")  # 使用Qt资源系统self.setWindowIcon(icon)# 设置默认字体app = QApplication.instance()font = QFont("Microsoft YaHei UI", 9)  # 使用微软雅黑UI字体app.setFont(font)# 设置窗口背景self.setObjectName("mainWindow")# 初始化读取器和发送器self.word_reader = WordReader()self.excel_reader = ExcelReader()self.email_sender = EmailSender()# 数据存储self.template_content = ""self.excel_data = Noneself.name_column = ""self.email_column = ""# 先创建UIself.setup_ui()# 设置特殊按钮的ObjectName (移到UI创建之后)self.test_send_btn.setObjectName("test_send_btn")self.stop_btn.setObjectName("stop_btn")# 应用样式self.apply_blur_style()def setup_ui(self):# 主容器central_widget = QWidget()main_layout = QVBoxLayout(central_widget)main_layout.setContentsMargins(20, 20, 20, 20)main_layout.setSpacing(15)# ===== 文件选择区域 =====file_group = QGroupBox()file_group.setTitle("文件选择")file_layout = QFormLayout()file_layout.setSpacing(12)file_layout.setContentsMargins(15, 25, 15, 15)# Word模板选择word_layout = QHBoxLayout()self.word_path = QLineEdit()self.word_path.setReadOnly(True)self.word_path.setMinimumHeight(32)  # 增加高度word_browse_btn = QPushButton("浏览...")word_browse_btn.setFixedSize(90, 32)  # 固定按钮大小word_browse_btn.clicked.connect(self.browse_word)word_layout.addWidget(self.word_path)word_layout.addWidget(word_browse_btn)word_layout.setSpacing(10)# Excel数据选择excel_layout = QHBoxLayout()self.excel_path = QLineEdit()self.excel_path.setReadOnly(True)self.excel_path.setMinimumHeight(32)  # 增加高度excel_browse_btn = QPushButton("浏览...")excel_browse_btn.setFixedSize(90, 32)  # 固定按钮大小excel_browse_btn.clicked.connect(self.browse_excel)excel_layout.addWidget(self.excel_path)excel_layout.addWidget(excel_browse_btn)excel_layout.setSpacing(10)file_layout.addRow("Word模板:", word_layout)file_layout.addRow("Excel数据:", excel_layout)file_group.setLayout(file_layout)# ===== 邮件配置区域 =====config_group = QGroupBox("邮件配置")config_layout = QFormLayout()config_layout.setSpacing(12)config_layout.setContentsMargins(15, 25, 15, 15)# 变量匹配状态显示self.variables_status = QLabel("变量匹配状态")self.variables_status.setWordWrap(True)# 主题输入框self.subject_input = QLineEdit()self.subject_input.setMinimumHeight(32)# 发送间隔设置self.interval_spinbox = QSpinBox()self.interval_spinbox.setMinimumHeight(32)self.interval_spinbox.setRange(1, 600)self.interval_spinbox.setValue(30)self.interval_spinbox.setSuffix(" 秒")# 将组件添加到配置布局config_layout.addRow("变量状态:", self.variables_status)config_layout.addRow("邮件主题:", self.subject_input)config_layout.addRow("发送间隔:", self.interval_spinbox)# 测试按钮和帮助按钮test_btn_layout = QHBoxLayout()self.test_send_btn = QPushButton("测试邮箱配置")self.test_send_btn.setFixedSize(120, 36)self.test_send_btn.clicked.connect(self.test_email_config)# 添加帮助按钮help_btn = QPushButton("帮助")help_btn.setObjectName("help_btn")help_btn.setFixedSize(80, 36)help_btn.clicked.connect(self.show_help)test_btn_layout.addWidget(self.test_send_btn)test_btn_layout.addWidget(help_btn)test_btn_layout.addStretch()config_layout.addRow("", test_btn_layout)config_group.setLayout(config_layout)# ===== 预览和进度区域 =====bottom_layout = QHBoxLayout()bottom_layout.setSpacing(15)# 预览区域preview_group = QGroupBox()preview_group.setTitle("邮件预览")preview_layout = QVBoxLayout()preview_layout.setSpacing(12)preview_layout.setContentsMargins(15, 25, 15, 15)self.preview_widget = EmailPreviewWidget()preview_layout.addWidget(self.preview_widget)# 进度区域progress_group = QGroupBox()progress_group.setTitle("发送进度")progress_layout = QVBoxLayout()progress_layout.setSpacing(12)progress_layout.setContentsMargins(15, 25, 15, 15)self.progress_bar = QProgressBar()self.progress_bar.setMinimumHeight(24)self.status_label = QLabel("就绪")self.status_label.setMinimumHeight(36)btn_layout = QHBoxLayout()btn_layout.setSpacing(10)self.send_btn = QPushButton("开始发送")self.send_btn.setFixedHeight(36)self.send_btn.clicked.connect(self.start_sending)self.stop_btn = QPushButton("停止发送")self.stop_btn.setFixedHeight(36)self.stop_btn.clicked.connect(self.stop_sending)self.stop_btn.setEnabled(False)btn_layout.addWidget(self.send_btn)btn_layout.addWidget(self.stop_btn)progress_layout.addWidget(self.progress_bar)progress_layout.addWidget(self.status_label)progress_layout.addLayout(btn_layout)progress_layout.addStretch()progress_group.setLayout(progress_layout)# 设置预览和进度区域的比例bottom_layout.addWidget(preview_group, 2)bottom_layout.addWidget(progress_group, 1)# 添加所有组件到主布局main_layout.addWidget(file_group)main_layout.addWidget(config_group)main_layout.addLayout(bottom_layout, 1)self.setCentralWidget(central_widget)def apply_blur_style(self):"""应用现代化UI风格"""self.setStyleSheet("""* {font-family: "Microsoft YaHei UI", "Microsoft YaHei", "SimHei", sans-serif;}QMainWindow {background-color: #f8f9fa;}QGroupBox {background-color: white;border-radius: 8px;border: 1px solid #e9ecef;margin-top: 20px;padding: 28px 15px 15px 15px;font-weight: 500;font-size: 14px;color: #2c3e50;}QGroupBox::title {subcontrol-origin: margin;subcontrol-position: top left;left: 15px;top: 10px;padding: 0px 10px;background-color: white;color: #2c3e50;font-size: 14px;font-weight: 500;}QPushButton {background-color: #3498db;color: white;border-radius: 4px;padding: 8px 16px;border: none;font-weight: 500;font-size: 13px;min-width: 80px;min-height: 32px;}QPushButton:hover {background-color: #2980b9;}QPushButton:pressed {background-color: #2473a7;}QPushButton:disabled {background-color: #bdc3c7;}QLineEdit, QTextEdit, QComboBox, QSpinBox {background-color: white;border-radius: 4px;border: 1px solid #ced4da;padding: 6px 12px;color: #2c3e50;font-size: 13px;min-height: 32px;}QLineEdit:focus, QTextEdit:focus, QComboBox:focus, QSpinBox:focus {border: 2px solid #3498db;background-color: white;}QLabel {color: #2c3e50;font-size: 13px;padding: 4px 0;font-weight: normal;}QProgressBar {border: none;border-radius: 4px;text-align: center;background-color: #e9ecef;font-size: 12px;color: white;min-height: 24px;}QProgressBar::chunk {background-color: #2ecc71;border-radius: 4px;}/* 特殊按钮样式 */QPushButton#test_send_btn {background-color: #2ecc71;}QPushButton#test_send_btn:hover {background-color: #27ae60;}QPushButton#stop_btn {background-color: #e74c3c;}QPushButton#stop_btn:hover {background-color: #c0392b;}/* 下拉框样式 */QComboBox::drop-down {border: none;width: 30px;}QComboBox::down-arrow {image: none;border-left: 5px solid transparent;border-right: 5px solid transparent;border-top: 5px solid #495057;margin-right: 8px;}/* 帮助按钮样式 */QPushButton#help_btn {background-color: #6c757d;color: white;border-radius: 4px;padding: 8px 16px;border: none;font-weight: bold;font-size: 13px;}QPushButton#help_btn:hover {background-color: #5a6268;}QPushButton#help_btn:pressed {background-color: #545b62;}""")def browse_word(self):file_path, _ = QFileDialog.getOpenFileName(self, "选择Word模板", "", "Word文档 (*.docx *.doc)")if file_path:self.word_path.setText(file_path)try:self.template_content, self.template_variables = self.word_reader.read_template(file_path)# 显示找到的变量variables_text = "模板中的变量:\n" + "\n".join([f"{{{var}}}" for var in self.template_variables])self.variables_status.setText(variables_text)# 如果已经加载了Excel,检查变量匹配if self.excel_data:self.check_variable_matching()QMessageBox.information(self, "成功", f"Word模板加载成功!\n找到 {len(self.template_variables)} 个变量。")except Exception as e:QMessageBox.critical(self, "错误", f"无法读取Word文档: {str(e)}")def browse_excel(self):"""修改Excel文件选择处理"""file_path, _ = QFileDialog.getOpenFileName(self, "选择Excel数据文件", "", "Excel文件 (*.xlsx *.xls)")if file_path:self.excel_path.setText(file_path)try:self.excel_data, self.excel_columns = self.excel_reader.read_data(file_path)# 如果已经加载了Word模板,检查变量匹配if hasattr(self, 'template_variables'):self.check_variable_matching()QMessageBox.information(self, "成功", f"Excel数据加载成功!共{len(self.excel_data)}条记录。")except Exception as e:QMessageBox.critical(self, "错误", f"无法读取Excel文件: {str(e)}")def check_variable_matching(self):"""检查Word模板变量与Excel列的匹配情况,并自动生成预览"""if not hasattr(self, 'template_variables') or not hasattr(self, 'excel_columns'):return# 检查变量匹配matched_vars = []unmatched_vars = []self.name_column = Noneself.email_column = None# 自动识别姓名和邮箱列for col in self.excel_columns:if not self.name_column and ("姓名" in col or "名字" in col or "name" in col.lower()):self.name_column = colif not self.email_column and ("邮箱" in col or "邮件" in col or "email" in col.lower()):self.email_column = col# 检查其他变量匹配for var in self.template_variables:if var in self.excel_columns:matched_vars.append(var)else:unmatched_vars.append(var)# 更新变量状态显示status_text = "变量匹配状态:\n\n"# 显示姓名和邮箱列匹配状态if self.name_column:status_text += f"✅ 姓名列: {self.name_column}\n"else:status_text += "❌ 未找到姓名列\n"if self.email_column:status_text += f"✅ 邮箱列: {self.email_column}\n"else:status_text += "❌ 未找到邮箱列\n"status_text += "\n其他变量匹配:\n"if matched_vars:status_text += "✅ 已匹配变量:\n" + "\n".join([f"{{{var}}}" for var in matched_vars]) + "\n\n"if unmatched_vars:status_text += "❌ 未匹配变量:\n" + "\n".join([f"{{{var}}}" for var in unmatched_vars])self.variables_status.setText(status_text)# 自动生成预览self.auto_generate_preview(unmatched_vars)# 显示警告信息warnings = []if not self.name_column:warnings.append("未找到姓名列")if not self.email_column:warnings.append("未找到邮箱列")if unmatched_vars:warnings.append(f"以下变量未找到对应列:{', '.join(unmatched_vars)}")if warnings:QMessageBox.warning(self, "警告", "\n".join(warnings))def auto_generate_preview(self, unmatched_vars=None):"""自动生成预览"""if not self.template_content or not self.excel_data:returnif not self.name_column or not self.email_column:return# 获取第一条数据作为预览try:first_row = self.excel_data[0]content = self.template_content# 替换所有匹配的变量for var in self.template_variables:if var in first_row:content = content.replace(f"{{{var}}}", str(first_row[var]))elif var in unmatched_vars:# 对于未匹配的变量,保留原样显示content = content.replace(f"{{{var}}}", f"[未匹配变量: {{{var}}}]")# 获取主题(如果未输入,使用默认值)subject = self.subject_input.text() or "[请输入邮件主题]"# 更新预览self.preview_widget.update_preview(subject,first_row[self.name_column],first_row[self.email_column],content)except Exception as e:self.preview_widget.update_preview("[请输入邮件主题]","预览生成失败","预览生成失败",f"生成预览时发生错误: {str(e)}")def start_sending(self):if not self.template_content:QMessageBox.warning(self, "警告", "请先加载Word模板!")returnif not self.excel_data:QMessageBox.warning(self, "警告", "请先加载Excel数据!")returnself.name_column = self.name_columnself.email_column = self.email_columnif not self.name_column or not self.email_column:QMessageBox.warning(self, "警告", "请选择姓名和邮箱列!")returnsubject = self.subject_input.text()if not subject:QMessageBox.warning(self, "警告", "请输入邮件主题!")return# 获取选中的变量列selected_items = self.columns_list.selectedItems()selected_columns = [item.text() for item in selected_items]# 准备数据data = []for row in self.excel_data:try:item = {"name": row[self.name_column],"email": row[self.email_column],}# 添加选中的变量数据for col in selected_columns:item[col] = row[col]data.append(item)except Exception as e:print(f"跳过无效数据: {row}, 错误: {str(e)}")# 创建并启动发送线程self.sender_thread = EmailSenderThread(self.email_sender,data,self.template_content,selected_columns,  # 传递选中的列名subject,self.interval_spinbox.value())self.sender_thread.progress_updated.connect(self.update_progress)self.sender_thread.email_sent.connect(self.on_email_sent)self.sender_thread.finished.connect(self.on_sending_finished)self.sender_thread.error.connect(self.on_sending_error)self.sender_thread.start()# 更新UI状态self.send_btn.setEnabled(False)self.stop_btn.setEnabled(True)self.status_label.setText("发送中...")self.progress_bar.setValue(0)def stop_sending(self):if hasattr(self, "sender_thread") and self.sender_thread.isRunning():self.sender_thread.stop()self.status_label.setText("正在停止...")self.stop_btn.setEnabled(False)def update_progress(self, value):self.progress_bar.setValue(value)def on_email_sent(self, name, email):self.status_label.setText(f"已发送至: {name} <{email}>")def on_sending_finished(self):self.send_btn.setEnabled(True)self.stop_btn.setEnabled(False)self.status_label.setText("发送完成!")QMessageBox.information(self, "成功", "所有邮件已发送完成!")def on_sending_error(self, error_msg):self.status_label.setText(f"错误: {error_msg}")def test_email_config(self):"""测试邮箱配置是否正确"""try:# 创建测试对话框dialog = EmailTestDialog(self)dialog.exec_()except Exception as e:QMessageBox.critical(self, "错误", f"测试发送失败: {str(e)}")def show_help(self):"""显示帮助对话框"""dialog = HelpDialog(self)dialog.exec_()def update_selected_variables(self):"""更新选中的变量列表"""selected_items = self.columns_list.selectedItems()selected_vars = [item.text() for item in selected_items]# 构建变量提示文本vars_text = "已选变量:\n"if selected_vars:vars_text += "\n".join([f"{{{var}}}" for var in selected_vars])else:vars_text += "(无)"self.selected_vars_label.setText(vars_text)# 更新预览self.auto_generate_preview()class EmailTestDialog(QDialog):def __init__(self, parent=None):super().__init__(parent)self.email_sender = EmailSender()self.setup_ui()def setup_ui(self):self.setWindowTitle("邮箱配置测试")self.setMinimumWidth(400)layout = QVBoxLayout(self)# 显示当前配置信息config_group = QGroupBox("当前配置")config_layout = QFormLayout()sender_name = self.email_sender.sender_namesender_email = self.email_sender.sender_emailsmtp_server = self.email_sender.smtp_serversmtp_port = str(self.email_sender.smtp_port)use_ssl = "是" if self.email_sender.use_ssl else "否"config_layout.addRow("发件人:", QLabel(f"{sender_name} <{sender_email}>"))config_layout.addRow("SMTP服务器:", QLabel(smtp_server))config_layout.addRow("SMTP端口:", QLabel(smtp_port))config_layout.addRow("使用SSL:", QLabel(use_ssl))config_group.setLayout(config_layout)layout.addWidget(config_group)# 测试进度和结果self.status_label = QLabel("准备测试...")layout.addWidget(self.status_label)self.progress = QProgressBar()self.progress.setRange(0, 3)self.progress.setValue(0)layout.addWidget(self.progress)# 按钮btn_layout = QHBoxLayout()self.test_btn = QPushButton("开始测试")self.test_btn.clicked.connect(self.run_test)self.close_btn = QPushButton("关闭")self.close_btn.clicked.connect(self.close)btn_layout.addWidget(self.test_btn)btn_layout.addWidget(self.close_btn)layout.addLayout(btn_layout)# 开始测试QTimer.singleShot(100, self.run_test)def run_test(self):self.test_btn.setEnabled(False)self.progress.setValue(0)try:# 测试SMTP连接self.status_label.setText("正在连接SMTP服务器...")self.progress.setValue(1)if self.email_sender.use_ssl:server = smtplib.SMTP_SSL(self.email_sender.smtp_server, self.email_sender.smtp_port)else:server = smtplib.SMTP(self.email_sender.smtp_server, self.email_sender.smtp_port)server.starttls()# 测试登录self.status_label.setText("正在验证登录信息...")self.progress.setValue(2)server.login(self.email_sender.sender_email, self.email_sender.smtp_password)# 发送测试邮件self.status_label.setText("正在发送测试邮件...")self.progress.setValue(3)# 创建测试邮件msg = MIMEMultipart('alternative')msg['From'] = f"{self.email_sender.sender_name} <{self.email_sender.sender_email}>"msg['To'] = self.email_sender.sender_emailmsg['Subject'] = Header("邮箱配置测试", 'utf-8')html_content = f"""<html><body><h3>邮箱配置测试成功</h3><p>这是一封测试邮件,用于验证邮箱配置是否正确。</p><p>配置信息:</p><ul><li>发件人:{self.email_sender.sender_name} &lt;{self.email_sender.sender_email}&gt;</li><li>SMTP服务器:{self.email_sender.smtp_server}</li><li>SMTP端口:{self.email_sender.smtp_port}</li><li>SSL加密:{'是' if self.email_sender.use_ssl else '否'}</li></ul><p>发送时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p></body></html>"""html_part = MIMEText(html_content, 'html', 'utf-8')msg.attach(html_part)# 发送邮件server.send_message(msg)server.quit()# 测试完成self.status_label.setText("测试完成!配置正确,邮件已发送。")self.progress.setValue(3)QMessageBox.information(self,"测试成功",f"邮箱配置测试成功!\n已向 {self.email_sender.sender_email} 发送测试邮件。")except Exception as e:error_msg = str(e)self.status_label.setText(f"测试失败: {error_msg}")QMessageBox.critical(self,"测试失败",f"邮箱配置测试失败!\n\n错误信息:{error_msg}\n\n""请检查以下内容:\n""1. SMTP服务器地址和端口是否正确\n""2. 邮箱账号和密码是否正确\n""3. 是否已开启SMTP服务\n""4. 如果使用Gmail,是否已开启两步验证并使用应用专用密码")finally:self.test_btn.setEnabled(True) class HelpDialog(QDialog):def __init__(self, parent=None):super().__init__(parent)self.setWindowTitle("使用帮助")self.setMinimumSize(600, 400)layout = QVBoxLayout(self)# 创建文本浏览器self.help_text = QTextEdit()self.help_text.setReadOnly(True)layout.addWidget(self.help_text)# 关闭按钮close_btn = QPushButton("关闭")close_btn.clicked.connect(self.close)close_btn.setFixedWidth(100)btn_layout = QHBoxLayout()btn_layout.addStretch()btn_layout.addWidget(close_btn)layout.addLayout(btn_layout)# 加载帮助文档self.load_help_content()def load_help_content(self):help_content = """
# 智能邮件群发系统使用说明## 1. 基本使用流程### 1.1 选择Word模板
- 点击"浏览..."选择Word文档作为邮件模板
- 在Word模板中使用 {变量名} 格式插入变量
- 变量名需要与Excel表格的列名完全一致
- 系统会自动识别模板中的所有变量### 1.2 选择Excel数据
- 点击"浏览..."选择Excel文件
- 系统会自动识别姓名列和邮箱列
- 自动匹配Word模板中的其他变量
- 自动显示第一条数据的预览效果### 1.3 发送邮件
1. 填写邮件主题
2. 设置发送间隔时间(秒)
3. 确认预览效果无误后点击"开始发送"
4. 可通过进度条查看发送进度
5. 如需停止发送,点击"停止发送"## 2. 变量使用说明### 2.1 变量格式
- 在Word中使用 {变量名} 格式
- 例如:{姓名}、{部门}、{职位}
- 变量名必须与Excel列名完全一致
- 大小写敏感,请注意保持一致### 2.2 自动匹配规则
- 姓名列:自动匹配包含"姓名"、"名字"、"name"的列
- 邮箱列:自动匹配包含"邮箱"、"邮件"、"email"的列
- 其他变量:自动与Excel列名进行匹配
- 未匹配变量会在预览中显示 [未匹配变量: {变量名}]## 3. 注意事项### 3.1 文件准备
- Word模板需为.doc或.docx格式
- Excel文件需为.xls或.xlsx格式
- Excel表格第一行必须为列名
- 确保数据列名与模板变量名一致### 3.2 发送建议
- 首次使用建议先测试邮箱配置
- 发送前请仔细检查预览效果
- 建议适当设置发送间隔时间
- 大量发送时注意邮箱服务限制## 4. 常见问题### 4.1 变量未匹配
- 检查变量名与Excel列名是否完全一致
- 注意大小写、空格等是否一致
- 确认Excel文件第一行是否为列名### 4.2 邮件发送失败
- 检查邮箱配置是否正确
- 确认网络连接是否正常
- 查看是否触发发送频率限制
- 验证收件人邮箱地址是否有效### 4.3 预览显示异常
- 确认Word模板格式是否正确
- 检查Excel数据是否完整
- 验证变量格式是否规范## 5. 技术支持如遇到问题,请检查:
1. 文件格式是否正确
2. 变量名是否匹配
3. 邮箱配置是否有效
4. 网络连接是否正常如需帮助,请联系技术支持。
"""self.help_text.setMarkdown(help_content) 

email_processor.py

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
import configparser
import osclass EmailSender:"""邮件发送处理类"""def __init__(self, config_file="config.ini"):self.config = self._load_config(config_file)self.sender_name = self.config.get('EMAIL', 'sender_name')self.sender_email = self.config.get('EMAIL', 'sender_email')self.smtp_server = self.config.get('EMAIL', 'smtp_server')self.smtp_port = self.config.getint('EMAIL', 'smtp_port')self.smtp_password = self.config.get('EMAIL', 'smtp_password')self.use_ssl = self.config.getboolean('EMAIL', 'use_ssl')def _load_config(self, config_file):"""加载配置文件"""if not os.path.exists(config_file):raise FileNotFoundError(f"找不到配置文件: {config_file}")config = configparser.ConfigParser()config.read(config_file, encoding='utf-8')# 验证必要配置required_options = [('EMAIL', 'sender_name'),('EMAIL', 'sender_email'),('EMAIL', 'smtp_server'),('EMAIL', 'smtp_port'),('EMAIL', 'smtp_password')]for section, option in required_options:if not config.has_option(section, option):raise ValueError(f"配置文件中缺少必要的选项: [{section}] {option}")return configdef send_email(self, to_email, subject, html_content):"""发送邮件"""# 创建邮件msg = MIMEMultipart('alternative')msg['From'] = f"{self.sender_name} <{self.sender_email}>"msg['To'] = to_emailmsg['Subject'] = Header(subject, 'utf-8')# 添加HTML内容html_part = MIMEText(html_content, 'html', 'utf-8')msg.attach(html_part)# 连接到SMTP服务器并发送try:if self.use_ssl:server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)else:server = smtplib.SMTP(self.smtp_server, self.smtp_port)server.starttls()server.login(self.sender_email, self.smtp_password)server.send_message(msg)server.quit()except Exception as e:raise Exception(f"发送邮件失败: {str(e)}") 

word_reader.py

import docx
from docx.opc.exceptions import PackageNotFoundError
import os
import html
import re
from docx import Documentclass WordReader:"""Word文档模板读取器"""def read_template(self, file_path):"""读取Word模板并返回内容和变量列表"""if not os.path.exists(file_path):raise FileNotFoundError(f"找不到文件: {file_path}")try:doc = Document(file_path)except PackageNotFoundError:raise ValueError(f"无法打开文件,可能不是有效的Word文档: {file_path}")content = []variables = set()  # 使用集合存储找到的所有变量# 遍历所有段落for para in doc.paragraphs:content.append(para.text)# 查找所有 {变量名} 格式的变量vars = re.findall(r'\{([^}]+)\}', para.text)variables.update(vars)return '\n'.join(content), list(variables)def read_template_html(self, file_path):"""读取Word文档内容,并转为HTML格式支持{name}和{email}作为替换变量"""if not os.path.exists(file_path):raise FileNotFoundError(f"找不到文件: {file_path}")try:doc = docx.Document(file_path)except PackageNotFoundError:raise ValueError(f"无法打开文件,可能不是有效的Word文档: {file_path}")# 转换为HTMLhtml_content = []for para in doc.paragraphs:if para.text.strip():# 处理段落样式style = ""if para.style.name.startswith('Heading'):level = para.style.name[-1]html_content.append(f"<h{level}>{html.escape(para.text)}</h{level}>")else:# 处理段落中的格式formatted_text = []for run in para.runs:text = html.escape(run.text)if run.bold:text = f"<strong>{text}</strong>"if run.italic:text = f"<em>{text}</em>"if run.underline:text = f"<u>{text}</u>"formatted_text.append(text)html_content.append(f"<p>{''.join(formatted_text)}</p>")return "\n".join(html_content) 

excel_reader.py

import pandas as pd
import osclass ExcelReader:"""Excel文件读取器"""def read_data(self, file_path):"""读取Excel文件数据返回数据列表和列名列表"""if not os.path.exists(file_path):raise FileNotFoundError(f"找不到文件: {file_path}")try:# 读取Exceldf = pd.read_excel(file_path)# 验证数据帧不为空if df.empty:raise ValueError("Excel文件中没有数据")# 将数据转换为列表字典data = df.to_dict(orient='records')# 获取列名columns = df.columns.tolist()return data, columnsexcept Exception as e:raise ValueError(f"读取Excel文件时出错: {str(e)}") 

requirements.txt

python-docx==0.8.11
openpyxl==3.1.2
pandas==2.0.3
PyQt5==5.15.9
PyQt5-Qt5==5.15.2
PyQt5-sip==12.12.1
pywin32==306
jinja2==3.1.2
pyinstaller==5.13.2
pillow==10.0.0
qt-material==2.14
python-dotenv==1.0.0 
qt-material

setup.py

import PyInstaller.__main__
import os
import shutil
import sys
from datetime import datetime
import site
import PyQt5def get_pyqt_path():"""获取PyQt5安装路径"""return os.path.dirname(PyQt5.__file__)def clean_dist():"""清理dist目录"""if os.path.exists('dist'):shutil.rmtree('dist')os.makedirs('dist')def clean_build():"""清理build目录"""if os.path.exists('build'):shutil.rmtree('build')if os.path.exists('*.spec'):try:os.remove('*.spec')except:passdef copy_resources():"""复制必要的资源文件"""resource_files = ['config.ini','README.md','requirements.txt','email.png'  # 程序图标]for file in resource_files:if os.path.exists(file):shutil.copy(file, 'dist/')else:print(f"警告: {file} 文件不存在")def create_version_info():"""创建版本信息文件"""version_info = f"""
VSVersionInfo(ffi=FixedFileInfo(filevers=(1, 0, 0, 0),prodvers=(1, 0, 0, 0),mask=0x3f,flags=0x0,OS=0x40004,fileType=0x1,subtype=0x0,date=(0, 0)),kids=[StringFileInfo([StringTable(u'080404b0',[StringStruct(u'CompanyName', u'Your Company'),StringStruct(u'FileDescription', u'智能邮件群发系统'),StringStruct(u'FileVersion', u'1.0.0'),StringStruct(u'InternalName', u'email_sender'),StringStruct(u'LegalCopyright', u'Copyright (C) {datetime.now().year}'),StringStruct(u'OriginalFilename', u'邮件群发工具.exe'),StringStruct(u'ProductName', u'智能邮件群发系统'),StringStruct(u'ProductVersion', u'1.0.0')])]),VarFileInfo([VarStruct(u'Translation', [2052, 1200])])]
)
"""with open('version_info.txt', 'w', encoding='utf-8') as f:f.write(version_info)def build_executable():"""构建可执行文件"""pyqt_path = get_pyqt_path()# 构建命令列表command = ['main.py',                        # 主脚本'--name=邮件群发工具',            # 程序名称'--windowed',                     # 使用窗口模式'--onefile',                      # 打包成单个文件'--icon=email.png',              # 程序图标'--version-file=version_info.txt', # 版本信息'--add-data=config.ini;.',        # 配置文件'--add-data=README.md;.',         # 说明文档'--add-data=email.png;.',         # 图标文件'--clean',                        # 清理临时文件'--noconfirm',                    # 不询问确认'--uac-admin',                    # 请求管理员权限'--noupx',                        # 不使用UPX压缩f'--workpath=build',              # 指定构建目录f'--distpath=dist',               # 指定输出目录'--hidden-import=PyQt5.sip',      # 添加隐式导入'--hidden-import=PyQt5.QtCore','--hidden-import=PyQt5.QtGui','--hidden-import=PyQt5.QtWidgets','--hidden-import=lxml._elementpath', # 添加lxml依赖'--hidden-import=lxml.etree',       # 添加lxml依赖'--collect-all=lxml',               # 收集所有lxml相关文件'--exclude-module=PyQt6',         # 排除PyQt6'--exclude-module=PySide6',       # 排除PySide6'--exclude-module=PySide2',       # 排除PySide2]# 添加PyQt5依赖qt_path = os.path.join(os.path.dirname(PyQt5.__file__), 'Qt5')if os.path.exists(qt_path):# 添加Qt5的bin目录bin_path = os.path.join(qt_path, 'bin')if os.path.exists(bin_path):command.append(f'--add-data={bin_path};PyQt5/Qt5/bin')# 添加Qt5的plugins目录plugins_path = os.path.join(qt_path, 'plugins')if os.path.exists(plugins_path):command.append(f'--add-data={plugins_path};PyQt5/Qt5/plugins')# 添加qt_material资源try:import qt_materialqt_material_path = os.path.dirname(qt_material.__file__)resources_path = os.path.join(qt_material_path, 'resources')if os.path.exists(resources_path):command.append(f'--add-data={resources_path};qt_material/resources')except ImportError:print("警告: qt_material模块未找到")# 添加python-docx依赖try:import docxdocx_path = os.path.dirname(docx.__file__)command.append(f'--add-data={docx_path};docx')except ImportError:print("警告: python-docx模块未找到")# 运行构建命令PyInstaller.__main__.run(command)def main():"""主函数"""try:print("开始构建应用...")print("1. 清理旧文件...")clean_dist()clean_build()print("2. 创建版本信息...")create_version_info()print("3. 构建可执行文件...")os.environ['PYTHONPATH'] = os.path.dirname(os.path.abspath(__file__))  # 设置PYTHONPATHbuild_executable()print("4. 复制资源文件...")copy_resources()print("5. 清理临时文件...")if os.path.exists('version_info.txt'):os.remove('version_info.txt')print("构建完成!输出目录: dist/")except Exception as e:print(f"构建失败: {str(e)}")import tracebacktraceback.print_exc()sys.exit(1)if __name__ == "__main__":main() 

相关文章:

基于python开发的邮箱合并群发工具

智能邮件群发系统 一个基于Python和PyQt5开发的智能邮件群发工具&#xff0c;支持Word模板和Excel数据源的自动匹配&#xff0c;具有现代化UI界面和友好的用户体验。 Github项目地址&#xff1a;https://github.com/liugang926/Auto-mail-sent.git dist目录有编译好的exe程序&…...

分治算法之凸包问题

1. 算法思路 基本思想 利用分治策略解决凸包问题主要分为两大步骤&#xff1a; 分解&#xff08;Divide&#xff09;&#xff1a; 将所有点按照 x 坐标排序&#xff0c;并将点集分为左右两部分。 递归地对左右两部分分别求解凸包。 合并&#xff08;Conquer/Merge&#xf…...

OpenBMC:BmcWeb 处理http请求3 字典树查找节点

OpenBMC:BmcWeb 处理http请求2 查找路由对象-CSDN博客 findRouteByPerMethod实际上是调用了perMethod.trie.find(url);来查找路由对象的 class Trie {struct FindResult{unsigned ruleIndex;std::vector<std::string> params;};FindResult findHelper(const std::string…...

音频进阶学习二十五——脉冲响应不变法实现低通滤波器

文章目录 前言一、脉冲响应不变法1.定义2.模拟系统冲激响应的周期采样3.模拟系统和数字系统的频域响应关系1&#xff09;S域和Z域的关系2&#xff09;幅频响应的关系 4.通过有理函数设计滤波器5.总结 二、低通滤波器的设计实例1.给定数字滤波器指标2.转换模拟滤波器指标3.模拟滤…...

Linux中输入输出管理技巧

一、输入输出使用到的系统资源 1、字符设备&#xff08;Character Devices&#xff09; 什么是字符设备 字符设备是 Linux 中的一类设备&#xff0c;支持以字符为单位进行数据传输。与块设备不同&#xff0c;字符设备不需要缓 冲区&#xff0c;即数据是逐字节直接传递的。典…...

wireshark抓包工具的使用

下载地址&#xff1a;https://www.wireshark.org/#downloadLink 安装方式&#xff0c;一路next。 使用方式 第一步启动后选择你要抓包的网卡&#xff0c;ipconfig 可以查看你的默认网卡&#xff0c;我的是 以太网 双击进入。 筛选操作&#xff08;快速筛选方式&#xff09…...

javaweb自用笔记:文件上传案例、登录(统一拦截)案例

文件上传 或者说新建一个类配置好信息&#xff0c;然后到aliOssUtils里面用getter、setter方法获取到配置项 登录&#xff08;统一拦截&#xff09; 前端要json格式的数据&#xff0c;捕获到异常后前端可以显示错误&#xff08;对不起&#xff0c;操作失败&#xff0c;请联系管…...

【区块链安全 | 第十七篇】类型之引用类型(一)

文章目录 引用类型数据存储位置分配行为 数组特殊数组&#xff1a;bytes 和 string 类型bytes.concat 和 string.concat 的功能分配 memory 数组数组字面量&#xff08;Array Literals&#xff09;二维数组字面量数组成员&#xff08;Array Members&#xff09;悬空引用&#x…...

2025国内DevOps新手突围指南:从Gitee零门槛入门到工具链深度对比

对于刚接触DevOps的新手&#xff0c;推荐优先选择Gitee DevOps平台&#xff0c;其次是Jenkins和GitLab。Gitee DevOps作为国内领先的一站式研发效能平台&#xff0c;深度融合代码托管、持续集成/持续交付&#xff08;CI/CD&#xff09;、项目协作等功能&#xff0c;不仅界面简洁…...

【C语言】文件操作(2)

一、文件的随机读写 在前面我们学习了文件的顺序读写的函数&#xff0c;那么当我们要读取某个指定位置的内容的时候&#xff0c;是否只能顺序的读取到这个内容&#xff1f;还有在对文件进行输入的时候&#xff0c;需要对指定的位置进行写入&#xff0c;那么此时应该怎么办呢&a…...

将内网的IP地址映射到外网的几种方案

文章目录 1. 背景与目标2. 核心方案选型3. 方案A&#xff1a;路由器端口映射&#xff08;详细步骤&#xff09;3.1 前置条件3.2 配置流程3.3 验证访问 4. 方案B&#xff1a;云平台NAT网关配置&#xff08;以阿里云为例&#xff09;4.1 前置条件4.2 配置流程4.3 验证访问 5. 方案…...

基于深度学习的图像超分辨率技术研究与实现

一、引言 在数字图像处理领域,图像超分辨率技术一直是一个备受关注的热点话题。随着人们对图像质量要求的不断提高,如何将低分辨率图像提升到高分辨率,同时保持图像的细节和清晰度,成为了一个极具挑战性的问题。传统的图像超分辨率技术主要依赖于插值方法,如双线性插值、双…...

A股复权计算_权息数据整理

目录 前置&#xff1a; 步骤&#xff1a; 1 以通达信为参照 2 从优矿获取所需数据 2.1 股票配股信息 2.2 股票分红信息 2.3 股票拆股信息 3 合并数据&#xff0c;制成权息数据表 权息数据截止20250329.7z 视频 前置&#xff1a; 1 本系列将以 “A股复权计算_” 开头…...

如何进行Prompt调优?

一. 神奇的咒语 在输入prompt前&#xff0c;加入下面这一段“神奇的咒语”&#xff0c;中文或者英文&#xff0c;就能帮你优化提示词。 I want you to become my Expert Prompt Creator. Your goal is to help me craft the best possible prompt for my needs. The prompt yo…...

Git Tag 详解:版本管理与实战指南

文章目录 Git Tag 详解&#xff1a;版本管理与实战指南1. Git Tag 的类型2. Git Tag 的常见操作(1) 创建标签① 创建轻量标签② 创建附注标签③ 给指定的提交打标签 (2) 查看标签(3) 删除标签(4) 推送标签到远程① 推送单个标签② 推送所有标签 (5) 删除远程标签 3. 使用 Tag 的…...

从零开始打造HTML5拼图游戏:一个Canvas实战项目

从零开始打造HTML5拼图游戏&#xff1a;一个Canvas实战项目 先看效果&#xff1a; 你是否曾经被那些精美的网页拼图游戏所吸引&#xff1f;用 HTML5 的 Canvas 技术&#xff0c;从零开始&#xff0c;教你怎么画图、处理鼠标事件&#xff0c;还有游戏的核心逻辑&#xff0c…...

【数据分享】2000—2024年我国乡镇的逐年归一化植被指数(NDVI)数据(年最大值/Shp/Excel格式)

之前我们分享过2000-2024年我国逐年的归一化植被指数&#xff08;NDVI&#xff09;栅格数据&#xff0c;该逐年数据是取的当年月归一化植被指数&#xff08;NDVI&#xff09;的年最大值&#xff01;另外&#xff0c;我们基于此年度栅格数据按照行政区划取平均值&#xff0c;得到…...

设计模式 Day 2:工厂方法模式(Factory Method Pattern)详解

继 Day 1 学习了单例模式之后&#xff0c;今天我们继续深入对象创建型设计模式——工厂方法模式&#xff08;Factory Method&#xff09;。工厂方法模式为对象创建提供了更大的灵活性和扩展性&#xff0c;是实际开发中使用频率极高的一种设计模式。 一方面&#xff0c;我们将简…...

TensorFlow SegFormer 实战训练代码解析

一、SegFormer 实战训练代码解析 SegFormer 是一个轻量级、高效的语义分割模型&#xff0c;结合了 ViT&#xff08;视觉 Transformer&#xff09; 和 CNN 的高效特征提取能力&#xff0c;适用于边缘 AI 设备&#xff08;如 Jetson Orin&#xff09;。下面&#xff0c;我们深入…...

51c嵌入式~单片机~合集7~※

我自己的原文哦~ https://blog.51cto.com/whaosoft/13692314 一、芯片工作的心脏--晶振 在振荡器中采用一个特殊的元件——石英晶体&#xff0c;它可以产生频率高度稳定的交流信号&#xff0c;这种采用石英晶体的振荡器称为晶体振荡器&#xff0c;简称晶振。 制作方法 …...

私有知识库 Coco AI 实战(一):Linux 平台部署

Coco AI 是一个完全开源、跨平台的统一搜索和生产力工具&#xff0c;能够连接各种数据源&#xff0c;包括应用程序、文件、Google Drive、Notion、Yuque、Hugo 等&#xff0c;帮助用户快速智能地访问他们的信息。通过集成 DeepSeek 等大型模型&#xff0c;Coco AI 实现了智能个…...

大模型高质量rag构建:A Cheat Sheet and Some Recipes For Building Advanced RAG

原文&#xff1a;A Cheat Sheet and Some Recipes For Building Advanced RAG — LlamaIndex - Build Knowledge Assistants over your Enterprise DataLlamaIndex is a simple, flexible framework for building knowledge assistants using LLMs connected to your enterpris…...

LeetCode 78.子集

问题描述 给定一个不含重复元素的整数数组 nums&#xff0c;返回其所有可能的子集&#xff08;幂集&#xff09;。 示例 输入&#xff1a; nums [1,2,3] 输出&#xff1a; [ [], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3] ]解法&#xff1a;回溯算法 回溯是一种 暴力…...

变量(Variable)

免责声明 如有异议请在评论区友好交流&#xff0c;或者私信 内容纯属个人见解&#xff0c;仅供学习参考 如若从事非法行业请勿食用 如有雷同纯属巧合 版权问题请直接联系本人进行删改 前言 提示&#xff1a;从小学解方程变量x&#xff0c;到中学阶段函数自变量x因变量y&…...

【STM32】最后一刷-江科大Flash闪存-学习笔记

FLASH简介 STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过闪存存储器接口&#xff08;外设&#xff09;可以对程序存储器和选项字节进行擦除和编程&#xff0c;&#xff08;系统存储器用于存储原厂写入的BootLoader程序&#xff0c;用于串口…...

Dify 深度集成 MCP实现灾害应急响应

一、架构设计 1.1 分层架构 #mermaid-svg-5dVNjmixTX17cCfg {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-5dVNjmixTX17cCfg .error-icon{fill:#552222;}#mermaid-svg-5dVNjmixTX17cCfg .error-text{fill:#552222…...

2025 年上半年软考信息系统项目管理师备考计划

2025 年上半年软考信息系统项目管理师备考计划​ 2025 年上半年软考信息系统项目管理师考试时间为 5 月 24 日 - 27 日&#xff0c;从现在开始备考&#xff0c;需合理规划&#xff0c;高效学习。以下为详细备考计划&#xff1a;​ 一、基础学习阶段&#xff08;现在 - 4 月上…...

Scikit-learn使用指南

1. Scikit-learn 简介 定义&#xff1a; Scikit-learn&#xff08;简称 sklearn&#xff09;是基于 Python 的开源机器学习库&#xff0c;提供了一系列算法和工具&#xff0c;用于数据挖掘、数据预处理、分类、回归、聚类、模型评估等任务。特点&#xff1a; 基于 NumPy、SciP…...

学习大模型需要具备哪些技术、知识和基础

数学基础 概率论与数理统计&#xff1a;用于理解模型中的不确定性、概率分布&#xff0c;以及进行数据的统计分析、评估模型的性能等。例如&#xff0c;通过概率分布来描述模型预测结果的可信度&#xff0c;利用统计方法对数据进行抽样、估计模型的参数等。线性代数&#xff1…...

十五届蓝桥杯省赛Java B组(持续更新..)

目录 十五届蓝桥杯省赛Java B组第一题&#xff1a;报数第二题&#xff1a;类斐波那契数第三题&#xff1a;分布式队列第四题&#xff1a;食堂第五题&#xff1a;最优分组第六题&#xff1a;星际旅行第七题&#xff1a;LITS游戏第八题&#xff1a;拼十字 十五届蓝桥杯省赛Java B…...

Flink SQL Client bug ---datagen connector

原始sql语句如下 CREATE TABLE test_source (event_time TIMESTAMP(3), -- 事件时间&#xff08;精确到毫秒&#xff09;click INT, -- 随机数值字段WATERMARK FOR event_time AS event_time - INTERVAL 5 SECOND WITH (connector datagen, …...

股指期货的多头套期保值是什么意思?

多头套期保值&#xff0c;又叫“买入套期保值”&#xff0c;听起来很复杂&#xff0c;其实很简单。它的核心就是“提前锁定价格&#xff0c;防止未来价格上涨”。 举个例子&#xff0c;假设你是一家工厂的老板&#xff0c;过几个月要买一批原材料。现在原材料的价格是100元/吨…...

hadoop集群配置-scp命令

scp 命令用于在不同主机之间复制文件或目录&#xff0c;在Hadoop集群配置中常用于将配置文件或相关资源分发到各个节点。以下是 scp 命令的基本用法和在Hadoop集群配置中的示例&#xff1a; 基本语法 scp [-r] [源文件或目录] [目标用户目标主机:目标路径] - -r &#xff1a;…...

Redis 源码硬核解析系列专题 - 结语:从源码看Redis的设计哲学

1. 引言 通过前七篇的源码解析,我们从Redis的整体架构、核心数据结构、事件驱动模型,到内存管理、持久化、主从复制与集群模式,逐步揭开了Redis高性能与简洁性的秘密。本篇将总结这些技术细节,提炼Redis的设计哲学,并探讨如何将源码学习成果应用到实际开发中。 2. Redis的…...

解决QSharedPointer栈变量的崩溃问题

目录 参考崩溃代码现象 解决 参考 QSharedPointer的陷阱 qt中的共享指针&#xff0c;QSharedPointer类 崩溃 代码 #include <QtCore/QCoreApplication> #include <QDebug> #include <QSharedPointer>class MyClass { public:void doSomething() {qDebug…...

Lambda 表达式是什么以及如何使用

目录 &#x1f4cc; Kotlin 的 Lambda 表达式详解 &#x1f3af; 什么是 Lambda 表达式&#xff1f; &#x1f525; 1. Lambda 表达式的基本语法 ✅ 示例 1&#xff1a;Lambda 基本写法 ✅ 示例 2&#xff1a;使用 it 关键字&#xff08;单参数简化&#xff09; ✅ 示例 3…...

C++自定义迭代器

实现自己的迭代器 最近在写数据结构&#xff0c;使用类模板实现&#xff0c;碰到了一些问题&#xff0c;其中有一个就是遍历的问题&#xff0c;查阅资料最后实现了自己的迭代器&#xff0c;让我实现的数据结构能像STL一样进行for循环遍历。 类的构成 #include <stdexcept…...

PWA 中的 Service Worker:如何实现应用离线功能

前言 在当今快速发展的互联网时代&#xff0c;Progressive Web App (PWA) 正在逐步成为现代 Web 开发的主流选择。PWA 将 Web 应用和原生应用的最佳特性相结合&#xff0c;提供了丰富的用户体验。而在 PWA 的众多技术中&#xff0c;Service Worker 无疑是其核心组件之一。 作…...

dockerfile制作镜像

1.docker pull centos:centos7 2.dockerfile内容 FROM centos:centos7 #指定镜像维护的作者和邮箱 MAINTAINER csdn< **********qq.com #设置环境变量mypath ENV MYPATH /usr/local #设置进入容器的默认目录是/usr/local WORKDIR $MYPATH # 下载并替换 CentOS 镜像源 RUN …...

网络空间安全(46)DevSecOps概述

一、定义与核心理念 DevSecOps是“开发&#xff08;Development&#xff09;、安全&#xff08;Security&#xff09;和运营&#xff08;Operations&#xff09;”的结合&#xff0c;它将安全实践融入软件开发生命周期的每个阶段&#xff0c;从需求、设计、开发、测试到部署和运…...

LeetCode 211

实现支持通配符的字典树&#xff08;Trie&#xff09;&#xff1a;解决单词匹配问题 一、问题描述 我们需要设计一个数据结构&#xff0c;支持以下功能&#xff1a; 添加新单词搜索字符串是否与任何已添加的单词匹配&#xff0c;其中搜索字符串可能包含通配符 .&#xff08;…...

Docker Compose 启动jar包项目

参考文章安装Docker和Docker Compose 点击跳转 配置 创建一个文件夹存放项目例如mydata mkdir /mydata上传jar包 假设我的jar包名称为goudan.jar 编写dockerfile文件 vim app-dockerfile按键盘上的i进行编辑 # 使用jdk8 FROM openjdk:8-jre# 设置时区 上海 ENV TZAsia/Sh…...

利用deepseek直接调用其他文生图网站生成图片

这次deepseek输入中文后&#xff0c;其实翻译英文后&#xff0c;是可以丢到比如pollinations.这个网站&#xff0c;来生成图片&#xff0c;用法如下&#xff1a; 你是一个图像生成助手&#xff0c;请根据我的简单描述&#xff0c;想象并详细描述一幅完整的画面。 然后将你的详…...

远程装个Jupyter-AI协作笔记本,Jupyter容器镜像版本怎么选?安装部署教程

通过Docker下载Jupyter镜像部署&#xff0c;输入jupyter会发现 有几个版本&#xff0c;不知道怎么选&#xff1f;这几个版本有什么差别&#xff1f; 常见版本有&#xff1a; jupyter/base-notebookjupyter/minimal-notebookjupyter/scipy-notebookjupyter/datascience-notebo…...

11. 盛最多水的容器

leetcode Hot 100系列 文章目录 一、核心操作二、外层配合操作三、核心模式代码总结 一、核心操作 最左右两边逐步往中间走&#xff0c;每次在左右中选取小的一个或–记录最大面积 提示&#xff1a;小白个人理解&#xff0c;如有错误敬请谅解&#xff01; 二、外层配合操作…...

Selenium Web自动化如何快速又准确的定位元素路径,强调一遍是元素路径

如果文章对你有用&#xff0c;请给个赞&#xff01; 匹配的ChromeDriver和浏览器版本是更好完成自动化的基础&#xff0c;可以从这里去下载驱动程序&#xff1a; 最全ChromeDriver下载含win linux mac 最新版本134.0.6998.165 持续更新..._chromedriver 134-CSDN博客 如果你问…...

Kotlin 基础语法解析

详细的 Kotlin 基础语法解析&#xff0c;结合概念说明和实用场景&#xff1a; --- ### **一、变量与常量** #### **1. 变量类型** - **val**&#xff08;不可变变量&#xff09;&#xff1a;声明后不可重新赋值&#xff0c;类似 Java 的 final。 kotlin val name "Kotl…...

html 列表循环滚动,动态初始化字段数据

html <div class"layui-row"><div class"layui-col-md4"><div class"boxall"><div class"alltitle">超时菜品排行</div><div class"marquee-container"><div class"scroll-…...

【大模型基础_毛玉仁】5.4 定位编辑法:ROME

目录 5.4 定位编辑法&#xff1a;ROME5.4.1 知识存储位置1&#xff09;因果跟踪实验2&#xff09;阻断实验 5.4.2 知识存储机制5.4.3 精准知识编辑1&#xff09;确定键向量2&#xff09;优化值向量3&#xff09;插入知识 5.4 定位编辑法&#xff1a;ROME 定位编辑&#xff1a;…...

Using SAP an introduction for beginners and business users

Using SAP an introduction for beginners and business users...