Files
pdf-translate-web/models.py

963 lines
38 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.
"""
数据库模型定义
"""
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
import hashlib
db = SQLAlchemy()
# ==================== 用户模型 ====================
class User(db.Model):
"""用户表"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(256), nullable=False)
# 用户类型: guest, free, vip_basic, vip_pro, vip_enterprise, admin
user_type = db.Column(db.String(20), default='free')
# 会员信息
membership_expire = db.Column(db.DateTime, nullable=True) # 会员到期时间
# 使用统计
daily_count = db.Column(db.Integer, default=0) # 今日翻译次数
total_count = db.Column(db.Integer, default=0) # 总翻译次数
last_translate_date = db.Column(db.Date, nullable=True) # 最后翻译日期
# 时间戳
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 状态
is_active = db.Column(db.Boolean, default=True) # 是否启用
is_admin = db.Column(db.Boolean, default=False) # 是否管理员
# 余额
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_with_attachment = db.Column(db.Boolean, default=False) # 邮件带附件VIP功能
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)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def is_vip(self):
"""检查是否为付费会员"""
if self.user_type.startswith('vip'):
if self.membership_expire and self.membership_expire > datetime.utcnow():
return True
# 过期则降级为免费用户
self.user_type = 'free'
self.membership_expire = None
db.session.commit()
return False
def can_translate(self, pages, config):
"""检查是否可以翻译(次数、页数限制)"""
limits = config['USER_LIMITS'].get(self.user_type, config['USER_LIMITS']['free'])
# 检查页数限制
max_pages = limits['max_pages']
if max_pages > 0 and pages > max_pages:
return False, f"PDF页数超出限制最大{max_pages}页)"
# 检查每日次数限制 - 使用上海时间UTC+8
from datetime import timezone, timedelta
shanghai_tz = timezone(timedelta(hours=8))
today = datetime.now(shanghai_tz).date()
if self.last_translate_date != today:
self.daily_count = 0
self.last_translate_date = today
daily_limit = limits['daily_translations']
if daily_limit > 0 and self.daily_count >= daily_limit:
return False, f"今日翻译次数已达上限({daily_limit}次)"
return True, "OK"
def increment_count(self):
"""增加翻译计数"""
from datetime import timezone, timedelta
shanghai_tz = timezone(timedelta(hours=8))
today = datetime.now(shanghai_tz).date()
if self.last_translate_date != today:
self.daily_count = 0
self.last_translate_date = today
self.daily_count += 1
self.total_count += 1
db.session.commit()
def to_dict(self):
return {
'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_with_attachment': self.notify_with_attachment,
'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,
'membership_expire': self.membership_expire.isoformat() if self.membership_expire else None,
}
# ==================== 翻译记录模型 ====================
class Translation(db.Model):
"""翻译记录表"""
__tablename__ = 'translations'
id = db.Column(db.Integer, primary_key=True)
# 用户关联
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) # guest可为null
# 文件信息
file_hash = db.Column(db.String(64), nullable=False) # 文件MD5哈希
original_filename = db.Column(db.String(255), nullable=False)
file_size = db.Column(db.Integer, nullable=False)
page_count = db.Column(db.Integer, nullable=False)
# 翻译信息
source_language = db.Column(db.String(10), default='en')
target_language = db.Column(db.String(10), default='zh')
translate_params = db.Column(db.Text, nullable=True) # JSON格式的翻译参数
# 状态
status = db.Column(db.String(20), default='pending') # pending, processing, completed, failed
progress = db.Column(db.Integer, default=0) # 翻译进度 0-100
error_message = db.Column(db.Text, nullable=True)
# 输出
upload_path = db.Column(db.String(255), nullable=True) # 原始PDF文件路径
output_path = db.Column(db.String(255), nullable=True) # 翻译结果文件路径
# 时间戳
created_at = db.Column(db.DateTime, default=datetime.utcnow)
completed_at = db.Column(db.DateTime, nullable=True)
# 是否来自缓存
from_cache = db.Column(db.Boolean, default=False)
# 不共享缓存
no_share = db.Column(db.Boolean, default=False) # 不共享此翻译给其他用户
# 重译信息
retranslate_request = db.Column(db.Text, nullable=True) # 重译要求
parent_id = db.Column(db.Integer, db.ForeignKey('translations.id'), nullable=True) # 原翻译ID
def to_dict(self):
return {
'id': self.id,
'filename': self.original_filename,
'pages': self.page_count,
'status': self.status,
'progress': self.progress,
'from_cache': self.from_cache,
'no_share': self.no_share,
'file_size': self.file_size,
'created_at': self.created_at.isoformat() if self.created_at else None,
'completed_at': self.completed_at.isoformat() if self.completed_at else None,
'user_id': self.user_id,
}
# ==================== 翻译缓存模型 ====================
class TranslationCache(db.Model):
"""翻译缓存表"""
__tablename__ = 'translation_cache'
id = db.Column(db.Integer, primary_key=True)
# 文件哈希
file_hash = db.Column(db.String(64), unique=True, nullable=False)
# 缓存信息
cache_path = db.Column(db.String(255), nullable=False) # 缓存文件路径
page_count = db.Column(db.Integer, nullable=False)
file_size = db.Column(db.Integer, default=0)
# 统计
hit_count = db.Column(db.Integer, default=0) # 缓存命中次数
# 时间戳
created_at = db.Column(db.DateTime, default=datetime.utcnow)
expires_at = db.Column(db.DateTime, nullable=True)
def increment_hit(self):
self.hit_count += 1
db.session.commit()
@staticmethod
def compute_hash(file_content):
"""计算文件哈希"""
return hashlib.md5(file_content).hexdigest()
# ==================== 访客翻译记录 ====================
class GuestTranslation(db.Model):
"""访客翻译记录基于IP或Session"""
__tablename__ = 'guest_translations'
id = db.Column(db.Integer, primary_key=True)
# 访客标识
session_id = db.Column(db.String(64), nullable=False) # Session ID
ip_address = db.Column(db.String(45), nullable=True)
# 统计
daily_count = db.Column(db.Integer, default=0)
total_count = db.Column(db.Integer, default=0)
last_translate_date = db.Column(db.Date, nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# ==================== 系统配置模型 ====================
class SystemConfig(db.Model):
"""系统配置表"""
__tablename__ = 'system_config'
id = db.Column(db.Integer, primary_key=True)
key = db.Column(db.String(100), unique=True, nullable=False)
value = db.Column(db.Text, nullable=True)
description = db.Column(db.String(255), nullable=True)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
@staticmethod
def get(key, default=None):
config = SystemConfig.query.filter_by(key=key).first()
return config.value if config else default
@staticmethod
def set(key, value, description=None):
config = SystemConfig.query.filter_by(key=key).first()
if config:
config.value = value
else:
config = SystemConfig(key=key, value=value, description=description)
db.session.add(config)
db.session.commit()
# ==================== 操作日志模型 ====================
class OperationLog(db.Model):
"""操作日志表"""
__tablename__ = 'operation_logs'
id = db.Column(db.Integer, primary_key=True)
# 操作者
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
username = db.Column(db.String(80), nullable=True)
# 操作信息
action = db.Column(db.String(50), nullable=False) # login, translate, register, etc.
target = db.Column(db.String(100), nullable=True) # 操作对象
detail = db.Column(db.Text, nullable=True) # 详细信息(JSON)
# IP地址
ip_address = db.Column(db.String(45), nullable=True)
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'username': self.username,
'action': self.action,
'target': self.target,
'ip_address': self.ip_address,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
# ==================== 数据包套餐模型 ====================
class DataPackage(db.Model):
"""数据包购买套餐"""
__tablename__ = 'data_packages'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False) # 套餐名称
description = db.Column(db.String(255), nullable=True) # 描述
# 翻译次数
translation_count = db.Column(db.Integer, default=0) # 翻译次数(-1表示无限
# 价格
price = db.Column(db.Float, default=0) # 价格
original_price = db.Column(db.Float, nullable=True) # 原价(用于显示折扣)
# 有效期
valid_days = db.Column(db.Integer, default=30) # 有效天数0表示永久
# 排序和状态
sort_order = db.Column(db.Integer, default=0) # 排序
is_active = db.Column(db.Boolean, default=True) # 是否上架
is_recommended = db.Column(db.Boolean, default=False) # 是否推荐
# 时间
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,
'name': self.name,
'description': self.description,
'translation_count': self.translation_count,
'price': self.price,
'original_price': self.original_price,
'valid_days': self.valid_days,
'is_active': self.is_active,
'is_recommended': self.is_recommended,
}
# ==================== 用户数据包购买记录 ====================
class UserPackage(db.Model):
"""用户购买的数据包"""
__tablename__ = 'user_packages'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
package_id = db.Column(db.Integer, db.ForeignKey('data_packages.id'), nullable=False)
# 套餐信息快照
package_name = db.Column(db.String(100), nullable=False)
translation_count = db.Column(db.Integer, default=0) # 总次数
remaining_count = db.Column(db.Integer, default=0) # 剩余次数
# 有效期
expire_at = db.Column(db.DateTime, nullable=True) # 过期时间None表示永久
# 购买信息
price_paid = db.Column(db.Float, default=0) # 实付金额
payment_method = db.Column(db.String(20), nullable=True) # 支付方式
payment_status = db.Column(db.String(20), default='pending') # pending, paid, refunded
# 时间
purchased_at = db.Column(db.DateTime, default=datetime.utcnow)
# 关系
user = db.relationship('User', backref=db.backref('packages', lazy=True))
package = db.relationship('DataPackage', backref=db.backref('purchases', lazy=True))
def is_valid(self):
"""检查数据包是否有效"""
if self.remaining_count <= 0:
return False
if self.expire_at and self.expire_at < datetime.utcnow():
return False
return True
# ==================== 系统配置(动态) ====================
class DynamicConfig(db.Model):
"""动态系统配置(可在页面修改)"""
__tablename__ = 'dynamic_config'
id = db.Column(db.Integer, primary_key=True)
category = db.Column(db.String(50), default='general') # 分类: general, user_limits, membership
key = db.Column(db.String(100), unique=True, nullable=False)
value = db.Column(db.Text, nullable=True)
value_type = db.Column(db.String(20), default='string') # string, int, float, bool, json
description = db.Column(db.String(255), nullable=True)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
updated_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
@staticmethod
def get(key, default=None):
config = DynamicConfig.query.filter_by(key=key).first()
if not config:
return default
# 类型转换
if config.value_type == 'int':
return int(config.value) if config.value else 0
elif config.value_type == 'float':
return float(config.value) if config.value else 0.0
elif config.value_type == 'bool':
return config.value.lower() in ('true', '1', 'yes')
elif config.value_type == 'json':
import json
return json.loads(config.value) if config.value else {}
return config.value
@staticmethod
def set(key, value, category='general', value_type='string', description=None, user_id=None):
config = DynamicConfig.query.filter_by(key=key).first()
# 类型转换
if value_type == 'json':
import json
value = json.dumps(value, ensure_ascii=False)
elif value_type == 'bool':
value = 'true' if value else 'false'
else:
value = str(value) if value is not None else None
if config:
config.value = value
config.value_type = value_type
config.updated_by = user_id
else:
config = DynamicConfig(
category=category,
key=key,
value=value,
value_type=value_type,
description=description,
updated_by=user_id
)
db.session.add(config)
db.session.commit()
return config
# ==================== 用户类型配置(动态增删) ====================
class UserTypeConfig(db.Model):
"""用户类型配置表 - 支持动态增删"""
__tablename__ = 'user_type_config'
id = db.Column(db.Integer, primary_key=True)
type_key = db.Column(db.String(50), unique=True, nullable=False) # 类型标识: guest, free, vip_basic, etc.
display_name = db.Column(db.String(100), nullable=False) # 显示名称: 访客, 免费用户, 基础会员
# 权限配置
daily_translations = db.Column(db.Integer, default=10) # 每日翻译次数 (-1=无限)
max_pages = db.Column(db.Integer, default=50) # 最大页数 (-1=无限)
max_file_size = db.Column(db.Integer, default=30*1024*1024) # 最大文件大小(bytes)
# 功能列表(JSON)
features = db.Column(db.Text, default='[]') # JSON数组: ["basic_translate", "history"]
# 排序和状态
sort_order = db.Column(db.Integer, default=0) # 排序权重
is_active = db.Column(db.Boolean, default=True) # 是否启用
is_system = db.Column(db.Boolean, default=False) # 是否系统内置(不可删除)
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
def get_features(self):
"""获取功能列表"""
import json
try:
return json.loads(self.features) if self.features else []
except:
return []
def set_features(self, feature_list):
"""设置功能列表"""
import json
self.features = json.dumps(feature_list, ensure_ascii=False)
def to_dict(self):
return {
'id': self.id,
'type_key': self.type_key,
'display_name': self.display_name,
'daily_translations': self.daily_translations,
'max_pages': self.max_pages,
'max_file_size': self.max_file_size,
'features': self.get_features(),
'sort_order': self.sort_order,
'is_active': self.is_active,
'is_system': self.is_system,
}
# ==================== 会员套餐配置(动态增删) ====================
class MembershipPlanConfig(db.Model):
"""会员套餐配置表 - 支持动态增删"""
__tablename__ = 'membership_plan_config'
id = db.Column(db.Integer, primary_key=True)
plan_key = db.Column(db.String(50), unique=True, nullable=False) # 套餐标识: vip_basic, vip_pro, etc.
display_name = db.Column(db.String(100), nullable=False) # 显示名称: 基础会员
# 价格配置
price = db.Column(db.Float, default=0) # 价格
original_price = db.Column(db.Float, nullable=True) # 原价(用于折扣显示)
period = db.Column(db.String(20), default='month') # 周期: month, quarter, year
period_days = db.Column(db.Integer, default=30) # 周期天数
# 描述
description = db.Column(db.String(255), nullable=True) # 套餐描述
# 对应的用户类型
user_type_key = db.Column(db.String(50), nullable=True) # 购买后升级到的用户类型
# 排序和状态
sort_order = db.Column(db.Integer, default=0)
is_active = db.Column(db.Boolean, default=True)
is_recommended = db.Column(db.Boolean, default=False) # 是否推荐
is_system = db.Column(db.Boolean, default=False) # 是否系统内置(不可删除)
# 时间
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,
'plan_key': self.plan_key,
'display_name': self.display_name,
'price': self.price,
'original_price': self.original_price,
'period': self.period,
'period_days': self.period_days,
'description': self.description,
'user_type_key': self.user_type_key,
'sort_order': self.sort_order,
'is_active': self.is_active,
'is_recommended': self.is_recommended,
'is_system': self.is_system,
}
# ==================== 用户充值记录 ====================
class UserRecharge(db.Model):
"""用户充值记录"""
__tablename__ = 'user_recharges'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
# 充值信息
amount = db.Column(db.Float, nullable=False) # 充值金额
balance_before = db.Column(db.Float, default=0) # 充值前余额
balance_after = db.Column(db.Float, default=0) # 充值后余额
# 支付方式
payment_method = db.Column(db.String(20), default='balance') # balance, alipay, wechat, manual
# 状态
status = db.Column(db.String(20), default='pending') # pending, completed, failed, cancelled
order_no = db.Column(db.String(64), unique=True, nullable=True) # 订单号
# 备注
remark = db.Column(db.String(255), nullable=True) # 管理员备注
operator_id = db.Column(db.Integer, nullable=True) # 操作管理员ID
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
completed_at = db.Column(db.DateTime, nullable=True)
def to_dict(self):
return {
'id': self.id,
'user_id': self.user_id,
'amount': self.amount,
'balance_before': self.balance_before,
'balance_after': self.balance_after,
'payment_method': self.payment_method,
'status': self.status,
'order_no': self.order_no,
'remark': self.remark,
'created_at': self.created_at.isoformat() if self.created_at else None,
'completed_at': self.completed_at.isoformat() if self.completed_at else None,
}
# ==================== 用户退款记录 ====================
class UserRefund(db.Model):
"""用户退款记录"""
__tablename__ = 'user_refunds'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
recharge_id = db.Column(db.Integer, db.ForeignKey('user_recharges.id'), nullable=True) # 关联充值记录
# 退款信息
amount = db.Column(db.Float, nullable=False) # 退款金额
balance_before = db.Column(db.Float, default=0) # 退款前余额
balance_after = db.Column(db.Float, default=0) # 退款后余额
# 原因
reason = db.Column(db.String(255), nullable=True) # 退款原因
reason_type = db.Column(db.String(20), default='user_request') # user_request, system_error, admin_initiated
# 状态
status = db.Column(db.String(20), default='pending') # pending, approved, completed, rejected
# 操作
operator_id = db.Column(db.Integer, nullable=True) # 处理管理员ID
operator_remark = db.Column(db.String(255), nullable=True) # 管理员处理备注
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
processed_at = db.Column(db.DateTime, nullable=True)
completed_at = db.Column(db.DateTime, nullable=True)
def to_dict(self):
return {
'id': self.id,
'user_id': self.user_id,
'recharge_id': self.recharge_id,
'amount': self.amount,
'balance_before': self.balance_before,
'balance_after': self.balance_after,
'reason': self.reason,
'reason_type': self.reason_type,
'status': self.status,
'operator_remark': self.operator_remark,
'created_at': self.created_at.isoformat() if self.created_at else None,
'processed_at': self.processed_at.isoformat() if self.processed_at else None,
'completed_at': self.completed_at.isoformat() if self.completed_at else None,
}
# ==================== 会员购买记录 ====================
class MembershipPurchase(db.Model):
"""会员购买记录"""
__tablename__ = 'membership_purchases'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
# 套餐信息
plan_key = db.Column(db.String(50), nullable=False) # vip_basic, vip_pro, vip_enterprise
plan_name = db.Column(db.String(100), nullable=False) # 基础会员, 专业会员
# 价格
price = db.Column(db.Float, nullable=False) # 实付金额
original_price = db.Column(db.Float, nullable=True) # 原价
# 有效期
period_days = db.Column(db.Integer, default=30) # 购买天数
expire_before = db.Column(db.DateTime, nullable=True) # 购买前到期时间
expire_after = db.Column(db.DateTime, nullable=True) # 购买后到期时间
# 支付方式
payment_method = db.Column(db.String(20), default='balance') # balance, alipay, wechat
# 状态
status = db.Column(db.String(20), default='completed') # pending, completed, refunded
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# 关系
user = db.relationship('User', backref=db.backref('membership_purchases', lazy=True))
def to_dict(self):
return {
'id': self.id,
'user_id': self.user_id,
'plan_key': self.plan_key,
'plan_name': self.plan_name,
'price': self.price,
'original_price': self.original_price,
'period_days': self.period_days,
'expire_after': self.expire_after.isoformat() if self.expire_after else None,
'payment_method': self.payment_method,
'status': self.status,
'created_at': self.created_at.isoformat() if self.created_at else None,
}
# ==================== 账户流水记录 ====================
class AccountTransaction(db.Model):
"""账户流水记录"""
__tablename__ = 'account_transactions'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
# 流水信息
transaction_type = db.Column(db.String(20), nullable=False) # recharge, refund, purchase, consume, refund_purchase
amount = db.Column(db.Float, nullable=False) # 金额(正数加,负数减)
balance_before = db.Column(db.Float, default=0) # 操作前余额
balance_after = db.Column(db.Float, default=0) # 操作后余额
# 关联
related_id = db.Column(db.Integer, nullable=True) # 关联记录ID充值ID、退款ID等
related_type = db.Column(db.String(50), nullable=True) # 关联类型recharge, refund, membership_purchase等
# 描述
description = db.Column(db.String(255), nullable=True) # 流水描述
# 时间
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# 关系
user = db.relationship('User', backref=db.backref('transactions', lazy=True))
def to_dict(self):
return {
'id': self.id,
'user_id': self.user_id,
'transaction_type': self.transaction_type,
'amount': self.amount,
'balance_before': self.balance_before,
'balance_after': self.balance_after,
'related_id': self.related_id,
'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 BackupLLMConfig(db.Model):
"""大模型接口配置"""
__tablename__ = 'backup_llm_config'
id = db.Column(db.Integer, primary_key=True)
# 服务商信息
provider_name = db.Column(db.String(100), nullable=False) # 服务商名称: OpenAI, DeepSeek, 阿里百炼, etc.
api_base = db.Column(db.String(255), nullable=False) # API地址
api_key = db.Column(db.String(255), nullable=True) # API Key可选
model = db.Column(db.String(100), nullable=True) # 默认模型
# 配置参数
max_tokens = db.Column(db.Integer, default=8000) # 最大输出Token
chunk_size = db.Column(db.Integer, default=2000) # 分块大小
timeout = db.Column(db.Integer, default=180) # 超时时间
# 状态
is_active = db.Column(db.Boolean, default=True) # 是否启用
is_default = db.Column(db.Boolean, default=False) # 是否默认使用
sort_order = db.Column(db.Integer, default=0) # 排序
# 备注
description = db.Column(db.String(255), nullable=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,
'provider_name': self.provider_name,
'api_base': self.api_base,
'api_key': self.api_key,
'model': self.model,
'max_tokens': self.max_tokens,
'chunk_size': self.chunk_size,
'timeout': self.timeout,
'is_active': self.is_active,
'is_default': self.is_default,
'sort_order': self.sort_order,
'description': self.description,
'created_at': self.created_at.isoformat() if self.created_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,
}