109 lines
3.3 KiB
Python
109 lines
3.3 KiB
Python
|
|
"""多渠道网关共享常量与 Redis Key 模板。"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from enum import StrEnum
|
|||
|
|
|
|||
|
|
|
|||
|
|
class UserChannelType(StrEnum):
|
|||
|
|
"""用户渠道来源类型。"""
|
|||
|
|
|
|||
|
|
FEISHU = "feishu"
|
|||
|
|
DINGTALK = "dingtalk"
|
|||
|
|
WECOM = "wecom"
|
|||
|
|
SLACK = "slack"
|
|||
|
|
TELEGRAM = "telegram"
|
|||
|
|
OPEN_API = "open_api"
|
|||
|
|
MANUAL = "manual"
|
|||
|
|
SSO = "sso"
|
|||
|
|
|
|||
|
|
|
|||
|
|
class DeliveryStatus(StrEnum):
|
|||
|
|
"""消息投递状态。"""
|
|||
|
|
|
|||
|
|
COMPLETE = "complete"
|
|||
|
|
PARTIAL_FAILED = "partial_failed"
|
|||
|
|
FAILED = "failed"
|
|||
|
|
DEAD_LETTER = "dead_letter"
|
|||
|
|
PENDING = "pending"
|
|||
|
|
|
|||
|
|
|
|||
|
|
class DispatchResult(StrEnum):
|
|||
|
|
"""出站调度结果。"""
|
|||
|
|
|
|||
|
|
SUCCESS = "success"
|
|||
|
|
PERMANENT_FAILURE = "permanent_failure"
|
|||
|
|
RETRYABLE = "retryable"
|
|||
|
|
|
|||
|
|
|
|||
|
|
class InboundRejectionReason(StrEnum):
|
|||
|
|
"""入站消息拒绝原因。"""
|
|||
|
|
|
|||
|
|
DUPLICATE = "duplicate"
|
|||
|
|
UNKNOWN_CHANNEL = "unknown_channel"
|
|||
|
|
CONFIG_ERROR = "config_error"
|
|||
|
|
SECURITY_REJECTED = "security_rejected"
|
|||
|
|
DM_NOT_ALLOWED = "dm_not_allowed"
|
|||
|
|
DM_PAIRING_REQUIRED = "dm_pairing_required"
|
|||
|
|
GROUP_NOT_ALLOWED = "group_not_allowed"
|
|||
|
|
BOT_LOOP_DETECTED = "bot_loop_detected"
|
|||
|
|
RATE_LIMITED = "rate_limited"
|
|||
|
|
INTERNAL_ERROR = "internal_error"
|
|||
|
|
|
|||
|
|
|
|||
|
|
# Redis Stream
|
|||
|
|
CHANNEL_STREAM_KEY = "ch:messages"
|
|||
|
|
CHANNEL_CONSUMER_GROUP = "ch:dispatchers"
|
|||
|
|
|
|||
|
|
# Redis Pub/Sub
|
|||
|
|
CHANNEL_CONFIG_CHANGE_CHANNEL = "ch:config:changed"
|
|||
|
|
|
|||
|
|
# Redis Key 模板
|
|||
|
|
CHANNEL_PROCESSED_KEY_TEMPLATE = "ch:processed:{message_id}"
|
|||
|
|
CHANNEL_DELIVERED_KEY_TEMPLATE = "ch:delivered:{message_id}"
|
|||
|
|
CHANNEL_PROCESSING_KEY_TEMPLATE = "ch:processing:{message_id}"
|
|||
|
|
CHANNEL_PUBLISHING_KEY_TEMPLATE = "ch:publishing:{message_id}"
|
|||
|
|
CHANNEL_COMPENSATE_LOCK_KEY = "ch:compensate:lock"
|
|||
|
|
CHANNEL_RATE_LIMIT_KEY_TEMPLATE = "ch:rate:{channel_type}:{account_id}:{actor_id}"
|
|||
|
|
|
|||
|
|
# TTL / 时间常量(秒)
|
|||
|
|
CHANNEL_DELIVERED_TTL_SECONDS = 7 * 24 * 3600 # 已投递标记保留 7 天
|
|||
|
|
CHANNEL_PROCESSING_LOCK_TTL_SECONDS = 300 # processing 锁初始 TTL
|
|||
|
|
CHANNEL_PROCESSING_LOCK_RENEW_INTERVAL_SECONDS = 60 # 锁续期间隔
|
|||
|
|
CHANNEL_PUBLISHING_LOCK_TTL_SECONDS = 300
|
|||
|
|
CHANNEL_PROCESSED_TTL_SECONDS = 300 # 入站去重默认 TTL
|
|||
|
|
CHANNEL_COMPENSATE_LOCK_TTL_SECONDS = 300
|
|||
|
|
|
|||
|
|
# 指数退避
|
|||
|
|
CHANNEL_MAX_DELIVERY_ATTEMPTS = 5
|
|||
|
|
CHANNEL_MAX_COMPENSATE_ATTEMPTS = 5
|
|||
|
|
CHANNEL_BACKOFF_BASE_SECONDS = 2
|
|||
|
|
CHANNEL_BACKOFF_MAX_SECONDS = 3600
|
|||
|
|
|
|||
|
|
# 消息认领:pending 条目空闲多久后视为死消费者(毫秒)
|
|||
|
|
CHANNEL_PENDING_CLAIM_MIN_IDLE_MS = (CHANNEL_PROCESSING_LOCK_TTL_SECONDS + 10) * 1000
|
|||
|
|
|
|||
|
|
|
|||
|
|
def channel_processed_key(message_id: str | int | None) -> str:
|
|||
|
|
return CHANNEL_PROCESSED_KEY_TEMPLATE.format(message_id=message_id or "unknown")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def channel_delivered_key(message_id: str | int | None) -> str:
|
|||
|
|
return CHANNEL_DELIVERED_KEY_TEMPLATE.format(message_id=message_id or "unknown")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def channel_processing_key(message_id: str | int | None) -> str:
|
|||
|
|
return CHANNEL_PROCESSING_KEY_TEMPLATE.format(message_id=message_id or "unknown")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def channel_publishing_key(message_id: str | int | None) -> str:
|
|||
|
|
return CHANNEL_PUBLISHING_KEY_TEMPLATE.format(message_id=message_id or "unknown")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def channel_rate_limit_key(channel_type: str, account_id: str, actor_id: str) -> str:
|
|||
|
|
return CHANNEL_RATE_LIMIT_KEY_TEMPLATE.format(
|
|||
|
|
channel_type=channel_type,
|
|||
|
|
account_id=account_id,
|
|||
|
|
actor_id=actor_id,
|
|||
|
|
)
|