feat: 双模式集成并精简 API 文档/Token、设置页卡片自适应
- 源/仓库各自可在直连(FTP/SFTP、MySQL)与 Metrix 存储/数据库平台间独立选择,两侧互不依赖 - Metrix 模式下源走平台储存 API、仓库走平台导入 + run-script(single_session)、查看导出代理到平台 - 去掉对外 API 文档与 API Token(前后端 + auth/config 解耦),业务接口仅登录态可访问 - 授权默认到期日改为 2026-12-30 - 设置页卡片改横向自适应(宽屏并排、窄屏换行),处理历史保留卡片收窄
This commit is contained in:
@@ -1,135 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Body, HTTPException, Request
|
||||
|
||||
from app.auth import resolve_login_context
|
||||
from app.services.api_tokens import create_token, delete_token, delete_tokens, list_tokens, regenerate_token, update_token
|
||||
|
||||
|
||||
router = APIRouter(tags=["api-tokens"])
|
||||
|
||||
|
||||
def _require_login(request: Request) -> None:
|
||||
if resolve_login_context(request) is None:
|
||||
raise HTTPException(status_code=401, detail="未登录或登录已过期")
|
||||
|
||||
|
||||
def _bad_expiration_error(exc: ValueError) -> HTTPException:
|
||||
return HTTPException(status_code=400, detail="到期日期格式无效,请使用 YYYY-MM-DD 或 ISO 日期时间")
|
||||
|
||||
|
||||
def _resolve_expires_at(payload: dict[str, Any]) -> str | None:
|
||||
if bool(payload.get("permanent", False)):
|
||||
return None
|
||||
|
||||
expires_at = str(payload.get("expires_at") or "").strip()
|
||||
if not expires_at:
|
||||
raise HTTPException(status_code=400, detail="请选择 Token 到期日期,或设置为永久有效")
|
||||
return expires_at
|
||||
|
||||
|
||||
@router.get("/api/tokens")
|
||||
async def get_tokens(request: Request):
|
||||
_require_login(request)
|
||||
return {"success": True, "tokens": list_tokens()}
|
||||
|
||||
|
||||
@router.post("/api/tokens/create")
|
||||
async def create_api_token(request: Request, payload: dict[str, Any] = Body(...)):
|
||||
_require_login(request)
|
||||
name = str(payload.get("name", "")).strip()
|
||||
enabled = bool(payload.get("enabled", True))
|
||||
raw_expires_at = _resolve_expires_at(payload)
|
||||
|
||||
try:
|
||||
raw_token, record = create_token(
|
||||
name=name,
|
||||
expires_at=raw_expires_at,
|
||||
enabled=enabled,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise _bad_expiration_error(exc) from exc
|
||||
return {
|
||||
"success": True,
|
||||
"message": "API Token 创建成功",
|
||||
"token": raw_token,
|
||||
"record": record,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/api/tokens/update")
|
||||
async def update_api_token(request: Request, payload: dict[str, Any] = Body(...)):
|
||||
_require_login(request)
|
||||
token_id = str(payload.get("id", "")).strip()
|
||||
if not token_id:
|
||||
raise HTTPException(status_code=400, detail="缺少 Token ID")
|
||||
|
||||
changes: dict[str, Any] = {}
|
||||
if "name" in payload:
|
||||
changes["name"] = payload.get("name")
|
||||
if "enabled" in payload:
|
||||
changes["enabled"] = payload.get("enabled")
|
||||
if "permanent" in payload or "expires_at" in payload:
|
||||
changes["expires_at"] = _resolve_expires_at(payload)
|
||||
|
||||
try:
|
||||
record = update_token(token_id, **changes)
|
||||
return {"success": True, "message": "API Token 已更新", "record": record}
|
||||
except ValueError as exc:
|
||||
raise _bad_expiration_error(exc) from exc
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/api/tokens/regenerate")
|
||||
async def regenerate_api_token(request: Request, payload: dict[str, Any] = Body(...)):
|
||||
_require_login(request)
|
||||
token_id = str(payload.get("id", "")).strip()
|
||||
if not token_id:
|
||||
raise HTTPException(status_code=400, detail="缺少 Token ID")
|
||||
|
||||
try:
|
||||
raw_token, record = regenerate_token(token_id)
|
||||
return {
|
||||
"success": True,
|
||||
"message": "API Token 已重新生成",
|
||||
"token": raw_token,
|
||||
"record": record,
|
||||
}
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post("/api/tokens/delete")
|
||||
async def delete_api_token(request: Request, payload: dict[str, Any] = Body(...)):
|
||||
_require_login(request)
|
||||
token_id = str(payload.get("id", "")).strip()
|
||||
if not token_id:
|
||||
raise HTTPException(status_code=400, detail="缺少 Token ID")
|
||||
|
||||
delete_token(token_id)
|
||||
return {"success": True, "message": "API Token 已删除"}
|
||||
|
||||
|
||||
@router.post("/api/tokens/batch-delete")
|
||||
async def batch_delete_api_tokens(request: Request, payload: dict[str, Any] = Body(...)):
|
||||
_require_login(request)
|
||||
token_ids = payload.get("ids", [])
|
||||
if not isinstance(token_ids, list) or not token_ids:
|
||||
raise HTTPException(status_code=400, detail="请选择要删除的 Token")
|
||||
|
||||
deleted_count = delete_tokens([str(token_id) for token_id in token_ids])
|
||||
return {"success": True, "message": f"已删除 {deleted_count} 个 API Token", "deleted_count": deleted_count}
|
||||
|
||||
|
||||
@router.get("/api/docs-info")
|
||||
async def docs_info(request: Request):
|
||||
_require_login(request)
|
||||
return {
|
||||
"success": True,
|
||||
"docs_url": "/api/docs-ui",
|
||||
"openapi_url": "/api/openapi.json",
|
||||
"token_header": "Authorization: Bearer <token>",
|
||||
"alt_header": "X-API-Token: <token>",
|
||||
"note": "API 文档仅登录后可访问。",
|
||||
}
|
||||
@@ -7,8 +7,14 @@ from fastapi import APIRouter, Body, HTTPException, UploadFile, File
|
||||
from fastapi.responses import Response
|
||||
|
||||
from app import state
|
||||
from app.config import HistoryRetentionConfig, RJDataConfig, RemoteDataConfig
|
||||
from app.services.api_tokens import export_tokens, import_tokens
|
||||
from app.config import (
|
||||
HistoryRetentionConfig,
|
||||
MetrixConfig,
|
||||
RJDataConfig,
|
||||
RemoteDataConfig,
|
||||
SOURCE_TYPES,
|
||||
WAREHOUSE_TYPES,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(tags=["config"])
|
||||
@@ -50,6 +56,30 @@ async def update_remote_config(config: dict[str, Any] = Body(...)):
|
||||
return {"success": True, "message": "远程数据配置已更新", "update": state.config.update}
|
||||
|
||||
|
||||
@router.post("/api/config/backend")
|
||||
async def update_backend(
|
||||
source_type: str = Body(...),
|
||||
warehouse_type: str = Body(...),
|
||||
):
|
||||
if source_type not in SOURCE_TYPES:
|
||||
raise HTTPException(status_code=400, detail="不支持的源类型")
|
||||
if warehouse_type not in WAREHOUSE_TYPES:
|
||||
raise HTTPException(status_code=400, detail="不支持的仓库类型")
|
||||
state.reload_config()
|
||||
state.config.source_type = source_type
|
||||
state.config.warehouse_type = warehouse_type
|
||||
state.config.save()
|
||||
return {"success": True, "message": "后端类型已更新", "update": state.config.update}
|
||||
|
||||
|
||||
@router.post("/api/config/metrix")
|
||||
async def update_metrix_config(config: dict[str, Any] = Body(...)):
|
||||
state.reload_config()
|
||||
state.config.metrix = MetrixConfig.from_dict(config)
|
||||
state.config.save()
|
||||
return {"success": True, "message": "Metrix 连接配置已更新", "update": state.config.update}
|
||||
|
||||
|
||||
@router.post("/api/config/history-retention")
|
||||
async def update_history_retention(config: dict[str, Any] = Body(...)):
|
||||
state.reload_config()
|
||||
@@ -79,7 +109,6 @@ async def download_config():
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"Configure_{timestamp}.json"
|
||||
config_data = state.current_config().to_file_dict()
|
||||
config_data["ApiTokens"] = export_tokens()
|
||||
content = json.dumps(config_data, ensure_ascii=False, indent=2)
|
||||
return Response(
|
||||
content=content,
|
||||
@@ -100,8 +129,6 @@ async def upload_config(file: UploadFile = File(...)):
|
||||
|
||||
state.reload_config()
|
||||
_apply_config_data(data)
|
||||
if "ApiTokens" in data:
|
||||
import_tokens(data["ApiTokens"])
|
||||
state.config.save()
|
||||
return {"success": True, "message": "配置文件上传成功", "update": state.config.update}
|
||||
except json.JSONDecodeError as exc:
|
||||
@@ -113,6 +140,15 @@ async def upload_config(file: UploadFile = File(...)):
|
||||
|
||||
|
||||
def _apply_config_data(data: dict[str, Any]) -> None:
|
||||
if data.get("SourceType") in SOURCE_TYPES:
|
||||
state.config.source_type = data["SourceType"]
|
||||
if data.get("WarehouseType") in WAREHOUSE_TYPES:
|
||||
state.config.warehouse_type = data["WarehouseType"]
|
||||
|
||||
metrix_data = data.get("Metrix")
|
||||
if isinstance(metrix_data, dict):
|
||||
state.config.metrix = MetrixConfig.from_dict(metrix_data)
|
||||
|
||||
mysql_data = data.get("MySQL_DBInfo")
|
||||
if isinstance(mysql_data, dict):
|
||||
for key in ("host", "port", "user", "passwd", "dbname"):
|
||||
|
||||
+51
-13
@@ -10,7 +10,9 @@ from starlette.background import BackgroundTask
|
||||
from app import state
|
||||
from app.config import CACHE_DIR
|
||||
from app.database import DatabaseManager
|
||||
from app.services.platform import make_client
|
||||
from app.utils.files import remove_file_safely
|
||||
from app.warehouse import make_warehouse
|
||||
|
||||
|
||||
router = APIRouter(tags=["database"])
|
||||
@@ -47,19 +49,20 @@ def _dataframe_from_table(db: DatabaseManager, table_name: str) -> pd.DataFrame:
|
||||
return pd.DataFrame(result["data"], columns=columns)
|
||||
|
||||
|
||||
def _db() -> DatabaseManager:
|
||||
return DatabaseManager(state.current_config())
|
||||
def _db():
|
||||
"""Direct MySQL DatabaseManager, or a Metrix-backed warehouse with the same interface."""
|
||||
return make_warehouse(state.current_config())
|
||||
|
||||
|
||||
@router.post("/api/database/test")
|
||||
async def test_database():
|
||||
def test_database():
|
||||
db = _db()
|
||||
success, message = db.test_connection()
|
||||
return {"success": success, "message": message}
|
||||
|
||||
|
||||
@router.get("/api/database/info")
|
||||
async def get_database_info():
|
||||
def get_database_info():
|
||||
db = _db()
|
||||
try:
|
||||
return {"success": True, **db.get_server_info()}
|
||||
@@ -69,7 +72,7 @@ async def get_database_info():
|
||||
|
||||
@router.get("/api/database/tables")
|
||||
@router.post("/api/database/tables")
|
||||
async def get_tables():
|
||||
def get_tables():
|
||||
db = _db()
|
||||
try:
|
||||
return {"tables": db.get_tables()}
|
||||
@@ -78,7 +81,7 @@ async def get_tables():
|
||||
|
||||
|
||||
@router.post("/api/database/table/info")
|
||||
async def get_table_info(table_name: str = Body(..., embed=True)):
|
||||
def get_table_info(table_name: str = Body(..., embed=True)):
|
||||
db = _db()
|
||||
try:
|
||||
return db.get_table_info(table_name)
|
||||
@@ -87,7 +90,7 @@ async def get_table_info(table_name: str = Body(..., embed=True)):
|
||||
|
||||
|
||||
@router.post("/api/database/table/data")
|
||||
async def query_table_data(
|
||||
def query_table_data(
|
||||
table_name: str = Body(..., embed=True),
|
||||
page: int = Body(1),
|
||||
page_size: int = Body(50),
|
||||
@@ -102,7 +105,7 @@ async def query_table_data(
|
||||
|
||||
|
||||
@router.post("/api/database/table/query")
|
||||
async def query_table_with_filter(
|
||||
def query_table_with_filter(
|
||||
table_name: str = Body(..., embed=True),
|
||||
page: int = Body(1),
|
||||
page_size: int = Body(50),
|
||||
@@ -125,7 +128,7 @@ async def query_table_with_filter(
|
||||
|
||||
|
||||
@router.post("/api/database/table/truncate")
|
||||
async def truncate_table(table_name: str = Body(..., embed=True)):
|
||||
def truncate_table(table_name: str = Body(..., embed=True)):
|
||||
db = _db()
|
||||
try:
|
||||
db.truncate_table(table_name)
|
||||
@@ -135,7 +138,7 @@ async def truncate_table(table_name: str = Body(..., embed=True)):
|
||||
|
||||
|
||||
@router.post("/api/database/table/drop")
|
||||
async def drop_table(table_name: str = Body(..., embed=True)):
|
||||
def drop_table(table_name: str = Body(..., embed=True)):
|
||||
db = _db()
|
||||
try:
|
||||
db.drop_table(table_name)
|
||||
@@ -145,7 +148,7 @@ async def drop_table(table_name: str = Body(..., embed=True)):
|
||||
|
||||
|
||||
@router.post("/api/database/table/drop-all")
|
||||
async def drop_all_tables():
|
||||
def drop_all_tables():
|
||||
db = _db()
|
||||
try:
|
||||
result = db.drop_all_tables()
|
||||
@@ -160,7 +163,7 @@ async def drop_all_tables():
|
||||
|
||||
|
||||
@router.post("/api/database/execute")
|
||||
async def execute_sql(sql: str = Body(..., embed=True)):
|
||||
def execute_sql(sql: str = Body(..., embed=True)):
|
||||
db = _db()
|
||||
try:
|
||||
success, result = db.execute_sql(sql)
|
||||
@@ -173,8 +176,10 @@ async def execute_sql(sql: str = Body(..., embed=True)):
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
# Sync def so FastAPI runs it in a threadpool: exporting large tables (and the Metrix
|
||||
# export-job polling) is blocking and would otherwise freeze the single-worker event loop.
|
||||
@router.post("/api/download")
|
||||
async def download_table(
|
||||
def download_table(
|
||||
table_name: Optional[str] = Body(None, embed=True),
|
||||
table_names: Optional[list[str]] = Body(None, embed=True),
|
||||
file_format: str = Body("csv", alias="format"),
|
||||
@@ -188,6 +193,10 @@ async def download_table(
|
||||
if file_format == "csv" and len(requested_tables) != 1:
|
||||
raise HTTPException(status_code=400, detail="CSV 每次只能导出一张表")
|
||||
|
||||
config = state.current_config()
|
||||
if config.warehouse_type == "metrix":
|
||||
return _download_via_metrix(config, requested_tables, file_format)
|
||||
|
||||
db = _db()
|
||||
try:
|
||||
available_tables = set(db.get_tables())
|
||||
@@ -230,3 +239,32 @@ async def download_table(
|
||||
media_type=media_type,
|
||||
background=BackgroundTask(remove_file_safely, filepath),
|
||||
)
|
||||
|
||||
|
||||
def _download_via_metrix(config, requested_tables: list[str], file_format: str) -> FileResponse:
|
||||
"""Metrix 仓库模式:用平台导出任务生成文件后流式返回(避免分页上限丢行)。"""
|
||||
metrix = config.metrix.normalized()
|
||||
client = make_client(metrix)
|
||||
try:
|
||||
job_id = client.submit_export(metrix.database_conn_id, requested_tables, file_format, metrix.target_database)
|
||||
job = client.wait_job(job_id)
|
||||
if job.get("status") != "success":
|
||||
raise HTTPException(status_code=500, detail=f"导出失败: {job.get('error_code') or job.get('status')}")
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename_prefix = requested_tables[0] if len(requested_tables) == 1 else "tables"
|
||||
filename = f"{filename_prefix}_{timestamp}.{file_format}"
|
||||
filepath = CACHE_DIR / filename
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
client.download_job_file(job_id, filepath)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
media_type = "text/csv" if file_format == "csv" else "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
return FileResponse(
|
||||
path=str(filepath),
|
||||
filename=filename,
|
||||
media_type=media_type,
|
||||
background=BackgroundTask(remove_file_safely, filepath),
|
||||
)
|
||||
|
||||
+34
-17
@@ -1,3 +1,4 @@
|
||||
import time
|
||||
from datetime import date, datetime
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
@@ -14,6 +15,8 @@ from app.api.routers.task_runtime import (
|
||||
from app.config import AppConfig, CACHE_DIR, RemoteDataConfig
|
||||
from app.processor import DataProcessor, ProcessLogger
|
||||
from app.services.license import LicenseError, check_processing_allowed
|
||||
from app.services.platform import PlatformStorageDownloader, make_source_downloader
|
||||
from app.services.pipeline import RESULT_TABLES, run_import_and_report
|
||||
from app.services.remote_download import RemoteDataDownloader
|
||||
|
||||
|
||||
@@ -22,12 +25,17 @@ router = APIRouter(tags=["remote"])
|
||||
|
||||
@router.post("/api/remote/test")
|
||||
async def test_remote_connection(config: dict[str, Any] | None = Body(None)):
|
||||
remote_config = RemoteDataConfig.from_dict(config) if config else state.current_config().remote_data
|
||||
app_config = state.current_config()
|
||||
try:
|
||||
if app_config.source_type == "metrix":
|
||||
PlatformStorageDownloader(app_config).test_connection()
|
||||
return {"success": True, "message": "平台储存连接成功"}
|
||||
# FTP/SFTP: test the posted form config if provided, else the saved one.
|
||||
remote_config = RemoteDataConfig.from_dict(config) if config else app_config.remote_data
|
||||
RemoteDataDownloader(remote_config).test_connection()
|
||||
return {"success": True, "message": "远程服务器连接成功"}
|
||||
except Exception as exc:
|
||||
return {"success": False, "message": f"远程服务器连接失败: {exc}"}
|
||||
return {"success": False, "message": f"连接失败: {exc}"}
|
||||
|
||||
|
||||
@router.post("/api/remote/start")
|
||||
@@ -123,28 +131,37 @@ def _run_remote_processing(
|
||||
) -> None:
|
||||
final_status = "failed"
|
||||
try:
|
||||
logger.info(
|
||||
f"开始远程下载,协议: {remote_config.protocol.upper()},"
|
||||
f"服务器: {remote_config.host}:{remote_config.port},目录: {remote_config.remote_dir}"
|
||||
)
|
||||
downloader = RemoteDataDownloader(remote_config, logger.info)
|
||||
if app_config.source_type == "metrix":
|
||||
logger.info(f"开始从平台储存下载,目录: {remote_config.remote_dir}")
|
||||
else:
|
||||
logger.info(
|
||||
f"开始远程下载,协议: {remote_config.protocol.upper()},"
|
||||
f"服务器: {remote_config.host}:{remote_config.port},目录: {remote_config.remote_dir}"
|
||||
)
|
||||
downloader = make_source_downloader(app_config, logger.info)
|
||||
download_result = downloader.download_to(work_dir, target_dates=target_dates)
|
||||
logger.success(
|
||||
f"远程下载完成,共 {download_result.file_count} 个文件,"
|
||||
f"下载完成,共 {download_result.file_count} 个文件,"
|
||||
f"{_format_bytes(download_result.total_bytes)}"
|
||||
)
|
||||
|
||||
if download_result.file_count == 0:
|
||||
raise RuntimeError("远程目录中未下载到任何文件")
|
||||
raise RuntimeError("源目录中未下载到任何文件")
|
||||
|
||||
state.history_manager.update(task_id, file_count=download_result.file_count)
|
||||
logger.set_stage("license")
|
||||
log_license_check(logger, check_processing_allowed(work_dir))
|
||||
|
||||
processor = DataProcessor(app_config, work_dir, logger)
|
||||
result = processor.process()
|
||||
status = "completed" if result.get("success") else "failed"
|
||||
error = result.get("error")
|
||||
if app_config.warehouse_type == "metrix":
|
||||
started = time.time()
|
||||
run_import_and_report(work_dir, app_config, logger)
|
||||
status, error, elapsed = "completed", None, round(time.time() - started, 2)
|
||||
else:
|
||||
logger.set_stage("license")
|
||||
log_license_check(logger, check_processing_allowed(work_dir))
|
||||
processor = DataProcessor(app_config, work_dir, logger)
|
||||
result = processor.process()
|
||||
status = "completed" if result.get("success") else "failed"
|
||||
error = result.get("error")
|
||||
elapsed = result.get("elapsed_time", 0)
|
||||
if status == "completed" and remote_config.auto_delete_source:
|
||||
try:
|
||||
deleted_count = downloader.delete_source_files(download_result.remote_files)
|
||||
@@ -159,9 +176,9 @@ def _run_remote_processing(
|
||||
state.history_manager.update(
|
||||
task_id,
|
||||
status=status,
|
||||
elapsed_time=result.get("elapsed_time", 0),
|
||||
elapsed_time=elapsed,
|
||||
error=error,
|
||||
result_tables=["4G_结果表", "5G_结果表"],
|
||||
result_tables=RESULT_TABLES,
|
||||
)
|
||||
state.processing_tasks[task_id] = {
|
||||
"logs": state.history_manager.get_logs(task_id),
|
||||
|
||||
@@ -84,11 +84,15 @@ def _run_script(task_id: str, logger: ProcessLogger, logs: list[str], app_config
|
||||
temp_work_dir: Path | None = None
|
||||
try:
|
||||
logger.info("开始执行 SQL 脚本...")
|
||||
temp_work_dir = CACHE_DIR / task_id
|
||||
temp_work_dir.mkdir(parents=True, exist_ok=True)
|
||||
if app_config.warehouse_type == "metrix":
|
||||
from app.services.pipeline import run_report_sql
|
||||
|
||||
processor = DataProcessor(app_config, temp_work_dir, logger)
|
||||
processor._execute_sql_script()
|
||||
run_report_sql(app_config, logger)
|
||||
else:
|
||||
temp_work_dir = CACHE_DIR / task_id
|
||||
temp_work_dir.mkdir(parents=True, exist_ok=True)
|
||||
processor = DataProcessor(app_config, temp_work_dir, logger)
|
||||
processor._execute_sql_script()
|
||||
logger.success("SQL 脚本执行完成")
|
||||
set_task_stage(task_id, "completed", logs, status="completed")
|
||||
except Exception as exc:
|
||||
|
||||
@@ -168,19 +168,29 @@ def _task_finished(task_id: str) -> bool:
|
||||
|
||||
def _run_processing(task_id: str, work_dir: Path, logger: ProcessLogger, app_config: AppConfig) -> None:
|
||||
try:
|
||||
logger.set_stage("license")
|
||||
log_license_check(logger, check_processing_allowed(work_dir))
|
||||
if app_config.warehouse_type == "metrix":
|
||||
import time
|
||||
|
||||
processor = DataProcessor(app_config, work_dir, logger)
|
||||
result = processor.process()
|
||||
status = "completed" if result.get("success") else "failed"
|
||||
error = result.get("error")
|
||||
from app.services.pipeline import RESULT_TABLES, run_import_and_report
|
||||
|
||||
started = time.time()
|
||||
run_import_and_report(work_dir, app_config, logger)
|
||||
status, error, elapsed, result_tables = "completed", None, round(time.time() - started, 2), RESULT_TABLES
|
||||
else:
|
||||
logger.set_stage("license")
|
||||
log_license_check(logger, check_processing_allowed(work_dir))
|
||||
processor = DataProcessor(app_config, work_dir, logger)
|
||||
result = processor.process()
|
||||
status = "completed" if result.get("success") else "failed"
|
||||
error = result.get("error")
|
||||
elapsed = result.get("elapsed_time", 0)
|
||||
result_tables = ["4G_结果表", "5G_结果表"]
|
||||
state.history_manager.update(
|
||||
task_id,
|
||||
status=status,
|
||||
elapsed_time=result.get("elapsed_time", 0),
|
||||
elapsed_time=elapsed,
|
||||
error=error,
|
||||
result_tables=["4G_结果表", "5G_结果表"],
|
||||
result_tables=result_tables,
|
||||
)
|
||||
state.processing_tasks[task_id] = {
|
||||
"logs": state.history_manager.get_logs(task_id),
|
||||
|
||||
Reference in New Issue
Block a user