""" 数据库模型定义 """ 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) # 输出 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) # 重译信息 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, '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, }