first commit
This commit is contained in:
44
old/app/services/__init__.py
Normal file
44
old/app/services/__init__.py
Normal 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'
|
||||
]
|
||||
BIN
old/app/services/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
old/app/services/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
old/app/services/__pycache__/email_processor.cpython-312.pyc
Normal file
BIN
old/app/services/__pycache__/email_processor.cpython-312.pyc
Normal file
Binary file not shown.
BIN
old/app/services/__pycache__/mail_store.cpython-312.pyc
Normal file
BIN
old/app/services/__pycache__/mail_store.cpython-312.pyc
Normal file
Binary file not shown.
BIN
old/app/services/__pycache__/smtp_server.cpython-312.pyc
Normal file
BIN
old/app/services/__pycache__/smtp_server.cpython-312.pyc
Normal file
Binary file not shown.
123
old/app/services/email_processor.py
Normal file
123
old/app/services/email_processor.py
Normal 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
|
||||
140
old/app/services/mail_store.py
Normal file
140
old/app/services/mail_store.py
Normal 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()
|
||||
230
old/app/services/smtp_server.py
Normal file
230
old/app/services/smtp_server.py
Normal 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 处理邮件时出现错误,请稍后重试'
|
||||
Reference in New Issue
Block a user