添加新需求

This commit is contained in:
super
2026-07-16 13:12:46 +08:00
parent 8543dad514
commit 69784b8d32
20 changed files with 1116 additions and 12 deletions

View File

@@ -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():