VIP功能实现 #33

Merged
ppy4sjqvf merged 1 commits from ybw-branch into develop 2 weeks ago

@ -0,0 +1,292 @@
# VIP功能接口文档
## 概述
本文档描述VIP用户注册、升级及管理相关的API接口。
---
## 1. 用户注册支持VIP邀请码
### POST /api/auth/register
用户注册接口可选提供VIP邀请码直接注册为VIP用户。
**请求参数JSON**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
| email | string | 是 | 邮箱 |
| code | string | 是 | 邮箱验证码 |
| vip_code | string | 否 | VIP邀请码提供有效邀请码则注册为VIP |
**请求示例**
```json
{
"username": "testuser",
"password": "password123",
"email": "test@example.com",
"code": "123456",
"vip_code": "VIP-A1B2C3D4"
}
```
**响应示例**
成功201
```json
{
"message": "VIP注册成功",
"user": {
"user_id": 1,
"username": "testuser",
"email": "test@example.com",
"role": "vip",
"is_active": true,
"created_at": "2025-12-28T10:00:00",
"updated_at": "2025-12-28T10:00:00"
}
}
```
失败400
```json
{
"error": "VIP邀请码无效或已过期"
}
```
---
## 2. 获取VIP状态
### GET /api/auth/vip-status
获取当前登录用户的VIP状态和特权信息。
**请求头**
| 参数 | 说明 |
|------|------|
| Authorization | Bearer {access_token} |
**响应示例**
成功200
```json
{
"is_vip": true,
"role": "vip",
"vip_features": {
"max_concurrent_tasks": 10,
"can_use_all_datasets": true,
"can_upload_finetune": true
}
}
```
---
## 3. 升级为VIP
### POST /api/user/upgrade-vip
已登录的普通用户通过VIP邀请码升级为VIP。
**请求头**
| 参数 | 说明 |
|------|------|
| Authorization | Bearer {access_token} |
**请求参数JSON**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| vip_code | string | 是 | VIP邀请码 |
**请求示例**
```json
{
"vip_code": "VIP-A1B2C3D4"
}
```
**响应示例**
成功200
```json
{
"message": "恭喜您已成功升级为VIP用户",
"user": {
"user_id": 1,
"username": "testuser",
"email": "test@example.com",
"role": "vip",
"is_active": true,
"created_at": "2025-12-28T10:00:00",
"updated_at": "2025-12-28T10:30:00"
},
"vip_features": {
"max_concurrent_tasks": 10,
"can_use_all_datasets": true,
"can_upload_finetune": true
}
}
```
失败400
```json
{
"error": "您已经是VIP用户"
}
```
---
## 4. 生成VIP邀请码管理员
### POST /api/admin/vip-codes
管理员生成VIP邀请码。
**请求头**
| 参数 | 说明 |
|------|------|
| Authorization | Bearer {admin_access_token} |
**请求参数JSON**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| expires_days | int | 否 | 30 | 邀请码有效天数 |
| count | int | 否 | 1 | 生成数量最多10个 |
**请求示例**
```json
{
"expires_days": 30,
"count": 5
}
```
**响应示例**
成功201
```json
{
"message": "成功生成 5 个VIP邀请码",
"codes": [
"VIP-A1B2C3D4",
"VIP-E5F6G7H8",
"VIP-I9J0K1L2",
"VIP-M3N4O5P6",
"VIP-Q7R8S9T0"
],
"expires_days": 30
}
```
---
## 5. 设置用户为VIP管理员
### POST /api/admin/users/{user_id}/set-vip
管理员直接将指定用户设置为VIP。
**请求头**
| 参数 | 说明 |
|------|------|
| Authorization | Bearer {admin_access_token} |
**路径参数**
| 参数 | 说明 |
|------|------|
| user_id | 用户ID |
**响应示例**
成功200
```json
{
"message": "用户 testuser 已升级为VIP",
"user": {
"user_id": 1,
"username": "testuser",
"email": "test@example.com",
"role": "vip",
"is_active": true,
"created_at": "2025-12-28T10:00:00",
"updated_at": "2025-12-28T11:00:00"
}
}
```
---
## 6. 撤销用户VIP管理员
### POST /api/admin/users/{user_id}/revoke-vip
管理员撤销指定用户的VIP权限。
**请求头**
| 参数 | 说明 |
|------|------|
| Authorization | Bearer {admin_access_token} |
**路径参数**
| 参数 | 说明 |
|------|------|
| user_id | 用户ID |
**响应示例**
成功200
```json
{
"message": "用户 testuser 的VIP权限已撤销",
"user": {
"user_id": 1,
"username": "testuser",
"email": "test@example.com",
"role": "normal",
"is_active": true,
"created_at": "2025-12-28T10:00:00",
"updated_at": "2025-12-28T12:00:00"
}
}
```
---
## VIP特权说明
| 特权 | 普通用户 | VIP用户 |
|------|----------|---------|
| 最大并发任务数 | 5 | 10 |
| 可用数据集 | 仅人脸数据集 | 全部数据集 |
| 上传微调功能 | ❌ | ✅ |
---
## 错误码说明
| HTTP状态码 | 说明 |
|------------|------|
| 400 | 请求参数错误或邀请码无效 |
| 401 | 未授权未登录或token无效 |
| 403 | 权限不足(非管理员访问管理接口) |
| 404 | 用户不存在 |
| 500 | 服务器内部错误 |

