feat: 新增平台

This commit is contained in:
2025-11-30 19:49:25 +08:00
parent c3e56a954d
commit fbd2c491b2
41 changed files with 4293 additions and 76 deletions

View File

@@ -113,6 +113,8 @@ def users_page():
@admin_required
def get_users():
"""获取用户列表API"""
from models import UserGroupExpiry
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 20, type=int)
group_id = request.args.get('group_id', type=int)
@@ -126,16 +128,28 @@ def get_users():
page=page, per_page=per_page, error_out=False
)
users = [{
'id': u.id,
'username': u.username,
'email': u.email,
'group_id': u.group_id,
'group_name': u.group.name if u.group else '',
'total_parse_count': u.total_parse_count,
'is_active': u.is_active,
'created_at': u.created_at.isoformat()
} for u in pagination.items]
users = []
for u in pagination.items:
# 获取套餐到期时间
expiry = UserGroupExpiry.query.filter_by(user_id=u.id).first()
expires_at = None
is_expired = False
if expiry and expiry.expires_at:
expires_at = expiry.expires_at.strftime('%Y-%m-%dT%H:%M')
is_expired = expiry.expires_at < datetime.utcnow()
users.append({
'id': u.id,
'username': u.username,
'email': u.email,
'group_id': u.group_id,
'group_name': u.group.name if u.group else '',
'total_parse_count': u.total_parse_count,
'is_active': u.is_active,
'created_at': u.created_at.isoformat(),
'expires_at': expires_at,
'is_expired': is_expired
})
return jsonify({
'success': True,
@@ -152,6 +166,8 @@ def get_users():
@admin_required
def update_user(user_id):
"""更新用户信息"""
from models import UserGroupExpiry
user = User.query.get_or_404(user_id)
data = request.get_json()
@@ -160,6 +176,29 @@ def update_user(user_id):
if 'is_active' in data:
user.is_active = data['is_active']
# 处理套餐到期时间
if 'expires_at' in data:
expiry = UserGroupExpiry.query.filter_by(user_id=user_id).first()
if data['expires_at']:
# 解析时间字符串
expires_at = datetime.strptime(data['expires_at'], '%Y-%m-%dT%H:%M')
if expiry:
expiry.group_id = user.group_id
expiry.expires_at = expires_at
else:
expiry = UserGroupExpiry(
user_id=user_id,
group_id=user.group_id,
expires_at=expires_at
)
db.session.add(expiry)
else:
# 清空到期时间(游客/普通用户)
if expiry:
db.session.delete(expiry)
db.session.commit()
return jsonify({'success': True, 'message': '更新成功'})
@@ -766,3 +805,230 @@ def change_email():
admin.email = email
db.session.commit()
return jsonify({'success': True, 'message': '邮箱修改成功'})
# ==================== 用户组 API ====================
@admin_bp.route('/api/user-groups', methods=['GET'])
@admin_required
def get_user_groups():
"""获取用户组列表(仅返回可兑换的套餐,排除游客和普通用户)"""
# 排除游客(id=1)和普通用户(id=2)
groups = UserGroup.query.filter(UserGroup.id > 2).all()
return jsonify({
'success': True,
'data': [{'id': g.id, 'name': g.name, 'daily_limit': g.daily_limit} for g in groups]
})
# ==================== 兑换码管理 ====================
@admin_bp.route('/redeem-codes')
@admin_required
def redeem_codes_page():
"""兑换码管理页面"""
from flask import render_template
return render_template('admin_redeem_codes.html')
@admin_bp.route('/api/redeem-codes', methods=['GET'])
@admin_required
def get_redeem_codes():
"""获取兑换码列表"""
from models import RedeemCode
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 20, type=int)
batch_id = request.args.get('batch_id', '')
status = request.args.get('status', '') # unused, used, expired
query = RedeemCode.query
if batch_id:
query = query.filter(RedeemCode.batch_id == batch_id)
if status == 'unused':
query = query.filter(RedeemCode.is_used == False)
elif status == 'used':
query = query.filter(RedeemCode.is_used == True)
elif status == 'expired':
query = query.filter(RedeemCode.is_used == False, RedeemCode.expires_at < datetime.utcnow())
pagination = query.order_by(RedeemCode.created_at.desc()).paginate(page=page, per_page=per_page)
codes = []
for code in pagination.items:
codes.append({
'id': code.id,
'code': code.code,
'batch_id': code.batch_id,
'target_group': code.target_group.name if code.target_group else '',
'target_group_id': code.target_group_id,
'duration_days': code.duration_days,
'is_used': code.is_used,
'used_by': code.user.username if code.user else None,
'used_at': code.used_at.strftime('%Y-%m-%d %H:%M') if code.used_at else None,
'expires_at': code.expires_at.strftime('%Y-%m-%d %H:%M') if code.expires_at else None,
'is_expired': code.expires_at and code.expires_at < datetime.utcnow() and not code.is_used,
'remark': code.remark,
'created_at': code.created_at.strftime('%Y-%m-%d %H:%M')
})
return jsonify({
'success': True,
'data': codes,
'pagination': {
'page': page,
'per_page': per_page,
'total': pagination.total,
'pages': pagination.pages
}
})
@admin_bp.route('/api/redeem-codes/generate', methods=['POST'])
@admin_required
def generate_redeem_codes():
"""批量生成兑换码"""
from models import RedeemCode
import secrets
import string
data = request.get_json()
count = data.get('count', 1)
target_group_id = data.get('target_group_id')
duration_days = data.get('duration_days', 30)
expires_days = data.get('expires_days') # 兑换码有效期(天)
remark = data.get('remark', '')
prefix = data.get('prefix', '') # 兑换码前缀
if not target_group_id:
return jsonify({'success': False, 'message': '请选择目标用户组'}), 400
if count < 1 or count > 1000:
return jsonify({'success': False, 'message': '生成数量必须在1-1000之间'}), 400
# 验证用户组存在
group = UserGroup.query.get(target_group_id)
if not group:
return jsonify({'success': False, 'message': '用户组不存在'}), 400
# 生成批次ID
batch_id = datetime.now().strftime('%Y%m%d%H%M%S') + secrets.token_hex(4)
# 计算过期时间
expires_at = None
if expires_days:
expires_at = datetime.utcnow() + timedelta(days=expires_days)
# 生成兑换码
codes = []
chars = string.ascii_uppercase + string.digits
for _ in range(count):
# 生成随机码
random_part = ''.join(secrets.choice(chars) for _ in range(12))
code_str = f"{prefix}{random_part}" if prefix else random_part
code = RedeemCode(
code=code_str,
batch_id=batch_id,
target_group_id=target_group_id,
duration_days=duration_days,
expires_at=expires_at,
remark=remark
)
db.session.add(code)
codes.append(code_str)
db.session.commit()
return jsonify({
'success': True,
'message': f'成功生成 {count} 个兑换码',
'data': {
'batch_id': batch_id,
'codes': codes
}
})
@admin_bp.route('/api/redeem-codes/<int:code_id>', methods=['DELETE'])
@admin_required
def delete_redeem_code(code_id):
"""删除兑换码"""
from models import RedeemCode
code = RedeemCode.query.get(code_id)
if not code:
return jsonify({'success': False, 'message': '兑换码不存在'}), 404
if code.is_used:
return jsonify({'success': False, 'message': '已使用的兑换码不能删除'}), 400
db.session.delete(code)
db.session.commit()
return jsonify({'success': True, 'message': '删除成功'})
@admin_bp.route('/api/redeem-codes/batch/<batch_id>', methods=['DELETE'])
@admin_required
def delete_batch_codes(batch_id):
"""删除整批兑换码"""
from models import RedeemCode
# 只删除未使用的
deleted = RedeemCode.query.filter_by(batch_id=batch_id, is_used=False).delete()
db.session.commit()
return jsonify({'success': True, 'message': f'成功删除 {deleted} 个未使用的兑换码'})
@admin_bp.route('/api/redeem-codes/batches', methods=['GET'])
@admin_required
def get_batch_list():
"""获取批次列表"""
from models import RedeemCode
batches = db.session.query(
RedeemCode.batch_id,
func.count(RedeemCode.id).label('total'),
func.sum(db.case((RedeemCode.is_used == True, 1), else_=0)).label('used'),
func.min(RedeemCode.created_at).label('created_at')
).group_by(RedeemCode.batch_id).order_by(func.min(RedeemCode.created_at).desc()).all()
result = []
for batch in batches:
result.append({
'batch_id': batch.batch_id,
'total': batch.total,
'used': int(batch.used or 0),
'unused': batch.total - int(batch.used or 0),
'created_at': batch.created_at.strftime('%Y-%m-%d %H:%M') if batch.created_at else ''
})
return jsonify({'success': True, 'data': result})
@admin_bp.route('/api/redeem-codes/export/<batch_id>', methods=['GET'])
@admin_required
def export_batch_codes(batch_id):
"""导出批次兑换码"""
from models import RedeemCode
codes = RedeemCode.query.filter_by(batch_id=batch_id).all()
if not codes:
return jsonify({'success': False, 'message': '批次不存在'}), 404
code_list = [code.code for code in codes]
return jsonify({
'success': True,
'data': {
'batch_id': batch_id,
'codes': code_list,
'total': len(code_list)
}
})