"""飞书 outbox 入队服务。""" from __future__ import annotations import hashlib from typing import Optional from sqlalchemy import and_, select from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession from ....models import FeishuReplyOutbox class FeishuOutboxService: """复用 reply_hash 幂等策略的 outbox 写入入口。""" @staticmethod def _build_reply_hash(*, source_message_id: Optional[str], message_text: str) -> Optional[str]: source = str(source_message_id or "").strip() body = str(message_text or "").strip() if not source or not body: return None raw = f"{source}{body}".encode("utf-8") return hashlib.md5(raw, usedforsecurity=False).hexdigest() @classmethod async def enqueue( cls, db: AsyncSession, *, tenant_id: int, chat_id: str, text: str, source_message_id: Optional[str], dedupe_seed: Optional[str] = None, trace_id: Optional[str] = None, ) -> Optional[int]: normalized_chat_id = str(chat_id or "").strip()[:128] if not normalized_chat_id: return None normalized_text = str(text or "").strip()[:2000] or "已收到,正在处理。" normalized_source_message_id = (str(source_message_id or "").strip()[:128] or None) hash_source = normalized_source_message_id or (str(dedupe_seed or "").strip()[:128] or None) reply_hash = cls._build_reply_hash(source_message_id=hash_source, message_text=normalized_text) if reply_hash: sent_exists = ( await db.execute( select(FeishuReplyOutbox.id) .where( and_( FeishuReplyOutbox.tenant_id == int(tenant_id), FeishuReplyOutbox.reply_hash == reply_hash, FeishuReplyOutbox.status == "sent", ) ) .limit(1) ) ).scalar_one_or_none() if sent_exists is not None: return int(sent_exists) row = FeishuReplyOutbox( tenant_id=int(tenant_id), trace_id=(str(trace_id or "").strip()[:64] or None), source_message_id=normalized_source_message_id, chat_id=normalized_chat_id, message_text=normalized_text, reply_hash=reply_hash, status="pending", attempts=0, next_retry_at=None, ) try: async with db.begin_nested(): db.add(row) await db.flush() except IntegrityError: if reply_hash: sent_exists = ( await db.execute( select(FeishuReplyOutbox.id) .where( and_( FeishuReplyOutbox.tenant_id == int(tenant_id), FeishuReplyOutbox.reply_hash == reply_hash, FeishuReplyOutbox.status == "sent", ) ) .limit(1) ) ).scalar_one_or_none() if sent_exists is not None: return int(sent_exists) raise return int(row.id) if row.id is not None else None