98 lines
3.4 KiB
Python
98 lines
3.4 KiB
Python
"""飞书 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
|