first commit

This commit is contained in:
huangzhenpc
2025-02-26 18:29:10 +08:00
parent 5d21c9468c
commit a8d1b41381
38 changed files with 2878 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
# 服务层初始化文件
# 这里将导入所有服务模块以便于统一调用
from .smtp_server import SMTPServer
from .email_processor import EmailProcessor
from .mail_store import MailStore
# 全局服务实例
_smtp_server = None
_email_processor = None
_mail_store = None
def register_smtp_server(instance):
"""注册SMTP服务器实例"""
global _smtp_server
_smtp_server = instance
def register_email_processor(instance):
"""注册邮件处理器实例"""
global _email_processor
_email_processor = instance
def register_mail_store(instance):
"""注册邮件存储实例"""
global _mail_store
_mail_store = instance
def get_smtp_server():
"""获取SMTP服务器实例"""
return _smtp_server
def get_email_processor():
"""获取邮件处理器实例"""
return _email_processor
def get_mail_store():
"""获取邮件存储实例"""
return _mail_store
__all__ = [
'SMTPServer', 'EmailProcessor', 'MailStore',
'register_smtp_server', 'register_email_processor', 'register_mail_store',
'get_smtp_server', 'get_email_processor', 'get_mail_store'
]

Binary file not shown.

View File

@@ -0,0 +1,123 @@
import logging
import re
import threading
import time
from queue import Queue
logger = logging.getLogger(__name__)
class EmailProcessor:
"""邮件处理器,负责处理邮件并提取验证信息"""
def __init__(self, mail_store):
"""
初始化邮件处理器
参数:
mail_store: 邮件存储服务实例
"""
self.mail_store = mail_store
self.processing_queue = Queue()
self.is_running = False
self.worker_thread = None
def start(self):
"""启动邮件处理器"""
if self.is_running:
logger.warning("邮件处理器已在运行")
return False
self.is_running = True
self.worker_thread = threading.Thread(
target=self._processing_worker,
daemon=True
)
self.worker_thread.start()
logger.info("邮件处理器已启动")
return True
def stop(self):
"""停止邮件处理器"""
if not self.is_running:
logger.warning("邮件处理器未在运行")
return False
self.is_running = False
if self.worker_thread:
self.worker_thread.join(timeout=5.0)
self.worker_thread = None
logger.info("邮件处理器已停止")
return True
def queue_email_for_processing(self, email_id):
"""将邮件添加到处理队列"""
self.processing_queue.put(email_id)
return True
def _processing_worker(self):
"""处理队列中的邮件的工作线程"""
while self.is_running:
try:
# 获取队列中的邮件最多等待1秒
try:
email_id = self.processing_queue.get(timeout=1.0)
except:
continue
# 处理邮件
self._process_email(email_id)
# 标记任务完成
self.processing_queue.task_done()
except Exception as e:
logger.error(f"处理邮件时出错: {str(e)}")
def _process_email(self, email_id):
"""处理单个邮件,提取验证码和链接"""
# 从邮件存储获取邮件
email_data = self.mail_store.get_email_by_id(email_id, mark_as_read=False)
if not email_data:
logger.warning(f"找不到ID为 {email_id} 的邮件")
return False
# 提取验证码和链接已经在Email模型的extract_verification_data方法中实现
# 这里可以添加更复杂的提取逻辑或后处理
logger.info(f"邮件 {email_id} 处理完成")
return True
@staticmethod
def extract_verification_code(content):
"""从内容中提取验证码"""
code_patterns = [
r'\b[A-Z0-9]{4,8}\b', # 基本验证码格式
r'验证码[:]\s*([A-Z0-9]{4,8})',
r'验证码是[:]\s*([A-Z0-9]{4,8})',
r'code[:]\s*([A-Z0-9]{4,8})',
r'码[:]\s*(\d{4,8})' # 纯数字验证码
]
for pattern in code_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
if matches:
return matches[0]
return None
@staticmethod
def extract_verification_link(content):
"""从内容中提取验证链接"""
link_patterns = [
r'(https?://\S+(?:verify|confirm|activate)\S+)',
r'(https?://\S+(?:token|auth|account)\S+)',
r'href\s*=\s*["\']([^"\']+(?:verify|confirm|activate)[^"\']*)["\']'
]
for pattern in link_patterns:
matches = re.findall(pattern, content, re.IGNORECASE)
if matches:
return matches[0]
return None

View File

