feat: 添加手机号、邀请好友、邮件通知功能

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

新用户注册奖励:¥2
邀请人奖励:¥5/人
This commit is contained in:
2026-04-14 18:58:40 +08:00
parent 4aac8ab04c
commit 71a613ff5f
4 changed files with 725 additions and 4 deletions

196
models.py
View File

@@ -41,8 +41,25 @@ class User(db.Model):
# 余额
balance = db.Column(db.Float, default=0.0) # 账户余额(元)
# 手机号
phone = db.Column(db.String(20), nullable=True) # 手机号
phone_verified = db.Column(db.Boolean, default=False) # 手机号已验证
# 邀请系统
invite_code = db.Column(db.String(10), unique=True, nullable=True) # 用户专属邀请码
invited_by = db.Column(db.Integer, nullable=True) # 邀请人ID
invite_count = db.Column(db.Integer, default=0) # 已邀请人数
invite_rewards = db.Column(db.Float, default=0.0) # 邀请奖励金额
# 邮件通知设置
email_notify = db.Column(db.Boolean, default=True) # 邮件通知开关
notify_on_complete = db.Column(db.Boolean, default=True) # 翻译完成通知
notify_on_expire = db.Column(db.Boolean, default=True) # 会员到期提醒
# 关系
translations = db.relationship('Translation', backref='user', lazy=True)
invitations = db.relationship('UserInvitation', backref='inviter', lazy=True,
foreign_keys='UserInvitation.inviter_id')
recharges = db.relationship('UserRecharge', backref='user', lazy=True)
refunds = db.relationship('UserRefund', backref='user', lazy=True)
@@ -103,11 +120,19 @@ class User(db.Model):
'id': self.id,
'username': self.username,
'email': self.email,
'phone': self.phone,
'phone_verified': self.phone_verified,
'user_type': self.user_type,
'is_vip': self.is_vip(),
'is_admin': self.is_admin,
'is_active': self.is_active,
'balance': self.balance,
'invite_code': self.invite_code,
'invite_count': self.invite_count,
'invite_rewards': self.invite_rewards,
'email_notify': self.email_notify,
'notify_on_complete': self.notify_on_complete,
'notify_on_expire': self.notify_on_expire,
'daily_count': self.daily_count,
'total_count': self.total_count,
'created_at': self.created_at.isoformat() if self.created_at else None,
@@ -709,4 +734,175 @@ class AccountTransaction(db.Model):
'related_type': self.related_type,
'description': self.description,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
# ==================== 用户邀请记录 ====================
class UserInvitation(db.Model):
"""用户邀请记录"""
__tablename__ = 'user_invitations'
id = db.Column(db.Integer, primary_key=True)
# 邀请人
inviter_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
invite_code = db.Column(db.String(10), nullable=False) # 使用的邀请码
# 被邀请人
invitee_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) # 注册后关联
invitee_email = db.Column(db.String(120), nullable=True) # 被邀请人邮箱(注册前)
# 奖励
reward_amount = db.Column(db.Float, default=0.0) # 奖励金额
reward_given = db.Column(db.Boolean, default=False) # 是否已发放奖励
# 状态
status = db.Column(db.String(20), default='pending') # pending, registered, rewarded
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
registered_at = db.Column(db.DateTime, nullable=True) # 被邀请人注册时间
rewarded_at = db.Column(db.DateTime, nullable=True) # 奖励发放时间
# 关系
invitee = db.relationship('User', backref=db.backref('invited_by_record', lazy=True),
foreign_keys=[invitee_id])
def to_dict(self):
return {
'id': self.id,
'inviter_id': self.inviter_id,
'invite_code': self.invite_code,
'invitee_id': self.invitee_id,
'invitee_email': self.invitee_email,
'reward_amount': self.reward_amount,
'reward_given': self.reward_given,
'status': self.status,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
# ==================== 邀请奖励配置 ====================
class InviteRewardConfig(db.Model):
"""邀请奖励配置"""
__tablename__ = 'invite_reward_config'
id = db.Column(db.Integer, primary_key=True)
# 奖励规则
reward_amount = db.Column(db.Float, default=5.0) # 邀请奖励金额(元)
invitee_bonus = db.Column(db.Float, default=2.0) # 被邀请人 bonus
min_invitee_user_type = db.Column(db.String(20), default='free') # 被邀请人最低等级才能奖励
# 限制
max_daily_invites = db.Column(db.Integer, default=10) # 每日最大邀请次数
max_total_invites = db.Column(db.Integer, default=100) # 总最大邀请次数
# 状态
is_active = db.Column(db.Boolean, default=True)
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'reward_amount': self.reward_amount,
'invitee_bonus': self.invitee_bonus,
'min_invitee_user_type': self.min_invitee_user_type,
'max_daily_invites': self.max_daily_invites,
'max_total_invites': self.max_total_invites,
'is_active': self.is_active,
}
# ==================== 邮件通知记录 ====================
class EmailNotification(db.Model):
"""邮件通知记录"""
__tablename__ = 'email_notifications'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
# 邮件信息
email_to = db.Column(db.String(120), nullable=False) # 收件人
email_type = db.Column(db.String(30), nullable=False) # complete, expire, invoice, welcome
# 附件
has_attachment = db.Column(db.Boolean, default=False)
attachment_path = db.Column(db.String(255), nullable=True) # 附件文件路径
attachment_name = db.Column(db.String(100), nullable=True) # 附件显示名称
# 内容
subject = db.Column(db.String(200), nullable=True)
body_preview = db.Column(db.Text, nullable=True) # 正文预览
# 状态
status = db.Column(db.String(20), default='pending') # pending, sent, failed
error_message = db.Column(db.String(255), nullable=True)
# 关联
related_id = db.Column(db.Integer, nullable=True) # 关联ID翻译ID、订单ID等
related_type = db.Column(db.String(30), nullable=True) # 关联类型
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
sent_at = db.Column(db.DateTime, nullable=True)
# 关系
user = db.relationship('User', backref=db.backref('email_notifications', lazy=True))
def to_dict(self):
return {
'id': self.id,
'user_id': self.user_id,
'email_to': self.email_to,
'email_type': self.email_type,
'has_attachment': self.has_attachment,
'subject': self.subject,
'status': self.status,
'created_at': self.created_at.isoformat() if self.created_at else None,
'sent_at': self.sent_at.isoformat() if self.sent_at else None,
}
# ==================== 邮件模板配置 ====================
class EmailTemplateConfig(db.Model):
"""邮件模板配置"""
__tablename__ = 'email_template_config'
id = db.Column(db.Integer, primary_key=True)
template_key = db.Column(db.String(50), unique=True, nullable=False) # complete, expire, invoice, welcome, invite
template_name = db.Column(db.String(100), nullable=False) # 显示名称
# 模板内容
subject_template = db.Column(db.String(200), nullable=False) # 主题模板
body_template = db.Column(db.Text, nullable=False) # 正文模板(支持变量)
# 支持变量说明
variables = db.Column(db.Text, nullable=True) # JSON: ["username", "filename", ...]
# 状态
is_active = db.Column(db.Boolean, default=True)
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def get_variables(self):
import json
try:
return json.loads(self.variables) if self.variables else []
except:
return []
def to_dict(self):
return {
'id': self.id,
'template_key': self.template_key,
'template_name': self.template_name,
'subject_template': self.subject_template,
'is_active': self.is_active,
}