from flask import Blueprint, request, jsonify, session from werkzeug.security import generate_password_hash, check_password_hash from app.db import get_db import functools bp = Blueprint('auth', __name__, url_prefix='/api') def login_required(f): """登录验证装饰器""" @functools.wraps(f) def wrapped(*args, **kwargs): if 'user_id' not in session: return jsonify({'error': '未登录,请先登录'}), 401 return f(*args, **kwargs) return wrapped def admin_required(f): """管理员验证装饰器""" @functools.wraps(f) def wrapped(*args, **kwargs): if 'user_id' not in session: return jsonify({'error': '未登录'}), 401 if session.get('role') != 'admin': return jsonify({'error': '无权限,仅管理员可操作'}), 403 return f(*args, **kwargs) return wrapped @bp.route('/login', methods=['POST']) def login(): data = request.get_json() username = data.get('username', '').strip() password = data.get('password', '') if not username or not password: return jsonify({'error': '用户名和密码不能为空'}), 400 conn = get_db() user = conn.execute( 'SELECT * FROM users WHERE username = ?', (username,) ).fetchone() conn.close() if not user or not check_password_hash(user['password_hash'], password): return jsonify({'error': '用户名或密码错误'}), 401 if not user['is_active']: return jsonify({'error': '账号已被停用,请联系管理员'}), 403 session.clear() session['user_id'] = user['id'] session['username'] = user['username'] session['display_name'] = user['display_name'] session['role'] = user['role'] session.permanent = True return jsonify({ 'success': True, 'user': { 'id': user['id'], 'username': user['username'], 'display_name': user['display_name'], 'role': user['role'] } }) @bp.route('/logout', methods=['POST']) def logout(): session.clear() return jsonify({'success': True}) @bp.route('/me', methods=['GET']) @login_required def me(): return jsonify({ 'user': { 'id': session['user_id'], 'username': session['username'], 'display_name': session['display_name'], 'role': session['role'] } }) @bp.route('/register', methods=['POST']) def register(): """邀请码注册""" data = request.get_json() invite_code = data.get('invite_code', '').strip() username = data.get('username', '').strip() password = data.get('password', '') display_name = data.get('display_name', '').strip() if not invite_code or not username or not password: return jsonify({'error': '邀请码、用户名、密码均不能为空'}), 400 if len(username) < 2 or len(username) > 20: return jsonify({'error': '用户名长度2-20个字符'}), 400 if len(password) < 6: return jsonify({'error': '密码至少6位'}), 400 conn = get_db() try: # 验证邀请码 invite = conn.execute( 'SELECT * FROM invite_codes WHERE code = ? AND is_used = 0', (invite_code,) ).fetchone() if not invite: return jsonify({'error': '邀请码无效或已被使用'}), 400 # 检查用户名是否已存在 existing = conn.execute( 'SELECT id FROM users WHERE username = ?', (username,) ).fetchone() if existing: return jsonify({'error': '用户名已存在,请换一个'}), 400 # 如果用户没填显示名,用邀请码绑定的编导姓名 if not display_name: display_name = invite['editor_name'] # 创建用户 cursor = conn.execute( 'INSERT INTO users (username, display_name, password_hash, role) VALUES (?, ?, ?, ?)', (username, display_name, generate_password_hash(password), 'editor') ) user_id = cursor.lastrowid # 标记邀请码已使用 conn.execute( 'UPDATE invite_codes SET is_used = 1, used_by_user_id = ?, used_at = CURRENT_TIMESTAMP WHERE id = ?', (user_id, invite['id']) ) conn.commit() return jsonify({ 'success': True, 'message': f'注册成功!欢迎 {display_name}' }) except Exception as e: conn.rollback() return jsonify({'error': f'注册失败: {str(e)}'}), 500 finally: conn.close()