增加web_api

This commit is contained in:
2026-02-05 15:13:54 +08:00
parent 443ec09c5c
commit d5edbc0723
43 changed files with 7036 additions and 2640 deletions

View File

@ -0,0 +1,228 @@
"""
Task Pool Blueprint
Handles task pool management endpoints: listing tasks with pagination, pool statistics.
"""
from flask import Blueprint, request
from pathlib import Path
from ..shared import (
get_task_list,
get_task_pool_stats,
_format_response,
log_performance,
logger,
task_status,
TASK_STATUS_PENDING,
TASK_STATUS_PROCESSING,
TASK_STATUS_COMPLETED,
TASK_STATUS_FAILED
)
# Create blueprint
task_pool_bp = Blueprint('task_pool', __name__, url_prefix='/tasks')
def _build_simple_downloads_from_results(results: list[dict]) -> dict:
"""
Build direct download shortcuts for common files, based on task results.
This is intentionally minimal and frontend-friendly.
"""
downloads: dict = {}
def set_once(key: str, url: str):
if key not in downloads and url:
downloads[key] = url
for item in results or []:
if not isinstance(item, dict):
continue
rel_path = item.get('rel_path')
if not rel_path:
continue
name_l = (item.get('name') or '').lower()
url = f"/download/{rel_path}"
if name_l.endswith('.xlsx'):
set_once('data_xlsx', url)
elif name_l.endswith('.xls'):
set_once('data_xls', url)
elif name_l.endswith('ch4_report.html'):
set_once('report_ch4', url)
elif name_l.endswith('co2_report.html'):
set_once('report_co2', url)
elif name_l.endswith(('.yaml', '.yml')):
set_once('config', url)
elif name_l.endswith('.json') and 'output_vars' in name_l:
set_once('metadata', url)
elif name_l.endswith('.html'):
# fallback: any html report
set_once('report_html', url)
return downloads
def _lean_task_summary(task_summary: dict) -> dict:
"""Return a minimal task representation for frontend consumption."""
task_id = task_summary.get('task_id')
status = task_summary.get('status')
lean = {
'task_id': task_id,
'status': status,
'message': task_summary.get('message'),
'updated_at': task_summary.get('updated_at'),
}
if status == TASK_STATUS_COMPLETED and task_id:
full_task_info = task_status.get(task_id, {})
results = full_task_info.get('results', []) or []
downloads = _build_simple_downloads_from_results(results)
if downloads:
lean['downloads'] = downloads
return lean
@task_pool_bp.route('', methods=['GET'])
@log_performance
def list_tasks():
"""Get paginated list of tasks with optional filtering."""
logger.debug(f"Task list request from IP {request.remote_addr}")
try:
# Parse query parameters
status_filter = request.args.get('status')
if status_filter:
# Support comma-separated status values
status_filter = status_filter.split(',')
page = int(request.args.get('page', 1))
page_size = int(request.args.get('page_size', 20))
sort_by = request.args.get('sort_by', 'updated_at')
sort_order = request.args.get('sort_order', 'desc')
# Validate parameters
if page < 1:
return _format_response(400, "页码必须大于0")
if page_size < 1 or page_size > 100:
return _format_response(400, "每页数量必须在1-100之间")
valid_sort_fields = ['created_at', 'updated_at', 'status']
if sort_by not in valid_sort_fields:
return _format_response(400, f"排序字段必须是以下之一: {', '.join(valid_sort_fields)}")
if sort_order.lower() not in ['asc', 'desc']:
return _format_response(400, "排序顺序必须是 'asc' 或 'desc'")
# Get task list
result = get_task_list(
status_filter=status_filter,
page=page,
page_size=page_size,
sort_by=sort_by,
sort_order=sort_order,
cleanup=False
)
# Slim response: only task status + downloads (completed only)
result['tasks'] = [_lean_task_summary(t) for t in result.get('tasks', [])]
logger.debug(f"Returning {len(result['tasks'])} tasks (page {page} of {result['total_pages']})")
return _format_response(200, "任务列表查询成功", result)
except ValueError as e:
logger.warning(f"Invalid parameter in task list request: {str(e)}")
return _format_response(400, "参数格式错误")
except Exception as e:
logger.error(f"Error listing tasks: {str(e)}", exc_info=True)
return _format_response(500, "内部服务器错误")
@task_pool_bp.route('/stats', methods=['GET'])
@log_performance
def get_pool_stats():
"""Get task pool statistics."""
logger.debug(f"Task pool stats request from IP {request.remote_addr}")
try:
stats = get_task_pool_stats()
logger.debug(f"Pool stats: {stats['total_tasks']} total tasks, "
f"{stats['active_tasks']} active, {stats['queued_tasks']} queued")
return _format_response(200, "任务池统计信息查询成功", stats)
except Exception as e:
logger.error(f"Error getting pool stats: {str(e)}", exc_info=True)
return _format_response(500, "内部服务器错误")
@task_pool_bp.route('/active', methods=['GET'])
@log_performance
def get_active_tasks():
"""Get list of currently active (processing) tasks."""
logger.debug(f"Active tasks request from IP {request.remote_addr}")
try:
# Get all processing tasks, no pagination needed for active tasks
result = get_task_list(
status_filter=TASK_STATUS_PROCESSING,
page=1,
page_size=1000, # Large page size to get all active tasks
sort_by='updated_at',
sort_order='asc', # Oldest first
cleanup=False
)
active_tasks = result['tasks']
active_tasks = [_lean_task_summary(t) for t in active_tasks]
logger.debug(f"Returning {len(active_tasks)} active tasks")
return _format_response(200, "活跃任务查询成功", {
'active_tasks': active_tasks,
'count': len(active_tasks)
})
except Exception as e:
logger.error(f"Error getting active tasks: {str(e)}", exc_info=True)
return _format_response(500, "内部服务器错误")
@task_pool_bp.route('/queue', methods=['GET'])
@log_performance
def get_queued_tasks():
"""Get list of queued (pending) tasks."""
logger.debug(f"Queued tasks request from IP {request.remote_addr}")
try:
# Get all pending tasks, sorted by creation time
result = get_task_list(
status_filter=TASK_STATUS_PENDING,
page=1,
page_size=1000, # Large page size to get all queued tasks
sort_by='created_at',
sort_order='asc', # Oldest first (FIFO)
cleanup=False
)
queued_tasks = result['tasks']
queued_tasks = [_lean_task_summary(t) for t in queued_tasks]
logger.debug(f"Returning {len(queued_tasks)} queued tasks")
return _format_response(200, "队列任务查询成功", {
'queued_tasks': queued_tasks,
'count': len(queued_tasks),
'queue_position_info': "任务按创建时间排序,较早的任务优先处理"
})
except Exception as e:
logger.error(f"Error getting queued tasks: {str(e)}", exc_info=True)
return _format_response(500, "内部服务器错误")