feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
124
apps/etl/connectors/feiqiu/tasks/dws/task_engine.py
Normal file
124
apps/etl/connectors/feiqiu/tasks/dws/task_engine.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# AI_CHANGELOG
|
||||
# - 2026-03-29 | Prompt: DWS_TASK_ENGINE ETL 任务 | 新建文件。
|
||||
# 编排任务引擎全流程:完成检查 → 过期检查 → 任务生成。
|
||||
# 通过 HTTP 调用后端 POST /api/internal/run-job 按 job_name 执行。
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
DWS 任务引擎编排任务(DWS_TASK_ENGINE)
|
||||
|
||||
在 DWS 指数计算完成后执行,按顺序调用后端任务引擎的各个步骤:
|
||||
1. recall_completion_check — 检测召回是否完成,生成回访任务
|
||||
2. task_expiry_check — 标记超时未处理的任务
|
||||
3. task_generator — 根据 WBI/NCI/RS 指数生成/替换任务
|
||||
|
||||
通过 HTTP 调用后端 POST /api/internal/run-job(Internal-Token 认证),
|
||||
每步失败仅记录日志,不中断后续步骤。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from ..base_task import BaseTask, TaskContext
|
||||
|
||||
# 加载根 .env(BACKEND_API_URL / INTERNAL_API_TOKEN 不在 AppConfig 映射中)
|
||||
# task_engine.py → dws/ → tasks/ → feiqiu/ → connectors/ → etl/ → apps/ → root
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[6]
|
||||
load_dotenv(_REPO_ROOT / ".env", override=False)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_TIMEOUT = (5, 30) # 连接 5s,读取 30s(任务执行可能较慢)
|
||||
|
||||
# 按顺序执行的后端任务列表
|
||||
_JOB_SEQUENCE = [
|
||||
"recall_completion_check",
|
||||
"task_expiry_check",
|
||||
"task_generator",
|
||||
]
|
||||
|
||||
|
||||
def _run_backend_job(backend_url: str, token: str, job_name: str) -> dict:
|
||||
"""调用后端 POST /api/internal/run-job 执行指定任务。
|
||||
|
||||
Returns:
|
||||
{"success": bool, "message": str} 或 {"success": False, "message": error}
|
||||
"""
|
||||
url = f"{backend_url}/api/internal/run-job"
|
||||
headers = {
|
||||
"Authorization": f"Internal-Token {token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
body = {"job_name": job_name}
|
||||
|
||||
try:
|
||||
resp = requests.post(url, json=body, headers=headers, timeout=_TIMEOUT)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
# 后端 ResponseWrapperMiddleware 包装:{"code": 0, "data": {...}}
|
||||
inner = data.get("data", data)
|
||||
return {
|
||||
"success": inner.get("success", False),
|
||||
"message": inner.get("message", ""),
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"HTTP {resp.status_code}: {resp.text[:200]}",
|
||||
}
|
||||
except requests.RequestException as exc:
|
||||
return {"success": False, "message": str(exc)}
|
||||
|
||||
|
||||
class DwsTaskEngineTask(BaseTask):
|
||||
"""DWS 任务引擎编排任务。
|
||||
|
||||
不读写 DWS 表,仅通过 HTTP 调用后端执行任务引擎步骤。
|
||||
继承 BaseTask 而非 BaseDwsTask,因为不需要 DWS 层的数据操作方法。
|
||||
"""
|
||||
|
||||
def get_task_code(self) -> str:
|
||||
return "DWS_TASK_ENGINE"
|
||||
|
||||
def extract(self, context: TaskContext) -> dict[str, Any]:
|
||||
"""无需提取数据,返回空上下文。"""
|
||||
return {}
|
||||
|
||||
def load(self, extracted: dict[str, Any], context: TaskContext) -> dict[str, Any]:
|
||||
"""按顺序调用后端任务引擎的各个步骤。"""
|
||||
backend_url = os.environ.get("BACKEND_API_URL", "").rstrip("/")
|
||||
token = os.environ.get("INTERNAL_API_TOKEN", "")
|
||||
|
||||
if not backend_url:
|
||||
self.logger.error("DWS_TASK_ENGINE 跳过:BACKEND_API_URL 未配置")
|
||||
return {"skipped": True, "reason": "BACKEND_API_URL 未配置"}
|
||||
if not token:
|
||||
self.logger.error("DWS_TASK_ENGINE 跳过:INTERNAL_API_TOKEN 未配置")
|
||||
return {"skipped": True, "reason": "INTERNAL_API_TOKEN 未配置"}
|
||||
|
||||
results: dict[str, Any] = {}
|
||||
|
||||
for job_name in _JOB_SEQUENCE:
|
||||
self.logger.info("DWS_TASK_ENGINE: 执行 %s ...", job_name)
|
||||
result = _run_backend_job(backend_url, token, job_name)
|
||||
success = result.get("success", False)
|
||||
message = result.get("message", "")
|
||||
|
||||
results[job_name] = {"success": success, "message": message}
|
||||
|
||||
if success:
|
||||
self.logger.info(
|
||||
"DWS_TASK_ENGINE: %s 成功 — %s", job_name, message
|
||||
)
|
||||
else:
|
||||
self.logger.warning(
|
||||
"DWS_TASK_ENGINE: %s 失败 — %s", job_name, message
|
||||
)
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user