Files
pdf-translate-web/email_service.py
coder 71a613ff5f feat: 添加手机号、邀请好友、邮件通知功能
- 用户模型添加:手机号、邀请码、邀请统计、邮件通知设置
- 邀请好友系统:专属邀请码、邀请奖励(¥5/人)
- 邮件通知:翻译完成通知(含附件)、欢迎邮件、到期提醒
- 新增模型:UserInvitation, InviteRewardConfig, EmailNotification, EmailTemplateConfig
- 个人中心添加:手机号绑定、通知设置、邀请好友模块
- email_service.py:邮件发送服务(支持附件)

新用户注册奖励:¥2
邀请人奖励:¥5/人
2026-04-14 18:58:40 +08:00

210 lines
10 KiB
Python
Raw 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.
"""
邮件发送服务
支持SMTP邮件发送、附件发送、模板渲染
"""
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from datetime import datetime
from flask import current_app
class EmailService:
"""邮件发送服务"""
def __init__(self, smtp_host=None, smtp_port=None, smtp_user=None, smtp_pass=None):
self.smtp_host = smtp_host or os.environ.get('SMTP_HOST', 'mail.tphai.com')
self.smtp_port = int(smtp_port or os.environ.get('SMTP_PORT', '587'))
self.smtp_user = smtp_user or os.environ.get('SMTP_USER', 'favor@tphai.com')
self.smtp_pass = smtp_pass or os.environ.get('SMTP_PASS', 'favor@!')
def send_email(self, to_email, subject, body, attachment_path=None, attachment_name=None):
"""发送邮件(支持附件)"""
try:
# 创建邮件对象
msg = MIMEMultipart()
msg['From'] = self.smtp_user
msg['To'] = to_email
msg['Subject'] = subject
msg['Date'] = datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0800')
msg['Reply-To'] = to_email
# 正文
msg.attach(MIMEText(body, 'html', 'utf-8'))
# 附件
if attachment_path and os.path.exists(attachment_path):
with open(attachment_path, 'rb') as f:
part = MIMEBase('application', 'octet-stream')
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment',
filename=attachment_name or os.path.basename(attachment_path))
msg.attach(part)
# 发送
server = smtplib.SMTP(self.smtp_host, self.smtp_port)
server.ehlo()
server.login(self.smtp_user, self.smtp_pass)
server.sendmail(self.smtp_user, to_email, msg.as_string())
server.quit()
return True, "发送成功"
except Exception as e:
return False, str(e)
def send_translation_complete(self, user_email, username, filename, output_path, translation_id):
"""翻译完成通知"""
subject = f"【PDF翻译助手】翻译完成 - {filename}"
body = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="color: white; margin: 0;">📄 PDF翻译助手</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #eee; border-radius: 0 0 10px 10px;">
<p style="color: #333; font-size: 16px;">您好,{username}</p>
<p style="color: #666;">您的翻译任务已完成:</p>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0;">
<p style="margin: 5px 0;"><strong>文件:</strong>{filename}</p>
<p style="margin: 5px 0;"><strong>状态:</strong><span style="color: #28a745;">✅ 完成</span></p>
<p style="margin: 5px 0;"><strong>时间:</strong>{datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
</div>
<p style="color: #666;">翻译结果已作为附件发送,您也可以登录网站查看详情。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://localhost:19000/translation/{translation_id}"
style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px;">
查看翻译结果
</a>
</div>
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
<p style="color: #999; font-size: 12px; text-align: center;">
此邮件由系统自动发送,请勿回复。<br>
PDF翻译助手 - 让翻译更简单
</p>
</div>
</body>
</html>
"""
# 附件名称
attachment_name = f"{filename}_translated.md"
return self.send_email(user_email, subject, body, output_path, attachment_name)
def send_welcome_email(self, user_email, username, invite_code=None):
"""欢迎邮件"""
subject = "欢迎加入PDF翻译助手"
body = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="color: white; margin: 0;">📄 PDF翻译助手</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #eee; border-radius: 0 0 10px 10px;">
<p style="color: #333; font-size: 16px;">您好,{username}</p>
<p style="color: #666;">欢迎加入PDF翻译助手您已获得</p>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0;">
<p style="margin: 5px 0;">✅ 每日免费翻译 10 次</p>
<p style="margin: 5px 0;">✅ 单文件最大 50 页</p>
<p style="margin: 5px 0;">✅ 翻译历史记录</p>
<p style="margin: 5px 0;">✅ 不满意重新翻译</p>
</div>
{"<p style='color: #666;'>您的专属邀请码:<strong style='color: #667eea;'>" + invite_code + "</strong>,分享给好友可获得奖励!</p>" if invite_code else ""}
<div style="text-align: center; margin: 30px 0;">
<a href="http://localhost:19000/"
style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px;">
开始使用
</a>
</div>
</div>
</body>
</html>
"""
return self.send_email(user_email, subject, body)
def send_expire_reminder(self, user_email, username, expire_date, user_type):
"""会员到期提醒"""
subject = "【PDF翻译助手】会员即将到期提醒"
body = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="color: white; margin: 0;">📄 PDF翻译助手</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #eee; border-radius: 0 0 10px 10px;">
<p style="color: #333; font-size: 16px;">您好,{username}</p>
<div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #ffc107;">
<p style="margin: 5px 0; color: #856404;">⚠️ 您的会员即将到期</p>
<p style="margin: 5px 0;"><strong>会员类型:</strong>{user_type}</p>
<p style="margin: 5px 0;"><strong>到期时间:</strong>{expire_date}</p>
</div>
<p style="color: #666;">到期后将降级为免费用户每日翻译次数限制为10次。续费可继续享受会员权益。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://localhost:19000/pricing"
style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px;">
续费会员
</a>
</div>
</div>
</body>
</html>
"""
return self.send_email(user_email, subject, body)
def send_invite_reward(self, user_email, username, reward_amount, invitee_count):
"""邀请奖励通知"""
subject = f"【PDF翻译助手】邀请奖励 - ¥{reward_amount}"
body = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #28a745 0%, #20c997 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="color: white; margin: 0;">🎉 邀请奖励已发放</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #eee; border-radius: 0 0 10px 10px;">
<p style="color: #333; font-size: 16px;">您好,{username}</p>
<div style="background: #d4edda; padding: 15px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #28a745;">
<p style="margin: 5px 0; color: #155724;"><strong>奖励金额:</strong>¥{reward_amount}</p>
<p style="margin: 5px 0; color: #155724;"><strong>累计邀请:</strong>{invitee_count}人</p>
</div>
<p style="color: #666;">奖励已发放到您的账户余额,可用于购买会员或翻译服务。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://localhost:19000/profile"
style="background: #28a745; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px;">
查看余额
</a>
</div>
</div>
</body>
</html>
"""
return self.send_email(user_email, subject, body)
# 全局邮件服务实例
email_service = EmailService()