@ -7,6 +7,7 @@ from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from app import db
from app.database import User, Task, Image
from app.services.vip_service import VipService
admin_bp = Blueprint('admin', __name__)
@ -248,4 +249,84 @@ def get_system_stats():
}), 200
except Exception as e:
return jsonify({'error': f'获取系统统计失败: {str(e)}'}), 500
return jsonify({'error': f'获取系统统计失败: {str(e)}'}), 500
@admin_bp.route('/vip-codes', methods=['POST'])
@jwt_required()
@admin_required
def generate_vip_code():
"""生成VIP邀请码仅管理员"""
try:
data = request.get_json() or {}
expires_days = data.get('expires_days', 30)
count = data.get('count', 1)
if count < 1 or count > 10:
return jsonify({'error': '一次最多生成10个邀请码'}), 400
codes = []
for _ in range(count):
code = VipService.generate_vip_code(expires_days)
codes.append(code)
return jsonify({
'message': f'成功生成 {count} 个VIP邀请码',
'codes': codes,
'expires_days': expires_days
}), 201
except Exception as e:
return jsonify({'error': f'生成VIP邀请码失败: {str(e)}'}), 500
@admin_bp.route('/users/<int:user_id>/set-vip', methods=['POST'])
@jwt_required()
@admin_required
def set_user_vip(user_id):
"""管理员直接设置用户为VIP"""
try:
user = User.query.get(user_id)
if not user:
return jsonify({'error': '用户不存在'}), 404
# 设置为VIProle_id=2
user.role_id = 2
db.session.commit()
return jsonify({
'message': f'用户 {user.username} 已升级为VIP',
'user': user.to_dict()
}), 200
except Exception as e:
db.session.rollback()
return jsonify({'error': f'设置VIP失败: {str(e)}'}), 500
@admin_bp.route('/users/<int:user_id>/revoke-vip', methods=['POST'])
@jwt_required()
@admin_required
def revoke_user_vip(user_id):
"""管理员撤销用户VIP权限"""
try:
user = User.query.get(user_id)
if not user:
return jsonify({'error': '用户不存在'}), 404
# 检查是否是管理员
if user.role and user.role.role_code == 'admin':
return jsonify({'error': '不能撤销管理员的权限'}), 400
# 设置为普通用户role_id=3
user.role_id = 3
db.session.commit()
return jsonify({
'message': f'用户 {user.username} 的VIP权限已撤销',
'user': user.to_dict()
}), 200
except Exception as e:
db.session.rollback()
return jsonify({'error': f'撤销VIP失败: {str(e)}'}), 500

