feat: 新增平台
This commit is contained in:
286
routes/admin.py
286
routes/admin.py
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user