new file: app.py new file: app/ali_oss.py new file: app/app.py new file: app/app_common.py new file: app/config.py new file: app/coze.py new file: app/generate_api.py new file: app/html_crypto.py new file: app/main.py new file: app/web_source/admin.html new file: app/web_source/brand.html new file: app/web_source/home.html new file: app/web_source/index.html new file: app/web_source/login.html new file: app_common.py new file: blueprints/__init__.py new file: blueprints/__pycache__/__init__.cpython-311.pyc new file: blueprints/__pycache__/__init__.cpython-39.pyc new file: blueprints/__pycache__/admin.cpython-311.pyc new file: blueprints/__pycache__/admin.cpython-39.pyc new file: blueprints/__pycache__/auth.cpython-311.pyc new file: blueprints/__pycache__/auth.cpython-39.pyc new file: blueprints/__pycache__/brand.cpython-311.pyc new file: blueprints/__pycache__/brand.cpython-39.pyc new file: blueprints/__pycache__/image.cpython-311.pyc new file: blueprints/__pycache__/image.cpython-39.pyc new file: blueprints/__pycache__/main.cpython-311.pyc new file: blueprints/__pycache__/main.cpython-39.pyc new file: blueprints/admin.py new file: blueprints/auth.py new file: blueprints/brand.py new file: blueprints/image.py new file: blueprints/main.py new file: brand_spider/__pycache__/main.cpython-39.pyc new file: brand_spider/__pycache__/web_dec.cpython-39.pyc new file: brand_spider/main.py new file: brand_spider/web_dec.py new file: config.py new file: coze.py new file: generate_api.py new file: html_crypto.py new file: main.py new file: static/bg.jpg new file: "static/\345\223\201\347\211\214\346\226\207\346\241\243\346\240\274\345\274\217_\346\250\241\346\235\277.xlsx" new file: "static/\346\250\241\346\235\2772-\344\273\245\346\226\207\344\273\266\345\244\271\346\226\271\345\274\217\344\270\212\344\274\240.zip" new file: tool/.device_id new file: tool/__pycache__/devices.cpython-311.pyc new file: tool/__pycache__/devices.cpython-39.pyc new file: tool/devices.py
363 lines
16 KiB
Python
363 lines
16 KiB
Python
"""
|
|
管理员蓝图:用户管理(列表/创建/更新/删除)、生成历史、管理页
|
|
"""
|
|
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/<int:uid>', 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/<int:uid>', 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/<int:uid>/column-permissions')
|
|
@login_required
|
|
def admin_user_column_permissions(uid):
|
|
"""获取指定用户的栏目权限列表:当前用户只能查自己,管理员可查任意用户。超级管理员返回全部栏目。"""
|
|
current_uid = session.get('user_id')
|
|
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()
|
|
if user_row and (user_row.get('role') or '').strip() == 'super_admin':
|
|
cur.execute("""
|
|
SELECT id, name, column_key, created_at FROM columns ORDER BY id
|
|
""")
|
|
rows = cur.fetchall()
|
|
else:
|
|
cur.execute("""
|
|
SELECT c.id, c.name, c.column_key, c.created_at
|
|
FROM columns c
|
|
INNER JOIN user_column_permission ucp ON ucp.column_id = c.id
|
|
WHERE ucp.user_id = %s
|
|
ORDER BY c.id
|
|
""", (uid,))
|
|
rows = cur.fetchall()
|
|
conn.close()
|
|
items = [
|
|
{
|
|
'id': r['id'],
|
|
'name': r['name'],
|
|
'column_key': r['column_key'],
|
|
'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)})
|