@ -51,13 +51,18 @@ def send_email_verification_code():
@auth_bp.route('/register', methods=['POST'])
def register():
"""用户注册"""
"""
用户注册
可选提供VIP邀请码如果邀请码有效则注册为VIP用户
"""
try:
data = request.get_json()
username = data.get('username')
password = data.get('password')
email = data.get('email')
code = data.get('code')
vip_code = data.get('vip_code') # 可选的VIP邀请码
# 验证输入
if not username or not password or not email:
return jsonify({'error': '用户名、密码和邮箱不能为空'}), 400
@ -76,23 +81,39 @@ def register():
return jsonify({'error': '该邮箱已被注册,同一邮箱只能注册一次'}), 400
verification_service = VerificationService()
if not code or not verification_service.verify_code(email, code, purpose = 'register'):
if not code or not verification_service.verify_code(email, code, purpose='register'):
return jsonify({'error': '验证码无效或已过期'}), 400
# 创建用户默认为普通用户role_id=3
user = User(username=username, email=email, role_id=3)
# 检查VIP邀请码如果提供
is_vip_register = False
if vip_code:
from app.services.vip_service import VipService
if VipService.verify_vip_code(vip_code):
is_vip_register = True
else:
return jsonify({'error': 'VIP邀请码无效或已过期'}), 400
# 创建用户role_id=2为VIProle_id=3为普通用户
role_id = 2 if is_vip_register else 3
user = User(username=username, email=email, role_id=role_id)
user.set_password(password)
db.session.add(user)
db.session.commit()
# 如果是VIP注册标记邀请码已使用
if is_vip_register:
from app.services.vip_service import VipService
VipService.mark_vip_code_used(vip_code, user.user_id)
# 创建用户默认配置
user_config = UserConfig(user_id=user.user_id)
db.session.add(user_config)
db.session.commit()
message = 'VIP注册成功' if is_vip_register else '注册成功'
return jsonify({
'message': '注册成功',
'message': message,
'user': user.to_dict()
}), 201
@ -245,3 +266,29 @@ def get_profile(current_user_id):
def logout():
"""用户登出客户端删除token即可"""
return jsonify({'message': '登出成功'}), 200
@auth_bp.route('/vip-status', methods=['GET'])
@int_jwt_required
def get_vip_status(current_user_id):
"""获取当前用户的VIP状态"""
try:
user = User.query.get(current_user_id)
if not user:
return jsonify({'error': '用户不存在'}), 404
role_code = user.role.role_code if user.role else 'user'
is_vip = role_code in ('vip', 'admin')
return jsonify({
'is_vip': is_vip,
'role': role_code,
'vip_features': {
'max_concurrent_tasks': user.role.max_concurrent_tasks if user.role else 1,
'can_use_all_datasets': is_vip,
'can_upload_finetune': is_vip
}
}), 200
except Exception as e:
return jsonify({'error': f'获取VIP状态失败: {str(e)}'}), 500

@ -1,13 +1,15 @@
"""
用户管理控制器
负责用户配置任务汇总等接口
负责用户配置任务汇总VIP升级等接口
"""
from flask import Blueprint, request, jsonify
from app import db
from app.controllers.auth_controller import int_jwt_required
from app.services.user_service import UserService
from app.services.vip_service import VipService
from app.database import User
user_bp = Blueprint('user', __name__)
@ -47,3 +49,52 @@ def update_user_config(current_user_id):
db.session.rollback()
return _json_error(f'更新配置失败: {exc}', 500)
@user_bp.route('/upgrade-vip', methods=['POST'])
@int_jwt_required
def upgrade_to_vip(current_user_id):
"""
升级为VIP用户
需要提供VIP邀请码进行验证
"""
try:
user = User.query.get(current_user_id)
if not user:
return _json_error('用户不存在', 404)
# 检查是否已经是VIP或管理员
role_code = user.role.role_code if user.role else 'user'
if role_code in ('vip', 'admin'):
return _json_error('您已经是VIP用户', 400)
data = request.get_json() or {}
vip_code = data.get('vip_code')
if not vip_code:
return _json_error('VIP邀请码不能为空')
# 验证VIP邀请码
if not VipService.verify_vip_code(vip_code):
return _json_error('VIP邀请码无效或已过期')
# 升级为VIProle_id=2
user.role_id = 2
db.session.commit()
# 标记VIP邀请码已使用
VipService.mark_vip_code_used(vip_code, user.user_id)
return jsonify({
'message': '恭喜您已成功升级为VIP用户',
'user': user.to_dict(),
'vip_features': {
'max_concurrent_tasks': user.role.max_concurrent_tasks if user.role else 1,
'can_use_all_datasets': True,
'can_upload_finetune': True
}
}), 200
except Exception as exc:
db.session.rollback()
return _json_error(f'升级VIP失败: {exc}', 500)

