Files
Neo-ZQYY/apps/backend/app/services/task_expiry.py
Neo caf179a5da feat: 2026-04-15~05-02 累积变更基线 — AI 重构 + Runtime Context + DWS 修复
涵盖(每条对应已存的审计记录):
- AI 模块拆分:apps/backend/app/ai/apps -> prompts/(8 个 APP + app2a 派生)
  audit: 2026-04-20__ai-module-complete.md
- admin-web AI 管理套件:AIDashboard / AIOperations / AIRunLogs / AITriggers / TriggerManager
  audit: 2026-04-21__admin-web-ai-management-suite.md
- App2 财务洞察 prompt v3 -> v5.1 + 小程序 AI 接入(chat / board-finance)
  audit: 2026-04-22__app2_prompt_v5_1_and_miniprogram_ai_insight.md
- App2 prewarm 全过滤器 + AI 触发器 cron reschedule
  audit: 2026-04-21__app2-finance-prewarm-all-filters.md
  migration: 20260420_ai_trigger_jobs_and_app2_prewarm.sql / 20260421_app2_prewarm_cron_reschedule.sql
- AppType 联合类型对齐 + adminAiAppTypes.test.ts
  audit: 2026-04-30__admin_web_ai_app_type_alignment.md
- DashScope tokens_used 提取修复
  audit: 2026-04-30__backend_dashscope_tokens_used_extraction.md
- App3 线索完整详情 prompt
  audit: 2026-05-01__backend_app3_full_detail_prompt.md
- Runtime Context 沙箱(5-1~5-2 主线):
  - 后端 schema/service + admin_runtime_context / xcx_runtime_clock 两个 router
  - admin-web RuntimeContext.tsx + miniprogram runtime-clock.ts
  - migration: 20260501__runtime_context_sandbox.sql
  - tools/db/verify_admin_web_sandbox.py + verify_sandbox_end_to_end.py
  - database/changes: 7 份 sandbox_* 验证报告
- 飞球 DWS 修复:finance_area_daily 区域汇总 + task_engine 调整
  + RLS 视图业务日上界(migration 20260502 + scripts/ops/gen_rls_business_date_migration.py)

合规:
- .gitignore 启用 tmp/ 排除
- 不入仓:apps/etl/connectors/feiqiu/.env(API_TOKEN secret,本地修改保留)

待验证清单:
- docs/audit/changes/2026-05-04__cumulative_baseline_pending_verification.md
  每个主题的功能完整性 / 上线验证几乎都未收口,按优先级 P0~P3 逐一处理
2026-05-04 02:30:19 +08:00

132 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
有效期轮询器Task Expiry Checker
每小时运行一次,检查 expires_at 不为 NULL 且已过期的 active 任务,
将其标记为 inactive 并记录 history。
由 trigger_jobs 中的 task_expiry_check 配置驱动。
"""
import json
import logging
from app.services.runtime_context import as_runtime_now_param, task_runtime_filter
from app.trace.decorators import trace_service
logger = logging.getLogger(__name__)
def _get_connection():
"""延迟导入 get_connection避免模块级导入失败。"""
from app.database import get_connection
return get_connection()
def _insert_history(
cur,
task_id: int,
action: str,
old_status: str | None = None,
new_status: str | None = None,
old_task_type: str | None = None,
new_task_type: str | None = None,
detail: dict | None = None,
) -> None:
"""在 coach_task_history 中记录变更。"""
cur.execute(
"""
INSERT INTO biz.coach_task_history
(task_id, action, old_status, new_status,
old_task_type, new_task_type, detail)
VALUES (%s, %s, %s, %s, %s, %s, %s)
""",
(
task_id,
action,
old_status,
new_status,
old_task_type,
new_task_type,
json.dumps(detail) if detail else None,
),
)
@trace_service(description_zh="执行任务过期检查", description_en="Run task expiry check")
def run() -> dict:
"""
有效期轮询主流程。
1. SELECT id, task_type FROM biz.coach_tasks
WHERE expires_at IS NOT NULL AND expires_at < NOW() AND status = 'active'
2. 逐条 UPDATE status = 'inactive'
3. INSERT coach_task_history (action='expired')
每条过期任务独立事务,失败不影响其他。
返回: {"expired_count": int}
"""
expired_count = 0
conn = _get_connection()
try:
# 查询所有已过期的 active 任务。沙箱模式按业务时间判断,并只处理当前运行实例。
expired_tasks = []
with conn.cursor() as cur:
cur.execute("SELECT site_id FROM biz.sites WHERE is_active = true")
site_ids = [row[0] for row in cur.fetchall()]
for site_id in site_ids:
runtime_now = as_runtime_now_param(site_id, conn=conn)
runtime_clause, runtime_params = task_runtime_filter(site_id, conn=conn)
cur.execute(
f"""
SELECT id, task_type, site_id
FROM biz.coach_tasks
WHERE site_id = %s
{runtime_clause}
AND expires_at IS NOT NULL
AND expires_at < %s
AND status = 'active'
""",
[site_id, *runtime_params, runtime_now],
)
expired_tasks.extend(cur.fetchall())
conn.commit()
# 逐条处理,每条独立事务
for task_id, task_type, site_id in expired_tasks:
try:
runtime_now = as_runtime_now_param(site_id, conn=conn)
with conn.cursor() as cur:
cur.execute("BEGIN")
cur.execute(
"""
UPDATE biz.coach_tasks
SET status = 'inactive', updated_at = %s
WHERE id = %s AND status = 'active'
""",
(runtime_now, task_id),
)
_insert_history(
cur,
task_id,
action="expired",
old_status="active",
new_status="inactive",
old_task_type=task_type,
new_task_type=task_type,
)
conn.commit()
expired_count += 1
except Exception:
logger.exception("处理过期任务失败: task_id=%s", task_id)
conn.rollback()
finally:
conn.close()
logger.info("有效期轮询完成: expired_count=%d", expired_count)
return {"expired_count": expired_count}