- 移除主配置表单,改为从列表选择默认接口 - 新增 is_default 字段标记默认使用的接口 - 新增 max_tokens/chunk_size/timeout 配置参数 - 点击"设为默认"按钮即可切换当前使用的接口 - get_llm_config() 从默认接口获取配置 - 默认接口不可删除,必须有至少一个默认
958 lines
38 KiB
Python
958 lines
38 KiB
Python
"""
|
||
数据库模型定义
|
||
"""
|
||
|
||
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,
|
||
} |