223761e717
- 新增用户认证系统(登录/登出/邀请码注册) - 新增管理后台(邀请码管理/用户管理/使用记录/用量统计) - 合成接口加登录验证,每次调用记录用户和稿件内容 - SQLite存储用户数据和使用日志 - 默认admin账号: simonkoson Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
151 lines
4.5 KiB
Python
151 lines
4.5 KiB
Python
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()
|