import smtplib import re import asyncio from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from email.utils import formataddr from concurrent.futures import ThreadPoolExecutor from app.core.config import settings from app.core.error_codes import BizCode from app.core.exceptions import BusinessException from app.core.logging_config import get_business_logger business_logger = get_business_logger() def _send_email_sync(to_email: str, subject: str, html_content: str, text_content: str = None): """同步发送邮件""" smtp_server = settings.SMTP_SERVER smtp_port = settings.SMTP_PORT smtp_user = settings.SMTP_USER smtp_password = settings.SMTP_PASSWORD if not smtp_server or not smtp_user or not smtp_password: raise BusinessException("邮件服务未配置", code=BizCode.SERVICE_UNAVAILABLE) msg = MIMEMultipart('alternative') msg['Subject'] = Header(subject, "utf-8") from_name = "MemoryBear系统" msg['From'] = formataddr((Header(from_name, 'utf-8').encode(), smtp_user)) msg['To'] = Header(to_email, "utf-8") if not text_content: text_content = html_content.replace('
', '\n').replace('

', '\n').replace('

', '\n') text_content = re.sub(r'<.*?>', '', text_content) text_part = MIMEText(text_content, 'plain', 'utf-8') msg.attach(text_part) html_part = MIMEText(html_content, 'html', 'utf-8') msg.attach(html_part) if smtp_port == 465: with smtplib.SMTP_SSL(smtp_server, smtp_port, timeout=10) as server: server.login(smtp_user, smtp_password) server.send_message(msg) else: with smtplib.SMTP(smtp_server, smtp_port, timeout=10) as server: server.starttls() server.login(smtp_user, smtp_password) server.send_message(msg) async def send_email(to_email: str, subject: str, html_content: str, text_content: str = None): """异步发送邮件""" to_email = to_email.strip() if not to_email or not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', to_email): err_msg = f"收件人邮箱格式无效: {to_email}" business_logger.error(err_msg) raise BusinessException(err_msg, code=BizCode.INVALID_PARAMETER) try: loop = asyncio.get_event_loop() with ThreadPoolExecutor() as executor: await loop.run_in_executor( executor, _send_email_sync, to_email, subject, html_content, text_content ) business_logger.info(f"邮件发送成功: {to_email}") except smtplib.SMTPAuthenticationError: err_msg = "SMTP认证失败,请检查SMTP账号/密码是否正确" business_logger.error(f"邮件发送失败: {to_email} - {err_msg}") raise BusinessException(err_msg, code=BizCode.UNAUTHORIZED) except smtplib.SMTPConnectError: err_msg = "SMTP服务器连接失败,请检查服务器地址/端口是否正确" business_logger.error(f"邮件发送失败: {to_email} - {err_msg}") raise BusinessException(err_msg, code=BizCode.SERVICE_UNAVAILABLE) except TimeoutError: err_msg = "邮件发送超时,请检查SMTP服务器配置" business_logger.error(f"邮件发送失败: {to_email} - {err_msg}") raise BusinessException(err_msg, code=BizCode.BAD_REQUEST) except Exception as e: business_logger.error(f"邮件发送失败: {to_email} - {str(e)}") raise BusinessException(f"邮件发送失败: {str(e)}", code=BizCode.SERVICE_UNAVAILABLE)