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,150 @@
# -*- coding: utf-8 -*-
"""
TraceContext 与 TraceSpan 数据模型
基于 contextvars 的请求级追踪上下文,存储 request_id 和有序 span 列表。
支持 HTTP / SSE / WebSocket / 后台 Job 四种 trace 类型,
每种类型使用不同的 request_id 前缀以便区分。
"""
from __future__ import annotations
import contextvars
import uuid
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
# ──────────────────────────────────────────────
# Span 类型常量(字符串常量类,不用 Enum
# ──────────────────────────────────────────────
class SpanType:
"""所有支持的 span 类型常量。"""
HTTP_IN = "HTTP_IN"
AUTH = "AUTH"
ROUTE = "ROUTE"
SERVICE = "SERVICE"
DB_QUERY = "DB_QUERY"
DB_CONN = "DB_CONN"
DB_CONN_RELEASE = "DB_CONN_RELEASE"
HTTP_OUT = "HTTP_OUT"
ERROR = "ERROR"
DB_ERROR = "DB_ERROR"
MIDDLEWARE = "MIDDLEWARE"
MIDDLEWARE_ERROR = "MIDDLEWARE_ERROR"
SSE_START = "SSE_START"
SSE_EVENT = "SSE_EVENT"
SSE_END = "SSE_END"
AI_CALL = "AI_CALL"
AI_STREAM = "AI_STREAM"
AI_ERROR = "AI_ERROR"
WS_CONNECT = "WS_CONNECT"
WS_MESSAGE = "WS_MESSAGE"
WS_DISCONNECT = "WS_DISCONNECT"
JOB_START = "JOB_START"
JOB_END = "JOB_END"
JOB_ERROR = "JOB_ERROR"
# ──────────────────────────────────────────────
# Trace 类型常量
# ──────────────────────────────────────────────
class TraceType:
"""Trace 类型HTTP / SSE / WebSocket / 后台 Job。"""
HTTP = "http"
SSE = "sse"
WS = "ws"
JOB = "job"
# ──────────────────────────────────────────────
# 数据模型
# ──────────────────────────────────────────────
@dataclass
class TraceSpan:
"""单个追踪节点,记录某一层的函数调用信息。"""
span_type: str # SpanType 中的常量值
module: str # 模块路径,如 "xcx_tasks"
function: str # 函数名,如 "get_task_list"
description_zh: str # 中文描述
description_en: str # 英文描述
params: dict[str, Any] # 调用参数
result_summary: str # 结果摘要
duration_ms: float # 耗时(毫秒)
timestamp: str # ISO 格式时间戳
extra: dict[str, Any] = field(default_factory=dict) # 额外信息SQL 等)
@dataclass
class TraceContext:
"""请求级追踪上下文,维护 request_id 和有序 span 列表。"""
request_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
trace_type: str = TraceType.HTTP # http / sse / ws / job
start_time: datetime = field(default_factory=datetime.now)
method: str = ""
path: str = ""
user_id: int | None = None
site_id: int | None = None
spans: list[TraceSpan] = field(default_factory=list)
def add_span(self, span: TraceSpan) -> None:
"""追加 span 到列表,保持插入顺序。"""
self.spans.append(span)
# ──────────────────────────────────────────────
# contextvars 存储
# ──────────────────────────────────────────────
trace_context_var: contextvars.ContextVar[TraceContext | None] = contextvars.ContextVar(
"trace_context", default=None
)
def get_current_trace() -> TraceContext | None:
"""获取当前请求的 TraceContext无则返回 None"""
return trace_context_var.get()
def set_current_trace(ctx: TraceContext) -> contextvars.Token:
"""设置当前请求的 TraceContext返回 Token 用于后续恢复。"""
return trace_context_var.set(ctx)
# ──────────────────────────────────────────────
# 工厂函数:创建不同类型的 TraceContext
# ──────────────────────────────────────────────
def create_http_trace(method: str, path: str) -> TraceContext:
"""创建 HTTP 请求的 TraceContextrequest_id = uuid hex[:12])。"""
return TraceContext(
request_id=uuid.uuid4().hex[:12],
trace_type=TraceType.HTTP,
method=method,
path=path,
)
def create_ws_trace() -> TraceContext:
"""创建 WebSocket 连接的 TraceContextrequest_id = ws_ 前缀)。"""
return TraceContext(
request_id=f"ws_{uuid.uuid4().hex[:12]}",
trace_type=TraceType.WS,
)
def create_job_trace(job_name: str) -> TraceContext:
"""创建后台 Job 的 TraceContextrequest_id = job_ 前缀)。"""
return TraceContext(
request_id=f"job_{uuid.uuid4().hex[:12]}",
trace_type=TraceType.JOB,
path=job_name,
)