@@ -0,0 +1,140 @@
import logging
import os
import email
from email.policy import default
from sqlalchemy.orm import Session
from datetime import datetime
import re
import redis
from ..models.domain import Domain
from ..models.mailbox import Mailbox
from ..models.email import Email
from ..models.attachment import Attachment
logging.basicConfig(
level=logging.DEBUG, # 设置日志级别为DEBUG
format='%(asctime)s - %(levelname)s - %(message)s', # 日志格式
filename='app.log', # 日志文件名
filemode='a' # 追加模式
)
logger = logging.getLogger(__name__)
# 连接到 Redis
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
class MailStore:
"""邮件存储服务,负责保存和检索邮件"""
def __init__(self, db_session_factory, storage_path=None):
"""
初始化邮件存储服务
参数:
db_session_factory: 数据库会话工厂函数
storage_path: 附件存储路径
"""
self.db_session_factory = db_session_factory
self.storage_path = storage_path or os.path.join(os.getcwd(), 'email_data')
# 确保存储目录存在
if not os.path.exists(self.storage_path):
os.makedirs(self.storage_path)
async def save_email(self, message, sender, recipients, raw_data=None):
logging.info(f"开始保存邮件: 发件人={sender}, 收件人={recipients}")
# 处理收件人列表
if recipients is None:
logging.error("收件人列表为None无法保存邮件")
return False, "收件人列表为None"
elif isinstance(recipients, list):
recipients_list = recipients # 如果是列表,直接使用
else:
recipients_list = recipients.split(",") # 假设是以逗号分隔的字符串
# 确保收件人列表不为空
if not recipients_list:
logging.error("收件人列表为空,无法保存邮件")
return False, "收件人列表为空"
# 解析邮件内容
email_subject = message.subject if message.subject else "无主题"
body_text = message.get_body(preferencelist=('plain')).get_content()
received_at = datetime.now().isoformat()
# 存储邮件到 Redis
for recipient in recipients_list:
email_id = f"email:{recipient}:{received_at}"
redis_client.hset(email_id, mapping={
"subject": email_subject,
"sender": sender,
"recipients": recipients,
"body": body_text,
"received_at": received_at
})
logging.info(f"邮件已保存到 Redis: {email_id}")
return True, "邮件已保存到 Redis"
def get_emails_for_mailbox(self, mailbox_id, limit=50, offset=0, unread_only=False):
"""获取指定邮箱的邮件列表"""
try:
# 从 Redis 获取邮件
keys = redis_client.keys(f"email:*")
emails = []
for key in keys:
email_data = redis_client.hgetall(key)
emails.append(email_data)
total = len(emails)
return {
'total': total,
'items': emails[offset:offset + limit]
}
except Exception as e:
logger.error(f"获取邮件列表时出错: {str(e)}")
return {'total': 0, 'items': []}
def get_email_by_id(self, email_id):
"""获取指定ID的邮件详情"""
try:
email_data = redis_client.hgetall(f"email:{email_id}")
if not email_data:
return None
return email_data
except Exception as e:
logger.error(f"获取邮件详情时出错: {str(e)}")
return None
def delete_email(self, email_id):
"""删除指定ID的邮件"""
try:
redis_client.delete(f"email:{email_id}")
return True
except Exception as e:
logger.error(f"删除邮件时出错: {str(e)}")
return False
def get_attachment_content(self, attachment_id):
"""获取附件内容"""
db = self.db_session_factory()
try:
attachment = db.query(Attachment).filter(Attachment.id == attachment_id).first()
if not attachment:
return None
content = attachment.get_content()
return {
'content': content,
'filename': attachment.filename,
'content_type': attachment.content_type
}
except Exception as e:
logger.error(f"获取附件内容时出错: {str(e)}")
return None
finally:
db.close()

View File

