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:
Neo
2026-04-06 00:03:48 +08:00
parent 70324d8542
commit 6f8f12314f
515 changed files with 76604 additions and 7456 deletions

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""
AI 触发工具 — DWS 任务完成后通知后端 AI 模块。
通过 POST /api/internal/ai/trigger 发送事件,
后端异步执行 AI 调用链App2 预生成、消费事件链等)。
失败时仅记录日志,不中断 ETL 流程。
"""
from __future__ import annotations
import logging
import os
from typing import Any
import requests
logger = logging.getLogger(__name__)
# 超时设置(秒):连接 5s读取 10s
_TIMEOUT = (5, 10)
def trigger_ai_event(
event_type: str,
site_id: int,
member_id: int | None = None,
payload: dict[str, Any] | None = None,
connector_type: str = "feiqiu",
) -> bool:
"""向后端 AI 模块发送触发事件。
Args:
event_type: 事件类型dws_completed / consumption / note_created / task_assigned
site_id: 门店 ID
member_id: 会员 ID可选
payload: 附加数据(可选)
connector_type: 连接器类型,默认 feiqiu
Returns:
True 表示发送成功False 表示失败(已记录日志)
"""
backend_url = os.environ.get("BACKEND_API_URL", "").rstrip("/")
token = os.environ.get("INTERNAL_API_TOKEN", "")
if not backend_url:
logger.warning("AI 触发跳过BACKEND_API_URL 未配置")
return False
if not token:
logger.warning("AI 触发跳过INTERNAL_API_TOKEN 未配置")
return False
url = f"{backend_url}/api/internal/ai/trigger"
headers = {
"Authorization": f"Internal-Token {token}",
"Content-Type": "application/json",
}
body = {
"event_type": event_type,
"connector_type": connector_type,
"site_id": site_id,
"member_id": member_id,
"payload": payload or {},
}
try:
resp = requests.post(url, json=body, headers=headers, timeout=_TIMEOUT)
if resp.status_code == 200:
data = resp.json()
logger.info(
"AI 触发成功event=%s site=%s job_id=%s",
event_type,
site_id,
data.get("trigger_job_id"),
)
return True
else:
logger.warning(
"AI 触发失败event=%s site=%s status=%s body=%s",
event_type,
site_id,
resp.status_code,
resp.text[:200],
)
return False
except requests.RequestException as exc:
logger.warning(
"AI 触发异常event=%s site=%s error=%s",
event_type,
site_id,
exc,
)
return False