添加新需求
This commit is contained in:
@@ -5,6 +5,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
@@ -83,6 +84,11 @@ ADMIN_MENU_ACCESS_CONFIG = {
|
||||
'route_path': 'invalid-asin-data',
|
||||
'error': '无权访问不符合ASIN数据模块',
|
||||
},
|
||||
'image-video-tasks': {
|
||||
'column_key': 'admin_image_video_tasks',
|
||||
'route_path': 'image-video-tasks',
|
||||
'error': '无权访问视频任务管理模块',
|
||||
},
|
||||
}
|
||||
|
||||
ADMIN_MENU_ACCESS_CONFIG.update({
|
||||
@@ -190,6 +196,122 @@ def _parse_optional_int(value):
|
||||
return int(text)
|
||||
|
||||
|
||||
_IMAGE_VIDEO_SECRET_KEYS = {
|
||||
'api_key', 'apikey', 'token', 'coze_token', 'cozetoken', 't8_key', 't8key',
|
||||
't8_video_key', 't8videokey', 't8star_key', 't8starkey',
|
||||
'ai_conductor_key', 'aiconductorkey', 'copy_api_key', 'copyapikey',
|
||||
'voice_api_key', 'voiceapikey',
|
||||
}
|
||||
|
||||
|
||||
def _parse_json_value(value, fallback=None):
|
||||
if value in (None, ''):
|
||||
return fallback
|
||||
if isinstance(value, (dict, list)):
|
||||
return value
|
||||
try:
|
||||
return json.loads(value)
|
||||
except (TypeError, ValueError):
|
||||
return value
|
||||
|
||||
|
||||
def _mask_image_video_secrets(value):
|
||||
if isinstance(value, dict):
|
||||
masked = {}
|
||||
for key, item in value.items():
|
||||
canonical = re.sub(r'[^a-z0-9]', '', str(key).lower())
|
||||
configured_keys = {re.sub(r'[^a-z0-9]', '', item) for item in _IMAGE_VIDEO_SECRET_KEYS}
|
||||
masked[key] = '******' if canonical in configured_keys and item not in (None, '') else _mask_image_video_secrets(item)
|
||||
return masked
|
||||
if isinstance(value, list):
|
||||
return [_mask_image_video_secrets(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def _image_video_mode(request_value):
|
||||
payload = _parse_json_value(request_value, {})
|
||||
try:
|
||||
mode = str(payload.get('parameters', {}).get('video_info', {}).get('mode', '')).strip()
|
||||
except AttributeError:
|
||||
mode = ''
|
||||
if mode == '1':
|
||||
return '图生视频'
|
||||
if mode == '2':
|
||||
return '视频复刻'
|
||||
return '未知'
|
||||
|
||||
|
||||
def _image_video_access_sql(role, current_row, alias='t'):
|
||||
if role == 'super_admin':
|
||||
return '1=1', []
|
||||
admin_id = int(current_row['id'])
|
||||
return (
|
||||
f"({alias}.user_id = %s OR {alias}.user_id IN "
|
||||
"(SELECT id FROM users WHERE role = 'normal' AND created_by_id = %s))",
|
||||
[admin_id, admin_id],
|
||||
)
|
||||
|
||||
|
||||
def _format_admin_datetime(value):
|
||||
if hasattr(value, 'strftime'):
|
||||
return value.strftime('%Y-%m-%d %H:%M:%S')
|
||||
return str(value).replace('T', ' ')[:19] if value else ''
|
||||
|
||||
|
||||
def _image_video_urls(row):
|
||||
originals = _parse_json_value(row.get('video_urls_json'), [])
|
||||
originals = originals if isinstance(originals, list) else []
|
||||
archived = _parse_json_value(row.get('archived_videos_json'), [])
|
||||
archived = archived if isinstance(archived, list) else []
|
||||
archived_by_source = {
|
||||
str(item.get('sourceUrl') or ''): item
|
||||
for item in archived if isinstance(item, dict)
|
||||
}
|
||||
items = []
|
||||
for source_url in originals:
|
||||
source_url = str(source_url or '')
|
||||
stored = archived_by_source.get(source_url) or {}
|
||||
items.append({
|
||||
'source_url': source_url,
|
||||
'archived_url': stored.get('url') or '',
|
||||
'object_key': stored.get('objectKey') or '',
|
||||
'archive_status': stored.get('status') or '',
|
||||
'archive_error': stored.get('error') or '',
|
||||
'display_url': stored.get('url') or source_url,
|
||||
})
|
||||
return items
|
||||
|
||||
|
||||
def _image_video_admin_item(row, include_json=False):
|
||||
videos = _image_video_urls(row)
|
||||
item = {
|
||||
'task_id': row.get('id'),
|
||||
'user_id': row.get('user_id'),
|
||||
'username': row.get('username') 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 '',
|
||||
'video_url': videos[0]['display_url'] if videos else '',
|
||||
'debug_url': row.get('debug_url') or '',
|
||||
'archive_status': row.get('archive_status') or '',
|
||||
'archive_error': row.get('archive_error') or '',
|
||||
'archive_attempt_count': row.get('archive_attempt_count') or 0,
|
||||
'submitted_at': _format_admin_datetime(row.get('submitted_at')),
|
||||
'completed_at': _format_admin_datetime(row.get('completed_at')),
|
||||
}
|
||||
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'), {})),
|
||||
'submit_response': _parse_json_value(row.get('submit_response_json'), {}),
|
||||
'result': _parse_json_value(row.get('result_json'), {}),
|
||||
})
|
||||
return item
|
||||
|
||||
|
||||
def _ensure_column_sort_schema():
|
||||
global _column_sort_schema_checked
|
||||
if _column_sort_schema_checked:
|
||||
@@ -992,6 +1114,120 @@ def history():
|
||||
|
||||
# ---------- 栏目权限配置 ----------
|
||||
|
||||
_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
|
||||
"""
|
||||
|
||||
|
||||
def _parse_admin_datetime_arg(name):
|
||||
value = (request.args.get(name) or '').strip()
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(value.replace('Z', '+00:00')).replace(tzinfo=None)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f'{name} 时间格式无效') from exc
|
||||
|
||||
|
||||
@admin_api.route('/image-video-tasks')
|
||||
@admin_required
|
||||
def list_image_video_tasks():
|
||||
role, current_row, denied = _ensure_admin_menu_access('image-video-tasks')
|
||||
if denied:
|
||||
return denied
|
||||
try:
|
||||
page = max(1, int(request.args.get('page', 1)))
|
||||
page_size = min(100, max(10, int(request.args.get('page_size', 20))))
|
||||
user_id = request.args.get('user_id', type=int)
|
||||
username = (request.args.get('username') or '').strip()
|
||||
status = (request.args.get('status') or '').strip().upper()
|
||||
execute_id = (request.args.get('coze_execute_id') or '').strip()
|
||||
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'"]
|
||||
if user_id:
|
||||
conditions.append('t.user_id = %s')
|
||||
params.append(user_id)
|
||||
if username:
|
||||
conditions.append('u.username LIKE %s')
|
||||
params.append('%' + username + '%')
|
||||
if status:
|
||||
conditions.append('t.status = %s')
|
||||
params.append(status)
|
||||
if execute_id:
|
||||
conditions.append('t.coze_execute_id LIKE %s')
|
||||
params.append('%' + execute_id + '%')
|
||||
if submitted_from:
|
||||
conditions.append('t.submitted_at >= %s')
|
||||
params.append(submitted_from)
|
||||
if submitted_to:
|
||||
conditions.append('t.submitted_at <= %s')
|
||||
params.append(submitted_to)
|
||||
where_sql = ' AND '.join(conditions)
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
conn = get_db()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
'SELECT ' + _IMAGE_VIDEO_ADMIN_COLUMNS +
|
||||
' FROM biz_image_video_async_task t LEFT JOIN users u ON u.id = t.user_id WHERE ' +
|
||||
where_sql + ' ORDER BY t.submitted_at DESC, t.id DESC LIMIT %s OFFSET %s',
|
||||
tuple(params + [page_size, offset]),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
cur.execute(
|
||||
'SELECT COUNT(*) AS total FROM biz_image_video_async_task t '
|
||||
'LEFT JOIN users u ON u.id = t.user_id WHERE ' + where_sql,
|
||||
tuple(params),
|
||||
)
|
||||
total = int((cur.fetchone() or {}).get('total') or 0)
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'items': [_image_video_admin_item(row) for row in rows],
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
})
|
||||
except ValueError as exc:
|
||||
return jsonify({'success': False, 'error': str(exc)}), 400
|
||||
except Exception as exc:
|
||||
return jsonify({'success': False, 'error': str(exc)}), 500
|
||||
|
||||
|
||||
@admin_api.route('/image-video-tasks/<int:task_id>')
|
||||
@admin_required
|
||||
def get_image_video_task(task_id):
|
||||
role, current_row, denied = _ensure_admin_menu_access('image-video-tasks')
|
||||
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)
|
||||
conn = get_db()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
'SELECT ' + _IMAGE_VIDEO_ADMIN_COLUMNS +
|
||||
' FROM biz_image_video_async_task t LEFT JOIN users u ON u.id = t.user_id WHERE ' +
|
||||
' AND '.join(conditions) + ' LIMIT 1',
|
||||
tuple(params),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
finally:
|
||||
conn.close()
|
||||
if not row:
|
||||
return jsonify({'success': False, 'error': '视频任务不存在或无权访问'}), 404
|
||||
return jsonify({'success': True, 'item': _image_video_admin_item(row, include_json=True)})
|
||||
|
||||
|
||||
@admin_api.route('/columns')
|
||||
@admin_required
|
||||
def list_columns():
|
||||
|
||||
Reference in New Issue
Block a user