@@ -0,0 +1,230 @@
import asyncio
import logging
import email
import platform
from email.policy import default
from aiosmtpd.controller import Controller
from aiosmtpd.smtp import SMTP as SMTPProtocol
from aiosmtpd.handlers import Message
import os
import sys
import threading
import traceback
from ..models.domain import Domain
from ..models.mailbox import Mailbox
from ..utils import email_parser
from ..models import Email
from aiosmtpd.smtp import SMTP, Session, Envelope
logger = logging.getLogger(__name__)
# 检测是否Windows环境
IS_WINDOWS = platform.system().lower() == 'windows'
class EmailHandler(Message):
"""处理接收的电子邮件"""
def __init__(self, mail_store):
super().__init__()
self.mail_store = mail_store
def handle_message(self, message):
"""处理邮件消息这是Message类的抽象方法必须实现"""
# 这个方法在异步DATA处理完成后被调用但我们的邮件处理逻辑已经在handle_DATA中实现
# 所以这里只是一个空实现
return
async def handle_DATA(self, server, session, envelope):
"""处理邮件数据"""
try:
logging.info(f"收到邮件: 发件人={envelope.mail_from}, 收件人={envelope.rcpt_tos}")
logging.debug(f"邮件内容: {envelope.content.decode('utf-8', errors='replace')}")
# 检查收件人列表是否有效
if not envelope.rcpt_tos:
logging.error("收件人列表无效,无法保存邮件")
return '550 收件人列表无效'
elif not isinstance(envelope.rcpt_tos, (list, str)):
logging.error("收件人列表格式不正确,无法保存邮件")
return '550 收件人列表格式不正确'
# 保存原始邮件数据
raw_data = envelope.content.decode('utf-8', errors='replace')
# 解析邮件数据
message = email_parser.Parser(policy=default).parsestr(raw_data)
subject = message.get('Subject', '')
logging.info(f"邮件主题: {subject}")
# 记录邮件结构和内容
logging.debug(f"邮件结构: is_multipart={message.is_multipart()}")
if message.is_multipart():
logging.debug(f"多部分邮件: 部分数量={len(list(message.walk()))}")
for i, part in enumerate(message.walk()):
content_type = part.get_content_type()
logging.debug(f"部分 {i+1}: 内容类型={content_type}")
# 使用邮件存储服务保存邮件
success, error_msg = await self.mail_store.save_email(
message,
envelope.mail_from,
envelope.rcpt_tos,
raw_data=raw_data
)
if success:
logging.info(f"邮件保存成功: 来自 {envelope.mail_from} 发送给 {envelope.rcpt_tos}")
return '250 消息接收完成'
else:
logging.error(f"邮件保存失败#server: {error_msg}")
# 即使保存失败,也返回成功状态码,避免邮件服务器重试
return '250 消息已收到'
except Exception as e:
logging.error(f"处理邮件时出错: {str(e)}")
traceback.print_exc()
return '451 处理邮件时出现错误,请稍后重试'
# 为Windows环境自定义SMTP控制器
if IS_WINDOWS:
class WindowsSafeController(Controller):
"""Windows环境安全的Controller跳过连接测试"""
def _trigger_server(self):
"""Windows环境下跳过SMTP服务器自检连接测试"""
# 在Windows环境下我们跳过自检连接测试
logger.info("Windows环境: 跳过SMTP服务器连接自检")
return
class SMTPServer:
"""SMTP服务器实现"""
def __init__(self, host='0.0.0.0', port=25, mail_store=None):
self.host = host
self.port = port
self.mail_store = mail_store
self.controller = None
self.server_thread = None
def start(self):
"""启动SMTP服务器"""
if self.controller:
logger.warning("SMTP服务器已经在运行")
return
try:
handler = EmailHandler(self.mail_store)
# 根据环境选择适当的Controller
if IS_WINDOWS:
# Windows环境使用自定义Controller
logger.info(f"Windows环境: 使用自定义Controller启动SMTP服务器 {self.host}:{self.port}")
self.controller = WindowsSafeController(
handler,
hostname=self.host,
port=self.port
)
else:
# 非Windows环境使用标准Controller
self.controller = Controller(
handler,
hostname=self.host,
port=self.port
)
# 在单独的线程中启动服务器
self.server_thread = threading.Thread(
target=self.controller.start,
daemon=True
)
self.server_thread.start()
logger.info(f"SMTP服务器已启动在 {self.host}:{self.port}")
return True
except Exception as e:
logger.error(f"启动SMTP服务器失败: {str(e)}")
return False
def stop(self):
"""停止SMTP服务器"""
if not self.controller:
logger.warning("SMTP服务器没有运行")
return
try:
self.controller.stop()
self.controller = None
self.server_thread = None
logger.info("SMTP服务器已停止")
return True
except Exception as e:
logger.error(f"停止SMTP服务器失败: {str(e)}")
return False
class MailHandler:
"""邮件处理器用于处理接收的SMTP邮件"""
def __init__(self, mail_store):
self.mail_store = mail_store
async def handle_EHLO(self, server, session, envelope, hostname):
session.host_name = hostname
return '250-AUTH PLAIN\n250-SIZE 52428800\n250 SMTPUTF8'
async def handle_MAIL(self, server, session, envelope, address, mail_options=None):
if not mail_options:
mail_options = []
envelope.mail_from = address
envelope.mail_options.extend(mail_options)
return '250 OK'
async def handle_RCPT(self, server, session, envelope, address, rcpt_options=None):
if not rcpt_options:
rcpt_options = []
envelope.rcpt_tos.append(address)
envelope.rcpt_options.extend(rcpt_options)
return '250 OK'
async def handle_DATA(self, server, session, envelope):
"""处理接收到的邮件数据"""
try:
logging.info(f"收到邮件: 发件人={envelope.mail_from}, 收件人={envelope.rcpt_tos}")
# 保存原始邮件数据
raw_data = envelope.content.decode('utf-8', errors='replace')
# 解析邮件数据
message = email_parser.parsestr(raw_data)
subject = message.get('Subject', '')
logging.info(f"邮件主题: {subject}")
# 记录邮件结构和内容
logging.debug(f"邮件结构: is_multipart={message.is_multipart()}")
if message.is_multipart():
logging.debug(f"多部分邮件: 部分数量={len(list(message.walk()))}")
for i, part in enumerate(message.walk()):
content_type = part.get_content_type()
logging.debug(f"部分 {i+1}: 内容类型={content_type}")
# 使用邮件存储服务保存邮件
success, error_msg = await self.mail_store.save_email(
message,
envelope.mail_from,
envelope.rcpt_tos,
raw_data=raw_data
)
if success:
logging.info(f"邮件保存成功: 来自 {envelope.mail_from} 发送给 {envelope.rcpt_tos}")
return '250 消息接收完成'
else:
logging.error(f"邮件保存失败: {error_msg}")
return '451 处理邮件时出现错误,请稍后重试'
except Exception as e:
logging.error(f"处理邮件时出错: {str(e)}")
traceback.print_exc()
return '451 处理邮件时出现错误,请稍后重试'