feat(feishu): 添加飞书入站事件inbox和混合数据库协调功能

- 实现飞书入站事件持久化inbox机制,支持状态管理、租约锁定和重试退避
- 添加混合数据库基线协调工具,确保平台PostgreSQL结构安全对齐
- 增加运行组件心跳检测和readiness就绪检查机制
- 实现app_ticket事件的安全轮换和验证处理
- 添加生产环境运行编排和fail-closed安全机制
- 支持webhook快速确认和长连接独立进程处理
- 完善个人数据擦除时的待处理事件清理功能
```
This commit is contained in:
2026-07-27 17:14:37 +08:00
parent d7db84571d
commit eb8267ed18
61 changed files with 8703 additions and 183 deletions

View File

@@ -1,6 +1,8 @@
from app.tasks.constants import (
TASK_DISPATCH_PENDING_EVENTS,
TASK_GENERATE_RISK_EVENTS,
TASK_PROCESS_DUE_FEISHU_INBOUND,
TASK_PROCESS_FEISHU_INBOUND,
TASK_PUSH_ATTENDANCE_SUMMARY,
TASK_PUSH_DAILY_BRIEF,
TASK_PUSH_PROJECT_WEEKLY,
@@ -13,15 +15,21 @@ from app.tasks.constants import (
TASK_RUN_MARKET_CLOSE,
TASK_RUN_MARKET_REPORT,
TASK_RUN_SUBSCRIPTION_CYCLE,
TASK_RECORD_WORKER_HEARTBEAT,
)
from app.core.background.task_queue.dispatcher import dispatch_task
from app.core.background.task_queue.events import enqueue_event_dispatch
from app.core.background.task_queue.feishu import (
enqueue_feishu_inbound_cycle,
enqueue_feishu_inbound_event,
)
from app.core.background.task_queue.legacy import (
enqueue_legacy_project_sync,
enqueue_legacy_task_sync,
)
from app.core.background.task_queue.lifecycle import enqueue_lifecycle_report
from app.core.background.task_queue.market import enqueue_market_close, enqueue_market_report
from app.core.background.task_queue.observability import enqueue_worker_heartbeat
from app.core.background.task_queue.reports import (
enqueue_attendance_summary_push,
enqueue_daily_brief_push,
@@ -37,6 +45,8 @@ from app.core.background.task_queue.subscriptions import enqueue_subscription_cy
__all__ = [
"TASK_DISPATCH_PENDING_EVENTS",
"TASK_GENERATE_RISK_EVENTS",
"TASK_PROCESS_DUE_FEISHU_INBOUND",
"TASK_PROCESS_FEISHU_INBOUND",
"TASK_PUSH_ATTENDANCE_SUMMARY",
"TASK_PUSH_DAILY_BRIEF",
"TASK_PUSH_PROJECT_WEEKLY",
@@ -49,15 +59,19 @@ __all__ = [
"TASK_RUN_MARKET_CLOSE",
"TASK_RUN_MARKET_REPORT",
"TASK_RUN_SUBSCRIPTION_CYCLE",
"TASK_RECORD_WORKER_HEARTBEAT",
"dispatch_task",
"enqueue_attendance_summary_push",
"enqueue_daily_brief_push",
"enqueue_event_dispatch",
"enqueue_feishu_inbound_cycle",
"enqueue_feishu_inbound_event",
"enqueue_legacy_project_sync",
"enqueue_legacy_task_sync",
"enqueue_lifecycle_report",
"enqueue_market_close",
"enqueue_market_report",
"enqueue_worker_heartbeat",
"enqueue_project_weekly_push",
"enqueue_risk_progress_push",
"enqueue_risk_event_generation",

View File

@@ -0,0 +1,37 @@
from typing import Any
from app.application.feishu.inbound import (
process_due_feishu_inbound_events,
process_feishu_inbound_event,
)
from app.core.background.task_queue.dispatcher import dispatch_task
from app.core.constants import ActorValue
from app.modules.feishu.constants import FEISHU_INBOUND_BATCH_SIZE
from app.tasks.constants import (
TASK_PROCESS_DUE_FEISHU_INBOUND,
TASK_PROCESS_FEISHU_INBOUND,
)
def enqueue_feishu_inbound_event(
event_key: str,
*,
actor: str = ActorValue.WORKER,
) -> dict[str, Any]:
return dispatch_task(
TASK_PROCESS_FEISHU_INBOUND,
{"event_key": event_key, "actor": actor},
lambda: process_feishu_inbound_event(event_key, actor=actor),
)
def enqueue_feishu_inbound_cycle(
*,
limit: int = FEISHU_INBOUND_BATCH_SIZE,
actor: str = ActorValue.SCHEDULER,
) -> dict[str, Any]:
return dispatch_task(
TASK_PROCESS_DUE_FEISHU_INBOUND,
{"limit": limit, "actor": actor},
lambda: process_due_feishu_inbound_events(limit=limit, actor=actor),
)

View File

@@ -0,0 +1,32 @@
from socket import gethostname
from app.core.background.task_queue.dispatcher import dispatch_task
from app.core.constants import ActorValue
from app.core.database import SessionLocal
from app.modules.observability.constants import HeartbeatComponent
from app.modules.observability.service import ObservabilityService
from app.tasks.constants import TASK_RECORD_WORKER_HEARTBEAT
def enqueue_worker_heartbeat(
actor: str = ActorValue.SCHEDULER,
) -> dict:
"""Ask a Celery worker to prove it can consume tasks."""
return dispatch_task(
TASK_RECORD_WORKER_HEARTBEAT,
{"actor": ActorValue.WORKER},
lambda: _record_inline_heartbeat(actor),
)
def _record_inline_heartbeat(actor: str) -> dict:
db = SessionLocal()
try:
return ObservabilityService(db).record_heartbeat(
component=HeartbeatComponent.WORKER,
instance_id=f"inline:{gethostname()}",
actor=actor,
)
finally:
db.close()