视频任务管理更新
This commit is contained in:
@@ -4,13 +4,15 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import threading
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from flask import Blueprint, request, jsonify, session, current_app, g, Response
|
||||
from flask import Blueprint, request, jsonify, session, current_app, g, Response, send_file
|
||||
|
||||
import pymysql
|
||||
from werkzeug.security import generate_password_hash
|
||||
@@ -52,6 +54,8 @@ _column_sort_schema_checked = False
|
||||
_column_sort_schema_lock = threading.Lock()
|
||||
_product_category_schema_checked = False
|
||||
_product_category_schema_lock = threading.Lock()
|
||||
IMAGE_VIDEO_PERMISSION_KEY = 'admin_image_video_tasks'
|
||||
IMAGE_VIDEO_DATA_PERMISSION_KEY = 'admin_image_video_task_data'
|
||||
|
||||
ADMIN_MENU_ACCESS_CONFIG = {
|
||||
'dedupe-total-data': {
|
||||
@@ -288,10 +292,12 @@ def _image_video_admin_item(row, include_json=False):
|
||||
'task_id': row.get('id'),
|
||||
'user_id': row.get('user_id'),
|
||||
'username': row.get('username') or '',
|
||||
'group_name': row.get('group_name') or '',
|
||||
'mode': _image_video_mode(row.get('request_json')),
|
||||
'status': row.get('status') or '',
|
||||
'coze_status': row.get('coze_status') or '',
|
||||
'coze_execute_id': row.get('coze_execute_id') or '',
|
||||
'videos': videos,
|
||||
'video_url': videos[0]['display_url'] if videos else '',
|
||||
'debug_url': row.get('debug_url') or '',
|
||||
'archive_status': row.get('archive_status') or '',
|
||||
@@ -302,7 +308,6 @@ def _image_video_admin_item(row, include_json=False):
|
||||
}
|
||||
if include_json:
|
||||
item.update({
|
||||
'videos': videos,
|
||||
'error_message': row.get('error_message') or '',
|
||||
'archived_at': _format_admin_datetime(row.get('archived_at')),
|
||||
'request': _mask_image_video_secrets(_parse_json_value(row.get('request_json'), {})),
|
||||
@@ -625,17 +630,17 @@ def _load_current_admin_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)
|
||||
if role == 'super_admin':
|
||||
items = [
|
||||
_format_permission_item(item)
|
||||
for item in _load_local_columns('admin')
|
||||
if (item.get('column_key') or '') != IMAGE_VIDEO_DATA_PERMISSION_KEY
|
||||
]
|
||||
return role, current_row, items, None
|
||||
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 [])]
|
||||
items = [_format_permission_item(item) for item in _get_local_user_column_permission_items(user_id, 'admin')]
|
||||
return role, current_row, items, None
|
||||
|
||||
|
||||
@@ -643,17 +648,17 @@ 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)
|
||||
if role == 'super_admin':
|
||||
items = [
|
||||
_format_permission_item(item)
|
||||
for item in _load_local_columns('admin')
|
||||
if (item.get('column_key') or '') != IMAGE_VIDEO_DATA_PERMISSION_KEY
|
||||
]
|
||||
return role, current_row, items, None
|
||||
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 [])]
|
||||
items = [_format_permission_item(item) for item in _get_local_user_column_permission_items(user_id, 'admin')]
|
||||
return role, current_row, items, None
|
||||
|
||||
|
||||
@@ -676,6 +681,30 @@ def _ensure_backend_menu_access(*menu_names):
|
||||
return role, current_row, (jsonify({'success': False, 'error': error_message}), 403)
|
||||
|
||||
|
||||
def _ensure_image_video_data_access():
|
||||
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)
|
||||
conn = get_db()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
data_column_id = _image_video_permission_column_id(cur)
|
||||
cur.execute(
|
||||
"SELECT 1 FROM user_column_permission ucp "
|
||||
"WHERE ucp.user_id = %s AND ucp.column_id = %s LIMIT 1",
|
||||
(int(current_row['id']), data_column_id),
|
||||
)
|
||||
granted = cur.fetchone()
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
if granted:
|
||||
return role, current_row, None
|
||||
return role, current_row, (jsonify({'success': False, 'error': '无权查看视频任务数据'}), 403)
|
||||
|
||||
|
||||
def _ensure_product_category_access():
|
||||
role, current_row, items, denied = _load_current_backend_menu_items()
|
||||
if denied:
|
||||
@@ -781,7 +810,8 @@ def list_users():
|
||||
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
|
||||
can_view_all_users = role == 'super_admin' or (current_row and (current_row.get('username') or '').strip().lower() == 'admin')
|
||||
# 用户范围只由角色决定,用户名不能绕过权限模型。
|
||||
can_view_all_users = role == 'super_admin'
|
||||
can_view_child_users = role == 'admin' and not can_view_all_users
|
||||
if not can_view_all_users:
|
||||
created_by_id = None
|
||||
@@ -935,10 +965,23 @@ def create_user():
|
||||
(username, pwd_hash, is_admin, want_role, want_created_by),
|
||||
)
|
||||
new_uid = cur.lastrowid
|
||||
_set_user_column_permissions(cur, new_uid, column_ids)
|
||||
_validate_admin_granted_columns(cur, role, current_row, column_ids)
|
||||
_set_user_column_permissions(
|
||||
cur,
|
||||
new_uid,
|
||||
column_ids,
|
||||
preserve_column_keys=(IMAGE_VIDEO_DATA_PERMISSION_KEY,),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True, 'msg': '用户创建成功'})
|
||||
except PermissionError as exc:
|
||||
try:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify({'success': False, 'error': str(exc)}), 403
|
||||
except pymysql.IntegrityError:
|
||||
return jsonify({'success': False, 'error': '用户名已存在'})
|
||||
except Exception as e:
|
||||
@@ -994,10 +1037,23 @@ def update_user(uid):
|
||||
(is_admin, want_role, uid),
|
||||
)
|
||||
if 'column_ids' in data:
|
||||
_set_user_column_permissions(cur, uid, data.get('column_ids'))
|
||||
_validate_admin_granted_columns(cur, role, current_row, data.get('column_ids'))
|
||||
_set_user_column_permissions(
|
||||
cur,
|
||||
uid,
|
||||
data.get('column_ids'),
|
||||
preserve_column_keys=(IMAGE_VIDEO_DATA_PERMISSION_KEY,),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True, 'msg': '更新成功'})
|
||||
except PermissionError as exc:
|
||||
try:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify({'success': False, 'error': str(exc)}), 403
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)})
|
||||
|
||||
@@ -1118,7 +1174,15 @@ _IMAGE_VIDEO_ADMIN_COLUMNS = """
|
||||
t.id, t.user_id, t.status, t.request_json, t.submit_response_json, t.result_json,
|
||||
t.video_urls_json, t.debug_url, t.archived_videos_json, t.archive_status,
|
||||
t.archive_error, t.archive_attempt_count, t.archived_at, t.error_message,
|
||||
t.coze_execute_id, t.coze_status, t.submitted_at, t.completed_at, u.username
|
||||
t.coze_execute_id, t.coze_status, t.submitted_at, t.completed_at, u.username,
|
||||
COALESCE((
|
||||
SELECT GROUP_CONCAT(DISTINCT g.group_name ORDER BY g.id SEPARATOR '、')
|
||||
FROM biz_shop_manage_group g
|
||||
LEFT JOIN biz_shop_manage_group_member gm ON gm.group_id = g.id
|
||||
WHERE g.created_by_id = t.user_id
|
||||
OR g.user_id = t.user_id
|
||||
OR gm.user_id = t.user_id
|
||||
), '') AS group_name
|
||||
"""
|
||||
|
||||
|
||||
@@ -1132,10 +1196,220 @@ def _parse_admin_datetime_arg(name):
|
||||
raise ValueError(f'{name} 时间格式无效') from exc
|
||||
|
||||
|
||||
def _image_video_permission_column_id(cur):
|
||||
cur.execute("SELECT id FROM columns WHERE column_key = %s LIMIT 1", (IMAGE_VIDEO_DATA_PERMISSION_KEY,))
|
||||
row = cur.fetchone()
|
||||
if not row or row.get('id') is None:
|
||||
# 兼容 V74 尚未执行的旧库:创建数据权限栏目,并继承已有菜单授权。
|
||||
cur.execute(
|
||||
"INSERT INTO columns (name, column_key, menu_type, route_path, sort_order) "
|
||||
"SELECT %s, %s, 'internal', 'image-video-task-data', 0 "
|
||||
"WHERE NOT EXISTS (SELECT 1 FROM columns WHERE column_key = %s)",
|
||||
('视频任务数据查看', IMAGE_VIDEO_DATA_PERMISSION_KEY, IMAGE_VIDEO_DATA_PERMISSION_KEY),
|
||||
)
|
||||
cur.execute("SELECT id FROM columns WHERE column_key = %s LIMIT 1", (IMAGE_VIDEO_DATA_PERMISSION_KEY,))
|
||||
row = cur.fetchone()
|
||||
if not row or row.get('id') is None:
|
||||
raise ValueError('视频任务数据权限栏目不存在')
|
||||
cur.execute(
|
||||
"INSERT IGNORE INTO user_column_permission (user_id, column_id) "
|
||||
"SELECT old_perm.user_id, %s FROM user_column_permission old_perm "
|
||||
"INNER JOIN columns old_col ON old_col.id = old_perm.column_id "
|
||||
"WHERE old_col.column_key = %s",
|
||||
(int(row['id']), IMAGE_VIDEO_PERMISSION_KEY),
|
||||
)
|
||||
return int(row['id'])
|
||||
|
||||
|
||||
def _is_stored_super_admin_sql(alias='u'):
|
||||
return (
|
||||
f"(LOWER(COALESCE({alias}.role, '')) = 'super_admin' OR "
|
||||
f"((COALESCE({alias}.role, '') = '') AND {alias}.is_admin = 1 AND {alias}.created_by_id IS NULL))"
|
||||
)
|
||||
|
||||
|
||||
@admin_api.route('/image-video-task-permissions', methods=['GET', 'PUT'])
|
||||
@login_required
|
||||
def manage_image_video_task_permissions():
|
||||
role, _ = get_current_admin_role()
|
||||
if role != 'super_admin':
|
||||
return jsonify({'success': False, 'error': '仅超级管理员可以配置视频任务权限'}), 403
|
||||
|
||||
conn = get_db()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
column_id = _image_video_permission_column_id(cur)
|
||||
if request.method == 'GET':
|
||||
cur.execute(
|
||||
"SELECT u.id, u.username, u.role, u.is_admin, u.created_by_id, "
|
||||
"CASE WHEN ucp.user_id IS NULL THEN 0 ELSE 1 END AS granted "
|
||||
"FROM users u LEFT JOIN user_column_permission ucp "
|
||||
"ON ucp.user_id = u.id AND ucp.column_id = %s "
|
||||
f"WHERE NOT {_is_stored_super_admin_sql('u')} "
|
||||
"ORDER BY u.username ASC, u.id ASC",
|
||||
(column_id,),
|
||||
)
|
||||
items = []
|
||||
for row in cur.fetchall():
|
||||
stored_role = (row.get('role') or '').strip().lower()
|
||||
effective_role = stored_role or ('admin' if row.get('is_admin') else 'normal')
|
||||
items.append({
|
||||
'id': int(row['id']),
|
||||
'username': row.get('username') or '',
|
||||
'role': effective_role,
|
||||
'granted': bool(row.get('granted')),
|
||||
})
|
||||
return jsonify({'success': True, 'items': items})
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
raw_user_ids = data.get('user_ids')
|
||||
if not isinstance(raw_user_ids, list):
|
||||
return jsonify({'success': False, 'error': 'user_ids 必须是数组'}), 400
|
||||
try:
|
||||
user_ids = sorted({int(value) for value in raw_user_ids if not isinstance(value, bool) and int(value) > 0})
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({'success': False, 'error': 'user_ids 包含无效用户 ID'}), 400
|
||||
if len(user_ids) != len(raw_user_ids):
|
||||
return jsonify({'success': False, 'error': 'user_ids 包含重复或无效用户 ID'}), 400
|
||||
|
||||
if user_ids:
|
||||
placeholders = ','.join(['%s'] * len(user_ids))
|
||||
cur.execute(
|
||||
f"SELECT id FROM users u WHERE id IN ({placeholders}) AND NOT {_is_stored_super_admin_sql('u')}",
|
||||
tuple(user_ids),
|
||||
)
|
||||
valid_ids = {int(row['id']) for row in cur.fetchall()}
|
||||
if valid_ids != set(user_ids):
|
||||
return jsonify({'success': False, 'error': '包含不存在或不可授权的用户'}), 400
|
||||
|
||||
cur.execute(
|
||||
"DELETE ucp FROM user_column_permission ucp "
|
||||
"INNER JOIN users u ON u.id = ucp.user_id "
|
||||
f"WHERE ucp.column_id = %s AND NOT {_is_stored_super_admin_sql('u')}",
|
||||
(column_id,),
|
||||
)
|
||||
for user_id in user_ids:
|
||||
cur.execute(
|
||||
"INSERT INTO user_column_permission (user_id, column_id) VALUES (%s, %s)",
|
||||
(user_id, column_id),
|
||||
)
|
||||
conn.commit()
|
||||
return jsonify({'success': True, 'granted_count': len(user_ids), 'msg': '视频任务权限已更新'})
|
||||
except ValueError as exc:
|
||||
return jsonify({'success': False, 'error': str(exc)}), 400
|
||||
except Exception as exc:
|
||||
if request.method == 'PUT':
|
||||
conn.rollback()
|
||||
return jsonify({'success': False, 'error': str(exc)}), 500
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@admin_api.route('/image-video-tasks/download-zip', methods=['POST'])
|
||||
@login_required
|
||||
def download_image_video_tasks_zip():
|
||||
_, _, denied = _ensure_backend_menu_access('image-video-tasks')
|
||||
if not denied:
|
||||
_, _, denied = _ensure_image_video_data_access()
|
||||
if denied:
|
||||
return denied
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
raw_items = data.get('items')
|
||||
if not isinstance(raw_items, list) or not raw_items:
|
||||
return jsonify({'success': False, 'error': '请至少选择一个视频'}), 400
|
||||
if len(raw_items) > 100:
|
||||
return jsonify({'success': False, 'error': '单次最多打包 100 个视频'}), 400
|
||||
|
||||
selections = []
|
||||
seen = set()
|
||||
try:
|
||||
for item in raw_items:
|
||||
if not isinstance(item, dict):
|
||||
raise ValueError
|
||||
task_id = int(item.get('task_id'))
|
||||
video_index = int(item.get('video_index'))
|
||||
if task_id <= 0 or video_index < 0:
|
||||
raise ValueError
|
||||
key = (task_id, video_index)
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
selections.append(key)
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({'success': False, 'error': '视频选择参数无效'}), 400
|
||||
|
||||
task_ids = sorted({task_id for task_id, _ in selections})
|
||||
conn = get_db()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
placeholders = ','.join(['%s'] * len(task_ids))
|
||||
cur.execute(
|
||||
'SELECT ' + _IMAGE_VIDEO_ADMIN_COLUMNS +
|
||||
' FROM biz_image_video_async_task t LEFT JOIN users u ON u.id = t.user_id '
|
||||
f"WHERE t.task_type = 'IMAGE_VIDEO_WORKFLOW' "
|
||||
f"AND t.submitted_at >= DATE_SUB(NOW(), INTERVAL 3 DAY) "
|
||||
f"AND t.id IN ({placeholders})",
|
||||
tuple(task_ids),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
tasks = {int(row['id']): row for row in rows}
|
||||
|
||||
archive = tempfile.SpooledTemporaryFile(max_size=64 * 1024 * 1024, mode='w+b')
|
||||
errors = []
|
||||
file_count = 0
|
||||
try:
|
||||
with zipfile.ZipFile(archive, mode='w', compression=zipfile.ZIP_STORED, allowZip64=True) as output_zip:
|
||||
for task_id, video_index in selections:
|
||||
row = tasks.get(task_id)
|
||||
videos = _image_video_urls(row) if row else []
|
||||
if video_index >= len(videos) or not videos[video_index].get('display_url'):
|
||||
errors.append(f'task-{task_id}-video-{video_index + 1}: 视频地址不存在')
|
||||
continue
|
||||
url = videos[video_index]['display_url']
|
||||
extension_match = re.search(r'\.([a-z0-9]{2,5})(?:[?#]|$)', url, re.IGNORECASE)
|
||||
extension = extension_match.group(1).lower() if extension_match else 'mp4'
|
||||
filename = f'task-{task_id}-video-{video_index + 1}.{extension}'
|
||||
remote_response = None
|
||||
try:
|
||||
remote_response = requests.get(url, stream=True, timeout=(10, 120))
|
||||
remote_response.raise_for_status()
|
||||
with output_zip.open(filename, mode='w', force_zip64=True) as target:
|
||||
for chunk in remote_response.iter_content(chunk_size=1024 * 1024):
|
||||
if chunk:
|
||||
target.write(chunk)
|
||||
file_count += 1
|
||||
except requests.RequestException as exc:
|
||||
errors.append(f'{filename}: 下载失败 ({exc})')
|
||||
finally:
|
||||
if remote_response is not None:
|
||||
remote_response.close()
|
||||
if errors:
|
||||
output_zip.writestr('download-errors.txt', '\n'.join(errors).encode('utf-8'))
|
||||
archive.seek(0)
|
||||
response = send_file(
|
||||
archive,
|
||||
mimetype='application/zip',
|
||||
as_attachment=True,
|
||||
download_name=f"video-tasks-{datetime.now().strftime('%Y%m%d-%H%M%S')}.zip",
|
||||
max_age=0,
|
||||
)
|
||||
response.headers['X-Archive-File-Count'] = str(file_count)
|
||||
response.headers['X-Archive-Error-Count'] = str(len(errors))
|
||||
response.call_on_close(archive.close)
|
||||
return response
|
||||
except Exception:
|
||||
archive.close()
|
||||
raise
|
||||
|
||||
|
||||
@admin_api.route('/image-video-tasks')
|
||||
@admin_required
|
||||
@login_required
|
||||
def list_image_video_tasks():
|
||||
role, current_row, denied = _ensure_admin_menu_access('image-video-tasks')
|
||||
_, _, denied = _ensure_backend_menu_access('image-video-tasks')
|
||||
if not denied:
|
||||
_, _, denied = _ensure_image_video_data_access()
|
||||
if denied:
|
||||
return denied
|
||||
try:
|
||||
@@ -1148,8 +1422,11 @@ def list_image_video_tasks():
|
||||
submitted_from = _parse_admin_datetime_arg('submitted_from')
|
||||
submitted_to = _parse_admin_datetime_arg('submitted_to')
|
||||
|
||||
access_sql, params = _image_video_access_sql(role, current_row)
|
||||
conditions = [access_sql, "t.task_type = 'IMAGE_VIDEO_WORKFLOW'"]
|
||||
params = []
|
||||
conditions = [
|
||||
"t.task_type = 'IMAGE_VIDEO_WORKFLOW'",
|
||||
"t.submitted_at >= DATE_SUB(NOW(), INTERVAL 3 DAY)",
|
||||
]
|
||||
if user_id:
|
||||
conditions.append('t.user_id = %s')
|
||||
params.append(user_id)
|
||||
@@ -1203,14 +1480,19 @@ def list_image_video_tasks():
|
||||
|
||||
|
||||
@admin_api.route('/image-video-tasks/<int:task_id>')
|
||||
@admin_required
|
||||
@login_required
|
||||
def get_image_video_task(task_id):
|
||||
role, current_row, denied = _ensure_admin_menu_access('image-video-tasks')
|
||||
_, _, denied = _ensure_backend_menu_access('image-video-tasks')
|
||||
if not denied:
|
||||
_, _, denied = _ensure_image_video_data_access()
|
||||
if denied:
|
||||
return denied
|
||||
access_sql, params = _image_video_access_sql(role, current_row)
|
||||
conditions = [access_sql, "t.task_type = 'IMAGE_VIDEO_WORKFLOW'", 't.id = %s']
|
||||
params.append(task_id)
|
||||
conditions = [
|
||||
"t.task_type = 'IMAGE_VIDEO_WORKFLOW'",
|
||||
"t.submitted_at >= DATE_SUB(NOW(), INTERVAL 3 DAY)",
|
||||
't.id = %s',
|
||||
]
|
||||
params = [task_id]
|
||||
conn = get_db()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
@@ -1248,6 +1530,7 @@ def list_columns():
|
||||
'created_at': str(row.get('created_at') or '')[:16].replace('T', ' '),
|
||||
}
|
||||
for row in local_rows
|
||||
if (row.get('column_key') or '') != IMAGE_VIDEO_DATA_PERMISSION_KEY
|
||||
]
|
||||
return jsonify({'success': True, 'items': items})
|
||||
|
||||
@@ -1405,16 +1688,50 @@ def get_user_column_permissions(uid):
|
||||
return jsonify({'success': True, 'items': [_format_permission_item(item) for item in items]})
|
||||
|
||||
|
||||
def _set_user_column_permissions(cur, user_id, column_ids):
|
||||
def _set_user_column_permissions(cur, user_id, column_ids, preserve_column_keys=()):
|
||||
"""设置用户栏目权限:先删后插。cur 为已打开的游标。"""
|
||||
cur.execute("DELETE FROM user_column_permission WHERE user_id = %s", (user_id,))
|
||||
if column_ids:
|
||||
column_ids = [int(x) for x in column_ids if x]
|
||||
for cid in column_ids:
|
||||
requested_ids = {int(x) for x in (column_ids or []) if x}
|
||||
preserved_ids = set()
|
||||
if preserve_column_keys:
|
||||
placeholders = ','.join(['%s'] * len(preserve_column_keys))
|
||||
cur.execute(
|
||||
f"SELECT id FROM columns WHERE column_key IN ({placeholders})",
|
||||
tuple(preserve_column_keys),
|
||||
)
|
||||
protected_ids = {int(row['id']) for row in cur.fetchall() if row.get('id') is not None}
|
||||
requested_ids.difference_update(protected_ids)
|
||||
if protected_ids:
|
||||
id_placeholders = ','.join(['%s'] * len(protected_ids))
|
||||
cur.execute(
|
||||
"INSERT INTO user_column_permission (user_id, column_id) VALUES (%s, %s)",
|
||||
(user_id, cid),
|
||||
f"SELECT column_id FROM user_column_permission WHERE user_id = %s AND column_id IN ({id_placeholders})",
|
||||
(user_id, *sorted(protected_ids)),
|
||||
)
|
||||
preserved_ids = {int(row['column_id']) for row in cur.fetchall() if row.get('column_id') is not None}
|
||||
cur.execute("DELETE FROM user_column_permission WHERE user_id = %s", (user_id,))
|
||||
for cid in sorted(requested_ids | preserved_ids):
|
||||
cur.execute(
|
||||
"INSERT INTO user_column_permission (user_id, column_id) VALUES (%s, %s)",
|
||||
(user_id, cid),
|
||||
)
|
||||
|
||||
|
||||
def _validate_admin_granted_columns(cur, role, current_row, column_ids):
|
||||
"""普通管理员只能把自己已拥有的栏目授予其直接管理的普通用户。"""
|
||||
if role != 'admin' or not current_row or not column_ids:
|
||||
return
|
||||
requested_ids = {int(value) for value in column_ids if str(value).isdigit() and int(value) > 0}
|
||||
if not requested_ids:
|
||||
return
|
||||
placeholders = ','.join(['%s'] * len(requested_ids))
|
||||
cur.execute(
|
||||
f"SELECT column_id FROM user_column_permission "
|
||||
f"WHERE user_id = %s AND column_id IN ({placeholders})",
|
||||
(int(current_row['id']), *sorted(requested_ids)),
|
||||
)
|
||||
owned_ids = {int(row['column_id']) for row in cur.fetchall() if row.get('column_id') is not None}
|
||||
denied_ids = requested_ids - owned_ids
|
||||
if denied_ids:
|
||||
raise PermissionError('普通管理员只能分配自己已有的菜单权限')
|
||||
|
||||
|
||||
# ---------- 版本管理(web_config) ----------
|
||||
|
||||
Reference in New Issue
Block a user