基于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() 

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/74261.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/74261.shtml
英文地址,請注明出處:http://en.pswp.cn/web/74261.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大模型-提示詞(Prompt)技巧

1、什么是提示詞&#xff1f; 提示詞&#xff08;Prompt&#xff09;是用戶發送給大語言模型的問題、指令或請求&#xff0c;用來明確地告訴模型用戶想要解決的問題或完成的任務&#xff0c;是大語言模型理解用戶需求并據此生成相關、準確回答或內容的基礎。對于大語言模型來說…

Android開發:support.v4包與AndroidX

Android中的support.v4包與AndroidX support.v4包概述 Android Support Library中的android.support.v4包是Google為保持Android應用向后兼容而提供的重要支持庫集合。它主要解決以下問題&#xff1a; API版本兼容&#xff1a;讓新版API能在舊版Android系統上使用功能增強&a…

TCP-IP模型

書接上回&#xff08;OSI通信模型&#xff09; TCP-IP協議結構 &#xff08;略講&#xff09; ARP&#xff1a;IP-->MAC RARP&#xff1a;MAC-->IP ICMP&#xff1a;控制報文信息協議&#xff0c;主要是涉及到主機就去連接路由器時控制傳輸報文&#xff08…

雪花算法生成的主鍵存在哪些問題,為什么不能使用自增ID或者UUID做MySQL的主鍵

MySQL 分布式架構中的主鍵選擇&#xff1a;自增ID、UUID與雪花算法 為什么MySQL分布式架構中不能使用自增主鍵&#xff1f; 在分布式架構中&#xff0c;自增主鍵存在以下問題&#xff1a; 主鍵沖突風險&#xff1a;多個數據庫實例同時生成自增主鍵會導致ID重復分片不均勻&am…

RapidJSON 處理 JSON(高性能 C++ 庫)(四)

第四部分:RapidJSON 處理 JSON(高性能 C++ 庫) ?? 快速掌握 JSON!文章 + 視頻雙管齊下 ?? 如果你覺得閱讀文章太慢,或者更喜歡 邊看邊學 的方式,不妨直接觀看我錄制的 RapidJSON 課程視頻!?? 視頻里會用更直觀的方式講解 RapidJSON 的核心概念、實戰技巧,并配有…

chromem-go + ollama + bge-m3 進行文檔向量嵌入和查詢

Ollama 安裝 https://ollama.com/download Ollama 運行嵌入模型 bge-m3:latest ollama run bge-m3:latestchromem-go 文檔嵌入和查詢 package mainimport ("context""fmt""runtime""github.com/philippgille/chromem-go" )func ma…

【LeetCode 題解】數據庫:180. 連續出現的數字

一、問題描述 給定一個Logs表&#xff0c;包含自增 ID 和數字字段&#xff1a; CREATE TABLE Logs (id INT PRIMARY KEY AUTO_INCREMENT,num VARCHAR(50) );要求編寫 SQL 查詢&#xff0c;找出所有至少連續出現三次的數字。例如&#xff1a; --------- | id | num | -------…

MaxEnt模型進階:基于R語言自動化與GIS空間建模的物種棲息地精準預測

生物多樣性的空間分布規律及其對環境變化的響應機制&#xff0c;是生態學與地理學研究的前沿議題。在氣候變化加劇和人類活動干擾的雙重壓力下&#xff0c;如何精準預測物種潛在分布范圍、識別關鍵環境驅動因子&#xff0c;已成為制定生物保護策略的核心科學問題。物種分布模型…

緩存雪崩解決方案:二級緩存VS隨機TTL

背景 在學習緩存雪崩的時候&#xff0c;了解到有二級緩存和隨機TTL兩個解決方案&#xff0c;但是在學習之后&#xff0c;個人認為二級緩存本質上還是利用兩層緩存的過期時間不一致來實現緩存過期時間隨機化&#xff0c;這不就是和隨機TTL一樣嗎&#xff0c;故有了這篇思考&…

Android View事件分發機制深度解析

在Android面試中&#xff0c;關于View事件分發機制的考察往往不僅限于基礎流程&#xff0c;更關注底層原理、性能優化和實際應用場景。以下是針對面試的全面回答策略&#xff1a; 一、基礎回答框架 核心三要素&#xff1a; 傳遞流程 "事件分發遵循Activity → Window →…

