""" 管理员蓝图:用户管理(列表/创建/更新/删除)、生成历史、管理页 """ import json import pymysql from flask import Blueprint, request, jsonify from werkzeug.security import generate_password_hash from app_common import get_db, _render_html, _get_current_admin_role, admin_required, login_required from flask import session admin_bp = Blueprint('admin', __name__) def _parse_json(val, default=None): if val is None: return default if default is not None else [] if isinstance(val, (list, dict)): return val try: return json.loads(val) except Exception: return default if default is not None else [] @admin_bp.route('/admin') @login_required @admin_required def admin_page(): return _render_html('admin.html') @admin_bp.route('/api/admin/users') @admin_required def admin_list_users(): """分页获取用户列表;支持用户名模糊搜索、指定管理员所属普通用户筛选""" role, current_row = _get_current_admin_role() if not role: return jsonify({'success': False, 'error': '需要管理员权限'}), 403 page = max(1, int(request.args.get('page', 1))) page_size = min(50, max(5, int(request.args.get('page_size', 15)))) offset = (page - 1) * page_size search_username = (request.args.get('username') or request.args.get('search') or '').strip() created_by_id_arg = request.args.get('created_by_id') or request.args.get('admin_id') created_by_id = int(created_by_id_arg) if created_by_id_arg and str(created_by_id_arg).isdigit() else None if role != 'super_admin': created_by_id = None try: conn = get_db() with conn.cursor() as cur: if role == 'super_admin': where_parts = ["1=1"] params = [] if search_username: where_parts.append("u.username LIKE %s") params.append("%" + search_username + "%") if created_by_id is not None: where_parts.append("u.created_by_id = %s") params.append(created_by_id) where_sql = " AND ".join(where_parts) cur.execute( """SELECT u.id, u.username, u.is_admin, u.role, u.created_at, u.created_by_id, creator.username AS creator_username FROM users u LEFT JOIN users creator ON creator.id = u.created_by_id WHERE """ + where_sql + """ ORDER BY u.id LIMIT %s OFFSET %s""", tuple(params) + (page_size, offset), ) rows = cur.fetchall() cur.execute("SELECT COUNT(*) as total FROM users u WHERE " + where_sql, tuple(params)) total = cur.fetchone()['total'] cur.execute("SELECT id, username FROM users WHERE role = 'admin' ORDER BY id") admins = [{'id': r['id'], 'username': r['username']} for r in cur.fetchall()] else: admin_id = current_row['id'] where_parts = ["(u.id = %s OR (u.role = 'normal' AND u.created_by_id = %s))"] params = [admin_id, admin_id] if search_username: where_parts.append("u.username LIKE %s") params.append("%" + search_username + "%") where_sql = " AND ".join(where_parts) cur.execute( """SELECT u.id, u.username, u.is_admin, u.role, u.created_at, u.created_by_id, creator.username AS creator_username FROM users u LEFT JOIN users creator ON creator.id = u.created_by_id WHERE """ + where_sql + """ ORDER BY u.id LIMIT %s OFFSET %s""", tuple(params) + (page_size, offset), ) rows = cur.fetchall() cur.execute( "SELECT COUNT(*) as total FROM users u WHERE " + where_sql, tuple(params), ) total = cur.fetchone()['total'] admins = [] items = [ { 'id': r['id'], 'username': r['username'], 'is_admin': bool(r.get('is_admin')), 'role': r.get('role') or 'normal', 'created_by_id': r.get('created_by_id'), 'creator_username': r.get('creator_username') or '', 'created_at': r['created_at'].strftime('%Y-%m-%d %H:%M') if r.get('created_at') else '', } for r in rows ] conn.close() payload = { 'success': True, 'items': items, 'total': total, 'page': page, 'page_size': page_size, 'current_user_role': role, 'admins': admins, } return jsonify(payload) except Exception as e: return jsonify({'success': False, 'error': str(e)}) @admin_bp.route('/api/admin/user', methods=['POST']) @admin_required def admin_create_user(): data = request.get_json() or {} username = (data.get('username') or '').strip() password = data.get('password') or '' role, current_row = _get_current_admin_role() if not role: return jsonify({'success': False, 'error': '需要管理员权限'}), 403 want_role = (data.get('role') or 'normal').strip() or 'normal' if want_role not in ('admin', 'normal'): want_role = 'normal' if role == 'admin' and want_role == 'admin': return jsonify({'success': False, 'error': '仅超级管理员可创建管理员'}) if want_role == 'admin': want_created_by = current_row['id'] elif role == 'super_admin': want_created_by = data.get('created_by_id') else: want_created_by = current_row['id'] if not username or not password: return jsonify({'success': False, 'error': '用户名和密码不能为空'}) if len(username) < 2: return jsonify({'success': False, 'error': '用户名至少2个字符'}) if len(password) < 6: return jsonify({'success': False, 'error': '密码至少6个字符'}) if want_role == 'normal' and role == 'super_admin' and want_created_by is None: try: conn = get_db() with conn.cursor() as cur: cur.execute("SELECT id FROM users WHERE role = 'admin' ORDER BY id LIMIT 1") r = cur.fetchone() conn.close() want_created_by = r['id'] if r else current_row['id'] except Exception: want_created_by = current_row['id'] if want_role == 'normal' and want_created_by is None: want_created_by = current_row['id'] is_admin = 1 if want_role in ('super_admin', 'admin') else 0 pwd_hash = generate_password_hash(password, method='pbkdf2:sha256') try: conn = get_db() with conn.cursor() as cur: cur.execute( "INSERT INTO users (username, password_hash, is_admin, role, created_by_id) VALUES (%s, %s, %s, %s, %s)", (username, pwd_hash, is_admin, want_role, want_created_by), ) conn.commit() conn.close() return jsonify({'success': True, 'msg': '用户创建成功'}) except pymysql.IntegrityError: return jsonify({'success': False, 'error': '用户名已存在'}) except Exception as e: return jsonify({'success': False, 'error': str(e)}) @admin_bp.route('/api/admin/user/', methods=['PUT']) @admin_required def admin_update_user(uid): data = request.get_json() or {} password = data.get('password') want_role = (data.get('role') or '').strip() or data.get('role') role, current_row = _get_current_admin_role() if not role: return jsonify({'success': False, 'error': '需要管理员权限'}), 403 if want_role is None and not password: return jsonify({'success': False, 'error': '请提供要修改的内容'}) try: conn = get_db() with conn.cursor() as cur: cur.execute("SELECT id, role, created_by_id FROM users WHERE id = %s", (uid,)) target = cur.fetchone() if not target: conn.close() return jsonify({'success': False, 'error': '用户不存在'}) if role == 'admin': if target['role'] != 'normal' or target.get('created_by_id') != current_row['id']: conn.close() return jsonify({'success': False, 'error': '只能编辑自己创建的普通用户'}), 403 want_role = None else: if target.get('role') == 'super_admin': conn.close() return jsonify({'success': False, 'error': '不能修改超级管理员'}) if want_role == 'super_admin': return jsonify({'success': False, 'error': '不能将用户设为超级管理员'}) if want_role not in ('admin', 'normal', None, ''): want_role = None if password: if len(password) < 6: conn.close() return jsonify({'success': False, 'error': '密码至少6个字符'}) pwd_hash = generate_password_hash(password, method='pbkdf2:sha256') cur.execute("UPDATE users SET password_hash = %s WHERE id = %s", (pwd_hash, uid)) if want_role is not None and want_role != '': is_admin = 1 if want_role == 'admin' else 0 cur.execute( "UPDATE users SET is_admin = %s, role = %s WHERE id = %s", (is_admin, want_role, uid), ) conn.commit() conn.close() return jsonify({'success': True, 'msg': '更新成功'}) except Exception as e: return jsonify({'success': False, 'error': str(e)}) @admin_bp.route('/api/admin/user/', methods=['DELETE']) @admin_required def admin_delete_user(uid): from flask import session if session.get('user_id') == uid: return jsonify({'success': False, 'error': '不能删除当前登录账号'}) role, current_row = _get_current_admin_role() if not role: return jsonify({'success': False, 'error': '需要管理员权限'}), 403 try: conn = get_db() with conn.cursor() as cur: cur.execute("SELECT id, role, created_by_id FROM users WHERE id = %s", (uid,)) target = cur.fetchone() if not target: conn.close() return jsonify({'success': False, 'error': '用户不存在'}) if target.get('role') == 'super_admin': conn.close() return jsonify({'success': False, 'error': '不能删除超级管理员'}) if role == 'admin': if target.get('role') != 'normal' or target.get('created_by_id') != current_row['id']: conn.close() return jsonify({'success': False, 'error': '只能删除自己创建的普通用户'}), 403 cur.execute("DELETE FROM users WHERE id = %s", (uid,)) affected = cur.rowcount conn.commit() conn.close() if affected == 0: return jsonify({'success': False, 'error': '用户不存在'}) return jsonify({'success': True, 'msg': '删除成功'}) except Exception as e: return jsonify({'success': False, 'error': str(e)}) @admin_bp.route('/api/admin/user//column-permissions') @login_required def admin_user_column_permissions(uid): """获取指定用户的栏目权限列表:当前用户只能查自己,管理员可查任意用户。超级管理员返回全部栏目。""" current_uid = session.get('user_id') menu_type = (request.args.get('menu_type') or '').strip().lower() if current_uid != uid: role, _ = _get_current_admin_role() if not role: return jsonify({'success': False, 'error': '无权查看该用户的栏目权限'}), 403 try: conn = get_db() with conn.cursor() as cur: cur.execute("SELECT role FROM users WHERE id = %s", (uid,)) user_row = cur.fetchone() valid_menu_type = menu_type if menu_type in ('app', 'admin') else '' if user_row and (user_row.get('role') or '').strip() == 'super_admin': sql = """ SELECT id, name, column_key, menu_type, route_path, sort_order, created_at FROM columns """ params = [] if valid_menu_type: sql += " WHERE menu_type = %s" params.append(valid_menu_type) sql += " ORDER BY sort_order ASC, id ASC" cur.execute(sql, tuple(params)) rows = cur.fetchall() else: sql = """ SELECT c.id, c.name, c.column_key, c.menu_type, c.route_path, c.sort_order, c.created_at FROM columns c INNER JOIN user_column_permission ucp ON ucp.column_id = c.id WHERE ucp.user_id = %s """ params = [uid] if valid_menu_type: sql += " AND c.menu_type = %s" params.append(valid_menu_type) sql += " ORDER BY c.sort_order ASC, c.id ASC" cur.execute(sql, tuple(params)) rows = cur.fetchall() conn.close() items = [ { 'id': r['id'], 'name': r['name'], 'column_key': r['column_key'], 'menu_type': r.get('menu_type') or 'app', 'route_path': r.get('route_path') or '', 'sort_order': r.get('sort_order') or 0, 'created_at': r['created_at'].strftime('%Y-%m-%d %H:%M') if r.get('created_at') else '', } for r in rows ] return jsonify({'success': True, 'items': items}) except Exception as e: return jsonify({'success': False, 'error': str(e)}) @admin_bp.route('/api/admin/history') @admin_required def admin_history(): """管理员分页获取所有生成记录,支持按用户和时间筛选""" page = max(1, int(request.args.get('page', 1))) page_size = min(50, max(10, int(request.args.get('page_size', 15)))) offset = (page - 1) * page_size user_id = request.args.get('user_id', type=int) time_start = (request.args.get('time_start') or '').strip() time_end = (request.args.get('time_end') or '').strip() conditions, params = [], [] if user_id: conditions.append("h.user_id = %s") params.append(user_id) if time_start: conditions.append("h.created_at >= %s") params.append(time_start) if time_end: conditions.append("h.created_at <= %s") params.append(time_end + ' 23:59:59' if len(time_end) <= 10 else time_end) where_clause = " AND ".join(conditions) if conditions else "1=1" params_count = params[:] params.extend([page_size, offset]) try: conn = get_db() with conn.cursor() as cur: cur.execute( """SELECT h.id, h.user_id, h.created_at, h.panel_type, h.original_urls, h.params, h.result_urls, h.long_image_url, u.username FROM image_history h LEFT JOIN users u ON h.user_id = u.id WHERE """ + where_clause + """ ORDER BY h.created_at DESC LIMIT %s OFFSET %s""", params, ) rows = cur.fetchall() cur.execute("SELECT COUNT(*) as total FROM image_history h WHERE " + where_clause, params_count) total = cur.fetchone()['total'] conn.close() items = [] for r in rows: items.append({ 'id': r['id'], 'user_id': r['user_id'], 'username': r.get('username') or '-', 'created_at': r['created_at'].strftime('%Y-%m-%d %H:%M') if r['created_at'] else '', 'panel_type': r['panel_type'] or '', 'original_urls': _parse_json(r['original_urls'], []), 'params': _parse_json(r['params'], {}), 'result_urls': _parse_json(r['result_urls'], []), 'long_image_url': (r.get('long_image_url') or '').strip() or None, }) return jsonify({'success': True, 'items': items, 'total': total, 'page': page, 'page_size': page_size}) except Exception as e: return jsonify({'success': False, 'error': str(e)})