feat: 登录验证 + 邀请码注册 + 用量监控后台
- 新增用户认证系统(登录/登出/邀请码注册) - 新增管理后台(邀请码管理/用户管理/使用记录/用量统计) - 合成接口加登录验证,每次调用记录用户和稿件内容 - SQLite存储用户数据和使用日志 - 默认admin账号: simonkoson Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user