import logging import os import email from email.policy import default from sqlalchemy.orm import Session from datetime import datetime import re from ..models.domain import Domain from ..models.mailbox import Mailbox from ..models.email import Email from ..models.attachment import Attachment logger = logging.getLogger(__name__) 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, sender, recipient, message, raw_data): """ 保存一封电子邮件 参数: sender: 发件人地址 recipient: 收件人地址 message: 解析后的邮件对象 raw_data: 原始邮件数据 返回: 成功返回邮件ID,失败返回None """ # 从收件人地址中提取用户名和域名 try: address, domain_name = recipient.split('@', 1) except ValueError: logger.warning(f"无效的收件人地址格式: {recipient}") return None # 获取数据库会话 db = self.db_session_factory() try: # 检查域名是否存在且活跃 domain = db.query(Domain).filter_by(name=domain_name, active=True).first() if not domain: logger.warning(f"不支持的域名: {domain_name}") return None # 查找或创建邮箱 mailbox = db.query(Mailbox).filter_by(address=address, domain_id=domain.id).first() if not mailbox: # 自动创建新邮箱 mailbox = Mailbox( address=address, domain_id=domain.id, active=True ) db.add(mailbox) db.flush() # 获取ID但不提交 logger.info(f"已为 {recipient} 自动创建邮箱") # 提取邮件内容 subject = message.get('subject', '') # 获取文本和HTML内容 body_text = None body_html = None attachments_data = [] if message.is_multipart(): for part in message.walk(): content_type = part.get_content_type() content_disposition = part.get_content_disposition() # 处理文本内容 if content_disposition is None or content_disposition == 'inline': if content_type == 'text/plain' and not body_text: body_text = part.get_content() elif content_type == 'text/html' and not body_html: body_html = part.get_content() # 处理附件 elif content_disposition == 'attachment': filename = part.get_filename() if filename: content = part.get_payload(decode=True) if content: attachments_data.append({ 'filename': filename, 'content_type': content_type, 'data': content, 'size': len(content) }) else: # 非多部分邮件 content_type = message.get_content_type() if content_type == 'text/plain': body_text = message.get_content() elif content_type == 'text/html': body_html = message.get_content() # 创建邮件记录 email_obj = Email( mailbox_id=mailbox.id, sender=sender, recipients=recipient, subject=subject, body_text=body_text, body_html=body_html, headers={k: v for k, v in message.items()} ) # 保存邮件 db.add(email_obj) db.flush() # 获取ID但不提交 # 提取验证信息 email_obj.extract_verification_data() # 保存附件 for attachment_data in attachments_data: attachment = Attachment( email_id=email_obj.id, filename=attachment_data['filename'], content_type=attachment_data['content_type'], size=attachment_data['size'] ) db.add(attachment) db.flush() # 决定存储位置 if attachment_data['size'] > 1024 * 1024: # 大于1MB的存储到文件系统 attachments_dir = os.path.join(self.storage_path, 'attachments') attachment.save_to_filesystem(attachment_data['data'], attachments_dir) else: # 小附件直接存储在数据库 attachment.content = attachment_data['data'] # 提交所有更改 db.commit() logger.info(f"邮件已成功保存: {sender} -> {recipient}, ID: {email_obj.id}") return email_obj.id except Exception as e: db.rollback() logger.error(f"保存邮件时出错: {str(e)}") return None finally: db.close() def get_emails_for_mailbox(self, mailbox_id, limit=50, offset=0, unread_only=False): """获取指定邮箱的邮件列表""" db = self.db_session_factory() try: query = db.query(Email).filter(Email.mailbox_id == mailbox_id) if unread_only: query = query.filter(Email.read == False) total = query.count() emails = query.order_by(Email.received_at.desc()) \ .limit(limit) \ .offset(offset) \ .all() return { 'total': total, 'items': [email.to_dict() for email in emails] } except Exception as e: logger.error(f"获取邮件列表时出错: {str(e)}") return {'total': 0, 'items': []} finally: db.close() def get_email_by_id(self, email_id, mark_as_read=True): """获取指定ID的邮件详情""" db = self.db_session_factory() try: email = db.query(Email).filter(Email.id == email_id).first() if not email: return None if mark_as_read and not email.read: email.read = True email.last_read = datetime.utcnow() db.commit() # 获取附件信息 attachments = [attachment.to_dict() for attachment in email.attachments] # 构建完整响应 result = email.to_dict() result['body_text'] = email.body_text result['body_html'] = email.body_html result['attachments'] = attachments return result except Exception as e: db.rollback() logger.error(f"获取邮件详情时出错: {str(e)}") return None finally: db.close() def delete_email(self, email_id): """删除指定ID的邮件""" db = self.db_session_factory() try: email = db.query(Email).filter(Email.id == email_id).first() if not email: return False db.delete(email) db.commit() return True except Exception as e: db.rollback() logger.error(f"删除邮件时出错: {str(e)}") return False finally: db.close() 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()