2829. k-avoiding 數組的最小總和

2829. k-avoiding 數組的最小總和 題目鏈接&#xff1a;2829. k-avoiding 數組的最小總和 代碼如下&#xff1a; class Solution { public:int minimumSum(int n, int k) {int m min(k / 2, n);return (m * (m 1) (k * 2 n - m - 1) * (n - m)) / 2;} };

phpStorm2021.3.3在windows系統上配置Xdebug調試

開始 首先根據PHP的版本下載并安裝對應的Xdebug擴展在phpStorm工具中找到設置添加服務添加php web page配置完信息后 首先根據PHP的版本下載并安裝對應的Xdebug擴展 我使用的是phpStudy工具&#xff0c;直接在php對應的版本中開啟xdebug擴展&#xff0c; 并在php.ini中添加如下…

LabVIEW永磁同步電機性能測試系統

開發了一種基于LabVIEW的永磁同步電機&#xff08;PMSM&#xff09;性能測試系統的設計及應用。該系統針對新能源汽車使用的電機進行穩態性能測試&#xff0c;解決了傳統測試方法成本高、效率低的問題&#xff0c;實現了測試自動化&#xff0c;提高了數據的準確性和客觀性。 ?…

谷粒商城:Redisson

目錄 Redisson 整合Redisson RLock RReadWriteLock RSemaphore RCountDownLatch 優化三級分類緩存 緩存一致性問題 雙寫模式 失效模式 臟數據解決 Redisson 提供redis分布式鎖&#xff08;Distributed locks with Redis&#xff09;的java客戶端 整合Redisson 引入 …

Linux系統調用編程

目錄 一. 理解進程和線程的概念。并在Linux系統下進行相應操作 1.1概念 1.1.1進程(Process) 1.1.2 線程(Thread) 1.2操作 1.2.1用 ps -a 命令查看系統中各進程的編號pid 1.2.2用kill 命令終止一個進程pid 二. 解釋Linux的“虛擬內存管理”&#xff0c;它與stm32中的 真…

25-智慧旅游系統(協同算法)三端

介紹 技術&#xff1a; 基于 B/S 架構 SpringBootMySQLLayuivue 環境&#xff1a; Idea mysql maven jdk1.8 node 管理端功能 首頁展示圖表&#xff1a;以數據可視化方式展示關鍵業務數據。 用戶管理&#xff1a;管理系統用戶&#xff0c;包括查看、編輯等操作。 供應商管…

【stm32--HAL庫DMA+USART+空閑中斷不定長收發數據】

串口通信-Hal庫實現不定長度收發&#xff0c;DMAUSART DMA串口STM32CUBEMX配置&#xff08;工程創建&#xff09;基礎配置時鐘配置工程配置 代碼編寫現象 DMA 在正式配置之前&#xff0c;我們先來一起簡單了解一下DMA。DMA&#xff08;Direct Memory Access&#xff0c;直接內…

沉浸式體驗測評|AI Ville:我在Web3小鎮“生活”了一周

最近&#xff0c;我在朋友的推薦下&#xff0c;體驗了 aivillebot 的項目。起初&#xff0c;我只是抱著試試看的心態&#xff0c;心想這不就是個 Web3 版的《星露谷物語》嗎&#xff1f; 但是一周下來&#xff0c;我發現這個虛擬小鎮也沒那么簡單——里面的居民不是目前端游或鏈…

FPGA學習-基于 DE2-115 板的 Verilog 分秒計數器設計與按鍵功能實現

一、核心功能設計 按鍵暫停/繼續&#xff1a;通過KEY1控制計時狀態 按鍵消抖處理&#xff1a;20ms消抖周期消除機械抖動 硬件資源分配&#xff1a;符合DE2-115開發板引腳規范 二、核心模塊實現詳解 1. 頂層模塊&#xff08;counter&#xff09; module counter(input CL…

后端開發 SpringBoot 工程模板

概述 本篇文章主要記錄如何開發一個通用的 SpringBoot 工程開發框架的項目模板&#xff0c;這樣后續需要開發項目時就可以直接開箱直用了&#xff0c;省區了很多重復步驟。 項目初始化 創建項目&#xff1a; 按照我的選項來選&#xff0c;然后點擊 create&#xff0c;等待文…