crypto.ai/cryptoai/utils/email_service.py
2025-05-06 16:37:49 +08:00

252 lines
8.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
邮件服务工具类使用腾讯云SES服务发送邮件
"""
import logging
import random
import string
import json
import time
from typing import Dict, Any, Optional
import requests
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ses.v20201002 import ses_client, models
from cryptoai.utils.config_loader import ConfigLoader
# 配置日志
logger = logging.getLogger('email_service')
# 验证码缓存,格式: {email: {'code': '123456', 'expire_time': timestamp}}
verification_codes = {}
class EmailService:
"""邮件服务类,用于发送验证码邮件"""
def __init__(self):
"""初始化邮件服务"""
# 加载腾讯云SES配置
config_loader = ConfigLoader()
self.ses_config = config_loader.get_ses_config()
# 初始化SES客户端
self.client = None
self._init_client()
def _init_client(self) -> None:
"""初始化腾讯云SES客户端"""
try:
# 创建认证对象
cred = credential.Credential(
self.ses_config.get('secret_id'),
self.ses_config.get('secret_key')
)
# 创建HTTP配置
http_profile = HttpProfile()
http_profile.endpoint = "ses.tencentcloudapi.com"
# 创建客户端配置
client_profile = ClientProfile()
client_profile.httpProfile = http_profile
# 创建SES客户端
self.client = ses_client.SesClient(cred, self.ses_config.get('region'), client_profile)
logger.info("成功初始化腾讯云SES客户端")
except TencentCloudSDKException as e:
logger.error(f"初始化腾讯云SES客户端失败: {e}")
self.client = None
def generate_verification_code(self, length: int = 6) -> str:
"""
生成数字验证码
Args:
length: 验证码长度默认6位
Returns:
生成的验证码
"""
return ''.join(random.choices(string.digits, k=length))
def save_verification_code(self, email: str, code: str, expire_minutes: int = 10) -> None:
"""
保存验证码到缓存
Args:
email: 邮箱地址
code: 验证码
expire_minutes: 过期时间分钟默认10分钟
"""
expire_time = time.time() + expire_minutes * 60
verification_codes[email] = {
'code': code,
'expire_time': expire_time
}
def verify_code(self, email: str, code: str) -> bool:
"""
验证邮箱验证码
Args:
email: 邮箱地址
code: 验证码
Returns:
验证是否成功
"""
# 检查验证码是否存在
if email not in verification_codes:
return False
# 获取验证码信息
code_info = verification_codes[email]
# 检查验证码是否过期
if time.time() > code_info['expire_time']:
# 删除过期验证码
del verification_codes[email]
return False
# 验证验证码
if code_info['code'] == code:
# 验证成功后删除验证码
del verification_codes[email]
return True
return False
def send_verification_email(self, email: str) -> Dict[str, Any]:
"""
发送验证码邮件
Args:
email: 收件人邮箱
Returns:
发送结果
"""
if not self.client:
try:
self._init_client()
except Exception as e:
logger.error(f"重新初始化SES客户端失败: {e}")
return {
'success': False,
'message': '邮件服务初始化失败'
}
try:
# 生成验证码
code = self.generate_verification_code()
# 保存验证码
self.save_verification_code(email, code)
# 构建邮件内容
subject = "CryptoAI - 您的验证码"
html_content = f"""
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; line-height: 1.6; }}
.container {{ max-width: 600px; margin: 0 auto; padding: 20px; }}
.header {{ background-color: #4A90E2; color: white; padding: 10px; text-align: center; }}
.content {{ padding: 20px; }}
.code {{ font-size: 24px; font-weight: bold; text-align: center;
color: #4A90E2; padding: 10px; margin: 20px 0; }}
.footer {{ font-size: 12px; color: #999; text-align: center; margin-top: 30px; }}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>CryptoAI 验证码</h2>
</div>
<div class="content">
<p>您好,</p>
<p>您正在注册 CryptoAI 账号,请使用以下验证码完成注册:</p>
<div class="code">{code}</div>
<p>验证码有效期为10分钟请勿将验证码泄露给他人。</p>
<p>如果您没有进行此操作,请忽略此邮件。</p>
</div>
<div class="footer">
<p>此邮件由系统自动发送,请勿回复。</p>
<p>&copy; {time.strftime('%Y')} CryptoAI. 保留所有权利。</p>
</div>
</div>
</body>
</html>
"""
# 创建发送邮件请求
req = models.SendEmailRequest()
req.FromEmailAddress = self.ses_config.get('from_email')
req.Destination = [email]
req.Subject = subject
req.Template = {
"TemplateID": self.ses_config.get('template_id', 1), # 使用默认模板ID或配置的模板ID
"TemplateData": json.dumps({
"code": code
})
}
# 如果没有配置模板ID则使用HTML内容
if not self.ses_config.get('template_id'):
req.Template = None
req.Simple = {
"Html": html_content
}
# 发送邮件
resp = self.client.SendEmail(req)
logger.info(f"成功发送验证码邮件到 {email}")
return {
'success': True,
'message': '验证码已发送',
'request_id': resp.RequestId
}
except TencentCloudSDKException as e:
logger.error(f"发送验证码邮件失败: {e}")
return {
'success': False,
'message': f'发送验证码失败: {e}'
}
except Exception as e:
logger.error(f"发送验证码邮件出现未知错误: {e}")
return {
'success': False,
'message': f'发送验证码失败: {str(e)}'
}
# 单例模式
_email_service_instance = None
def get_email_service() -> EmailService:
"""
获取邮件服务实例(单例模式)
Returns:
邮件服务实例
"""
global _email_service_instance
# 如果已经初始化过,直接返回
if _email_service_instance is not None:
return _email_service_instance
# 创建实例
_email_service_instance = EmailService()
return _email_service_instance