@ -0,0 +1,211 @@
"""
VIP服务模块
处理VIP邀请码验证VIP权限检查等功能
使用Redis存储邀请码
"""
import json
import logging
import secrets
from datetime import datetime, timedelta
from typing import Optional
from functools import wraps
from flask import jsonify
from app import db
from app.database import User
from app.services.cache import RedisClient
logger = logging.getLogger(__name__)
# Redis键前缀
VIP_CODE_PREFIX = "vip_code:"
class VipService:
"""VIP服务类"""
# 默认的VIP邀请码用于测试
DEFAULT_VIP_CODES = ['VIP2024', 'VIP2025', 'PREMIUM']
@classmethod
def _get_redis(cls) -> RedisClient:
"""获取Redis客户端"""
return RedisClient()
@classmethod
def _get_code_key(cls, code: str) -> str:
"""获取邀请码的Redis键"""
return f"{VIP_CODE_PREFIX}{code}"
@classmethod
def verify_vip_code(cls, code: str) -> bool:
"""
验证VIP邀请码是否有效
Args:
code: VIP邀请码
Returns:
True 如果邀请码有效否则 False
"""
if not code:
return False
# 检查是否是默认的测试邀请码
if code in cls.DEFAULT_VIP_CODES:
redis_client = cls._get_redis()
key = cls._get_code_key(code)
data = redis_client.get(key)
# 默认邀请码如果没有被使用过,则有效
if data is None:
return True
# 如果已存储,检查是否已使用
try:
code_info = json.loads(data)
return not code_info.get('used', False)
except (json.JSONDecodeError, TypeError):
return True
# 检查动态生成的邀请码
redis_client = cls._get_redis()
key = cls._get_code_key(code)
data = redis_client.get(key)
if data is None:
return False
try:
code_info = json.loads(data)
# 检查是否已使用
if code_info.get('used'):
return False
return True
except (json.JSONDecodeError, TypeError):
return False
@classmethod
def mark_vip_code_used(cls, code: str, user_id: int) -> bool:
"""
标记VIP邀请码已使用
Args:
code: VIP邀请码
user_id: 使用该邀请码的用户ID
Returns:
True 如果标记成功否则 False
"""
if not code:
return False
if code in cls.DEFAULT_VIP_CODES:
return True
redis_client = cls._get_redis()
key = cls._get_code_key(code)
code_info = {
'used': True,
'used_by': user_id,
'used_at': datetime.utcnow().isoformat()
}
# 已使用的邀请码保留90天记录
success = redis_client.set(key, json.dumps(code_info), ex=90 * 24 * 3600)
if success:
logger.info(f"VIP邀请码 {code} 已被用户 {user_id} 使用")
return success
@classmethod
def generate_vip_code(cls, expires_days: int = 30) -> str:
"""
生成新的VIP邀请码
Args:
expires_days: 邀请码有效天数
Returns:
生成的邀请码
"""
code = f"VIP-{secrets.token_hex(4).upper()}"
redis_client = cls._get_redis()
key = cls._get_code_key(code)
code_info = {
'used': False,
'used_by': None,
'used_at': None,
'created_at': datetime.utcnow().isoformat(),
'expires_days': expires_days
}
# 设置过期时间
expires_seconds = expires_days * 24 * 3600
redis_client.set(key, json.dumps(code_info), ex=expires_seconds)
logger.info(f"生成新的VIP邀请码: {code}, 有效期: {expires_days}")
return code
@classmethod
def is_user_vip(cls, user: User) -> bool:
"""
检查用户是否为VIP
Args:
user: 用户对象
Returns:
True 如果用户是VIP或管理员否则 False
"""
if not user or not user.role:
return False
return user.role.role_code in ('vip', 'admin')
@classmethod
def get_vip_features(cls, user: User) -> dict:
"""
获取用户的VIP特权信息
Args:
user: 用户对象
Returns:
VIP特权字典
"""
is_vip = cls.is_user_vip(user)
return {
'is_vip': is_vip,
'max_concurrent_tasks': user.role.max_concurrent_tasks if user and user.role else 1,
'can_use_all_datasets': is_vip,
'can_upload_finetune': is_vip,
'priority_queue': is_vip
}
def vip_required(f):
"""
VIP权限装饰器
用于保护需要VIP权限的接口
"""
@wraps(f)
def decorated_function(*args, **kwargs):
# 从kwargs中获取current_user_id由int_jwt_required装饰器注入
current_user_id = kwargs.get('current_user_id')
if not current_user_id:
return jsonify({'error': '未授权访问'}), 401
user = User.query.get(current_user_id)
if not user:
return jsonify({'error': '用户不存在'}), 404
if not VipService.is_user_vip(user):
return jsonify({
'error': '此功能仅限VIP用户使用',
'upgrade_hint': '请使用VIP邀请码升级为VIP用户'
}), 403
return f(*args, **kwargs)
return decorated_function
Loading…
Cancel
Save