完成后端架构重构等

This commit is contained in:
super
2026-04-23 15:25:41 +08:00
parent 46f46039ea
commit 34bc980eea
76 changed files with 3242 additions and 1111 deletions

View File

@@ -12,7 +12,7 @@ import pymysql
from werkzeug.security import generate_password_hash
from utils.db import get_db
from utils.auth import admin_required, get_current_admin_role
from utils.auth import admin_required, login_required, get_current_admin_role
ALLOWED_DEDUPE_TOTAL_DATA_ADMIN_USERNAMES = {'刘丽泓'}
@@ -353,8 +353,82 @@ def _load_current_admin_menu_items():
return role, current_row, items, None
def _load_current_backend_menu_items():
role, current_row = get_current_admin_role()
if not role or not current_row:
return None, None, None, (jsonify({'success': False, 'error': '需要登录'}), 403)
user_id = _get_current_admin_id(current_row)
if not user_id:
return role, current_row, None, (jsonify({'success': False, 'error': '当前登录用户缺少有效ID'}), 400)
result, error_response, status = _proxy_backend_java(
'GET',
f'/api/admin/permission-users/{user_id}/column-permissions',
params={'menuType': 'admin'},
)
if error_response is not None:
return role, current_row, None, (error_response, status)
items = [_format_permission_item(item) for item in (result.get('data') or [])]
return role, current_row, items, None
def _ensure_backend_menu_access(*menu_names):
role, current_row = get_current_admin_role()
if role == 'super_admin':
return role, current_row, None
if not role or not current_row:
return role, current_row, (jsonify({'success': False, 'error': '需要登录'}), 403)
key_set, route_set = _get_current_admin_permission_sets()
for menu_name in menu_names:
config = ADMIN_MENU_ACCESS_CONFIG.get(menu_name) or {}
column_key = (config.get('column_key') or '').strip()
route_path = (config.get('route_path') or '').strip()
if (column_key and column_key in key_set) or (route_path and route_path in route_set):
return role, current_row, None
fallback_menu = menu_names[0] if menu_names else ''
fallback_config = ADMIN_MENU_ACCESS_CONFIG.get(fallback_menu) or {}
error_message = fallback_config.get('error') or '无权访问当前模块'
return role, current_row, (jsonify({'success': False, 'error': error_message}), 403)
def _get_column_ids_by_route_paths(route_paths, menu_type='admin'):
normalized_paths = [(path or '').strip() for path in (route_paths or []) if (path or '').strip()]
if not normalized_paths:
return []
conn = get_db()
try:
with conn.cursor() as cur:
placeholders = ','.join(['%s'] * len(normalized_paths))
cur.execute(
f"SELECT id FROM columns WHERE menu_type = %s AND route_path IN ({placeholders}) ORDER BY id ASC",
[menu_type] + normalized_paths,
)
rows = cur.fetchall()
return [int(row['id']) for row in rows if row.get('id') is not None]
finally:
conn.close()
def _grant_backend_menu_permissions(user_ids, route_paths):
normalized_user_ids = [int(uid) for uid in (user_ids or []) if str(uid).isdigit() and int(uid) > 0]
column_ids = _get_column_ids_by_route_paths(route_paths, 'admin')
if not normalized_user_ids or not column_ids:
return
conn = get_db()
try:
with conn.cursor() as cur:
for user_id in normalized_user_ids:
for column_id in column_ids:
cur.execute(
"INSERT IGNORE INTO user_column_permission (user_id, column_id) VALUES (%s, %s)",
(user_id, column_id),
)
conn.commit()
finally:
conn.close()
@admin_api.route('/current-user')
@admin_required
@login_required
def get_admin_current_user():
role, current_row = get_current_admin_role()
if not role or not current_row:
@@ -370,16 +444,16 @@ def get_admin_current_user():
@admin_api.route('/current-user/menus')
@admin_required
@login_required
def get_admin_current_user_menus():
_, _, items, denied = _load_current_admin_menu_items()
_, _, items, denied = _load_current_backend_menu_items()
if denied:
return denied
return jsonify({'success': True, 'items': items})
@admin_api.route('/logout', methods=['POST'])
@admin_required
@login_required
def admin_logout():
session.clear()
return jsonify({'success': True, 'msg': '退出成功', 'redirect': '/login'})
@@ -388,17 +462,14 @@ def admin_logout():
# ---------- 用户管理 ----------
@admin_api.route('/users')
@admin_required
@login_required
def list_users():
"""分页获取用户列表,支持用户名模糊搜索和按管理员归属筛选普通用户"""
"""分页获取用户列表,支持用户名模糊搜索和按管理员归属筛选普通用户"""
role, current_row = get_current_admin_role()
if not role:
return jsonify({'success': False, 'error': '需要管理员权限'}), 403
return jsonify({'success': False, 'error': '需要登录'}), 403
page = max(1, int(request.args.get('page', 1)))
_, _, denied = _ensure_admin_menu_access('users', 'history', 'shop-manage', 'skip-price-asin')
if denied:
return denied
_, _, denied = _ensure_admin_menu_access('users', 'history', 'shop-manage', 'skip-price-asin')
_, _, denied = _ensure_backend_menu_access('users', 'history', 'shop-manage', 'skip-price-asin')
if denied:
return denied
page_size = min(999, max(5, int(request.args.get('page_size', 15))))
@@ -407,6 +478,7 @@ def list_users():
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
can_view_all_users = role == 'super_admin' or (current_row and (current_row.get('username') or '').strip().lower() == 'admin')
can_view_child_users = role == 'admin' and not can_view_all_users
if not can_view_all_users:
created_by_id = None
try:
@@ -435,7 +507,7 @@ def list_users():
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:
elif can_view_child_users:
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]
@@ -458,6 +530,28 @@ def list_users():
)
total = cur.fetchone()['total']
admins = []
else:
where_parts = ["u.id = %s"]
params = [current_row['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'],
@@ -485,7 +579,6 @@ def list_users():
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
@admin_api.route('/user', methods=['POST'])
@admin_required
def create_user():
@@ -1341,9 +1434,9 @@ def delete_dedupe_total_data(item_id):
# ---------- 店铺管理 ----------
@admin_api.route('/shop-manages')
@admin_required
@login_required
def list_shop_manages():
role, current_row, denied = _ensure_admin_menu_access('shop-manage')
role, current_row, denied = _ensure_backend_menu_access('shop-manage')
if denied:
return denied
page = max(1, int(request.args.get('page', 1)))
@@ -1397,9 +1490,9 @@ def list_shop_manages():
@admin_api.route('/shop-manage', methods=['POST'])
@admin_required
@login_required
def create_shop_manage():
role, current_row, denied = _ensure_admin_menu_access('shop-manage')
role, current_row, denied = _ensure_backend_menu_access('shop-manage')
if denied:
return denied
data = request.get_json() or {}
@@ -1441,9 +1534,9 @@ def create_shop_manage():
@admin_api.route('/shop-manage/<int:item_id>', methods=['PUT'])
@admin_required
@login_required
def update_shop_manage(item_id):
role, current_row, denied = _ensure_admin_menu_access('shop-manage')
role, current_row, denied = _ensure_backend_menu_access('shop-manage')
if denied:
return denied
data = request.get_json() or {}
@@ -1483,9 +1576,9 @@ def update_shop_manage(item_id):
@admin_api.route('/shop-manage/<int:item_id>', methods=['DELETE'])
@admin_required
@login_required
def delete_shop_manage(item_id):
role, current_row, denied = _ensure_admin_menu_access('shop-manage')
role, current_row, denied = _ensure_backend_menu_access('shop-manage')
if denied:
return denied
result, error_response, status = _proxy_backend_java(
@@ -1505,9 +1598,9 @@ def delete_shop_manage(item_id):
@admin_api.route('/shop-manage-groups')
@admin_required
@login_required
def list_shop_manage_groups():
role, current_row, denied = _ensure_admin_menu_access('shop-manage', 'skip-price-asin')
role, current_row, denied = _ensure_backend_menu_access('shop-manage', 'skip-price-asin')
if denied:
return denied
params = {}
@@ -1543,12 +1636,13 @@ def list_shop_manage_groups():
@admin_api.route('/shop-manage-group', methods=['POST'])
@admin_required
@login_required
def create_shop_manage_group():
role, current_row, denied = _ensure_admin_menu_access('shop-manage', 'skip-price-asin')
role, current_row, denied = _ensure_backend_menu_access('shop-manage', 'skip-price-asin')
if denied:
return denied
data = request.get_json() or {}
grant_menu_routes = data.get('grant_menu_routes') or []
payload = {
'groupName': (data.get('group_name') or '').strip(),
'memberUserIds': data.get('member_user_ids') or [],
@@ -1565,6 +1659,7 @@ def create_shop_manage_group():
)
if error_response is not None:
return error_response, status
_grant_backend_menu_permissions(data.get('member_user_ids') or [], grant_menu_routes)
item = result.get('data') or {}
return jsonify({
'success': True,
@@ -1586,12 +1681,13 @@ def create_shop_manage_group():
@admin_api.route('/shop-manage-group/<int:item_id>', methods=['PUT'])
@admin_required
@login_required
def update_shop_manage_group(item_id):
role, current_row, denied = _ensure_admin_menu_access('shop-manage', 'skip-price-asin')
role, current_row, denied = _ensure_backend_menu_access('shop-manage', 'skip-price-asin')
if denied:
return denied
data = request.get_json() or {}
grant_menu_routes = data.get('grant_menu_routes') or []
payload = {
'groupName': (data.get('group_name') or '').strip(),
'memberUserIds': data.get('member_user_ids') or [],
@@ -1607,6 +1703,7 @@ def update_shop_manage_group(item_id):
)
if error_response is not None:
return error_response, status
_grant_backend_menu_permissions(data.get('member_user_ids') or [], grant_menu_routes)
item = result.get('data') or {}
return jsonify({
'success': True,
@@ -1628,9 +1725,9 @@ def update_shop_manage_group(item_id):
@admin_api.route('/skip-price-asins')
@admin_required
@login_required
def list_skip_price_asins():
role, current_row, denied = _ensure_admin_menu_access('skip-price-asin')
role, current_row, denied = _ensure_backend_menu_access('skip-price-asin')
if denied:
return denied
page = max(1, int(request.args.get('page', 1)))
@@ -1668,10 +1765,15 @@ def list_skip_price_asins():
'group_name': item.get('groupName') or '',
'shop_name': item.get('shopName') or '',
'asin_de': item.get('asinDe') or '',
'minimum_price_de': item.get('minimumPriceDe'),
'asin_uk': item.get('asinUk') or '',
'minimum_price_uk': item.get('minimumPriceUk'),
'asin_fr': item.get('asinFr') or '',
'minimum_price_fr': item.get('minimumPriceFr'),
'asin_it': item.get('asinIt') or '',
'minimum_price_it': item.get('minimumPriceIt'),
'asin_es': item.get('asinEs') or '',
'minimum_price_es': item.get('minimumPriceEs'),
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
'updated_at': (item.get('updatedAt') or '').replace('T', ' ')[:16],
}
@@ -1687,25 +1789,34 @@ def list_skip_price_asins():
@admin_api.route('/skip-price-asin', methods=['POST'])
@admin_required
@login_required
def create_skip_price_asin():
role, current_row, denied = _ensure_admin_menu_access('skip-price-asin')
role, current_row, denied = _ensure_backend_menu_access('skip-price-asin')
if denied:
return denied
data = request.get_json() or {}
asin_mappings = data.get('asin_mappings') or {}
minimum_price_mappings = data.get('minimum_price_mappings') or {}
fallback_asin = (data.get('asin') or '').strip()
if not fallback_asin and isinstance(asin_mappings, dict):
for value in asin_mappings.values():
fallback_asin = (value or '').strip()
if fallback_asin:
break
fallback_minimum_price = data.get('minimum_price')
if fallback_minimum_price in ('', None) and isinstance(minimum_price_mappings, dict):
for value in minimum_price_mappings.values():
if value not in ('', None):
fallback_minimum_price = value
break
payload = {
'groupId': data.get('group_id'),
'shopName': (data.get('shop_name') or '').strip(),
'countries': data.get('countries') or [],
'asin': fallback_asin,
'asinMappings': asin_mappings,
'minimumPrice': fallback_minimum_price,
'minimumPriceMappings': minimum_price_mappings,
}
result, error_response, status = _proxy_backend_java(
'POST',
@@ -1728,10 +1839,15 @@ def create_skip_price_asin():
'group_name': item.get('groupName') or '',
'shop_name': item.get('shopName') or '',
'asin_de': item.get('asinDe') or '',
'minimum_price_de': item.get('minimumPriceDe'),
'asin_uk': item.get('asinUk') or '',
'minimum_price_uk': item.get('minimumPriceUk'),
'asin_fr': item.get('asinFr') or '',
'minimum_price_fr': item.get('minimumPriceFr'),
'asin_it': item.get('asinIt') or '',
'minimum_price_it': item.get('minimumPriceIt'),
'asin_es': item.get('asinEs') or '',
'minimum_price_es': item.get('minimumPriceEs'),
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
'updated_at': (item.get('updatedAt') or '').replace('T', ' ')[:16],
},
@@ -1739,9 +1855,9 @@ def create_skip_price_asin():
@admin_api.route('/skip-price-asin/<int:item_id>/country/<country>', methods=['DELETE'])
@admin_required
@login_required
def delete_skip_price_asin_country(item_id, country):
role, current_row, denied = _ensure_admin_menu_access('skip-price-asin')
role, current_row, denied = _ensure_backend_menu_access('skip-price-asin')
if denied:
return denied
result, error_response, status = _proxy_backend_java(
@@ -1758,9 +1874,9 @@ def delete_skip_price_asin_country(item_id, country):
@admin_api.route('/shop-manage-group/<int:item_id>', methods=['DELETE'])
@admin_required
@login_required
def delete_shop_manage_group(item_id):
role, current_row, denied = _ensure_admin_menu_access('shop-manage', 'skip-price-asin')
role, current_row, denied = _ensure_backend_menu_access('shop-manage', 'skip-price-asin')
if denied:
return denied
result, error_response, status = _proxy_backend_java(
@@ -1777,13 +1893,16 @@ def delete_shop_manage_group(item_id):
@admin_api.route('/skip-price-asin/<int:item_id>/country/<country>', methods=['PUT'])
@admin_required
@login_required
def update_skip_price_asin_country(item_id, country):
role, current_row, denied = _ensure_admin_menu_access('skip-price-asin')
role, current_row, denied = _ensure_backend_menu_access('skip-price-asin')
if denied:
return denied
data = request.get_json() or {}
payload = {'asin': (data.get('asin') or '').strip()}
payload = {
'asin': (data.get('asin') or '').strip(),
'minimumPrice': data.get('minimum_price'),
}
result, error_response, status = _proxy_backend_java(
'PUT',
f'/api/admin/skip-price-asins/{item_id}/countries/{country}',
@@ -1805,10 +1924,15 @@ def update_skip_price_asin_country(item_id, country):
'group_name': item.get('groupName') or '',
'shop_name': item.get('shopName') or '',
'asin_de': item.get('asinDe') or '',
'minimum_price_de': item.get('minimumPriceDe'),
'asin_uk': item.get('asinUk') or '',
'minimum_price_uk': item.get('minimumPriceUk'),
'asin_fr': item.get('asinFr') or '',
'minimum_price_fr': item.get('minimumPriceFr'),
'asin_it': item.get('asinIt') or '',
'minimum_price_it': item.get('minimumPriceIt'),
'asin_es': item.get('asinEs') or '',
'minimum_price_es': item.get('minimumPriceEs'),
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
'updated_at': (item.get('updatedAt') or '').replace('T', ' ')[:16],
},

View File

@@ -5,7 +5,7 @@ from flask import Blueprint, request, redirect, url_for, session, jsonify
from werkzeug.security import check_password_hash
from utils.db import get_db
from utils.auth import login_required, is_session_user_valid, is_current_user_admin
from utils.auth import login_required, is_session_user_valid
from utils.render import render_html
auth = Blueprint('auth', __name__, url_prefix='')
@@ -13,7 +13,7 @@ auth = Blueprint('auth', __name__, url_prefix='')
@auth.route('/login', methods=['GET', 'POST'])
def login():
if session.get('user_id') and is_session_user_valid() and is_current_user_admin():
if session.get('user_id') and is_session_user_valid():
return redirect(url_for('main.admin_page'))
if request.method == 'POST':
data = request.get_json() if request.is_json else request.form
@@ -31,22 +31,17 @@ def login():
(username,)
)
row = cur.fetchone()
conn.close()
if row and check_password_hash(row['password_hash'], password):
session.permanent = True
session['user_id'] = row['id']
session['username'] = username
if not row.get('is_admin'):
session.clear()
if request.is_json:
return jsonify({'success': False, 'error': '需要管理员权限'}), 403
return render_html('login.html', error='需要管理员权限')
if request.is_json:
return jsonify({'success': True, 'redirect': url_for('main.admin_page')})
return redirect(url_for('main.admin_page'))
conn.close()
except Exception as e:
except Exception as exc:
if request.is_json:
return jsonify({'success': False, 'error': str(e)})
return jsonify({'success': False, 'error': str(exc)})
return render_html('login.html', error='登录失败,请稍后重试')
if request.is_json:
return jsonify({'success': False, 'error': '用户名或密码错误'})
@@ -57,7 +52,7 @@ def login():
@auth.route('/api/auth/check')
@login_required
def api_auth_check():
"""校验登录状态,用于页面加载时判断是否已登录"""
"""校验登录状态,用于页面加载时判断是否已登录"""
if not session.get('user_id'):
return jsonify({'logged_in': False})
try:
@@ -66,7 +61,7 @@ def api_auth_check():
cur.execute("SELECT machine, is_admin FROM users WHERE id = %s", (session['user_id'],))
row = cur.fetchone()
conn.close()
if not row or not row.get('is_admin'):
if not row:
return jsonify({'logged_in': False})
except Exception:
return jsonify({'logged_in': False})

View File

@@ -4,7 +4,7 @@
import os
from flask import Blueprint, redirect, url_for, send_file, session
from utils.auth import login_required, admin_required, is_session_user_valid, is_current_user_admin
from utils.auth import login_required, is_session_user_valid
from utils.render import render_html
main = Blueprint('main', __name__, url_prefix='')
@@ -15,14 +15,13 @@ STATIC_DIR = os.path.join(BASE_DIR, 'static')
@main.route('/')
def index():
if session.get('user_id') and is_session_user_valid() and is_current_user_admin():
if session.get('user_id') and is_session_user_valid():
return redirect(url_for('main.admin_page'))
return redirect(url_for('auth.login'))
@main.route('/admin')
@login_required
@admin_required
def admin_page():
return render_html('admin.html')