2026-05-31 16:44:13 +08:00
|
|
|
|
"""PostgreSQL 渠道网关模型 - 渠道绑定、出站重试、消息审计日志、插件注册"""
|
2026-05-30 21:53:09 +08:00
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import (
|
|
|
|
|
|
JSON,
|
|
|
|
|
|
Boolean,
|
|
|
|
|
|
Column,
|
|
|
|
|
|
DateTime,
|
|
|
|
|
|
ForeignKey,
|
|
|
|
|
|
Integer,
|
|
|
|
|
|
String,
|
|
|
|
|
|
Text,
|
|
|
|
|
|
UniqueConstraint,
|
|
|
|
|
|
)
|
|
|
|
|
|
from yuxi.storage.postgres.models_business import Base
|
|
|
|
|
|
from yuxi.utils.datetime_utils import format_utc_datetime, utc_now_naive
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChannelBinding(Base):
|
|
|
|
|
|
"""渠道 Agent 绑定路由表 — Worker Step 3 查询,决定消息路由到哪个 Agent"""
|
|
|
|
|
|
|
|
|
|
|
|
__tablename__ = "channel_bindings"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
|
|
|
|
|
|
channel_type = Column(String(20), nullable=False, index=True, comment="渠道类型: feishu/dingtalk/hooks")
|
|
|
|
|
|
account_id = Column(String(64), nullable=False, default="", comment="渠道账户标识")
|
|
|
|
|
|
group_id = Column(String(128), nullable=False, default="", comment="群组标识,DM 为空字符串")
|
|
|
|
|
|
agent_config_id = Column(Integer, ForeignKey("agent_configs.id"), nullable=False, comment="绑定的 Agent 配置 ID")
|
|
|
|
|
|
session_key_strategy = Column(String(20), nullable=False, default="auto", comment="会话键策略: auto/main/channel_group/custom")
|
|
|
|
|
|
is_enabled = Column(Boolean, nullable=False, default=True, index=True, comment="是否启用")
|
|
|
|
|
|
|
|
|
|
|
|
created_by = Column(String(64), nullable=True)
|
|
|
|
|
|
updated_by = Column(String(64), nullable=True)
|
|
|
|
|
|
created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间")
|
|
|
|
|
|
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间")
|
|
|
|
|
|
is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是")
|
|
|
|
|
|
deleted_at = Column(DateTime, nullable=True, comment="删除时间")
|
|
|
|
|
|
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
|
|
UniqueConstraint("channel_type", "account_id", "group_id", name="uq_channel_binding"),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": self.id,
|
|
|
|
|
|
"channel_type": self.channel_type,
|
|
|
|
|
|
"account_id": self.account_id,
|
|
|
|
|
|
"group_id": self.group_id,
|
|
|
|
|
|
"agent_config_id": self.agent_config_id,
|
|
|
|
|
|
"session_key_strategy": self.session_key_strategy,
|
|
|
|
|
|
"is_enabled": bool(self.is_enabled),
|
|
|
|
|
|
"created_by": self.created_by,
|
|
|
|
|
|
"updated_by": self.updated_by,
|
|
|
|
|
|
"created_at": format_utc_datetime(self.created_at),
|
|
|
|
|
|
"updated_at": format_utc_datetime(self.updated_at),
|
|
|
|
|
|
"is_deleted": self.is_deleted,
|
|
|
|
|
|
"deleted_at": format_utc_datetime(self.deleted_at),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChannelOutbox(Base):
|
|
|
|
|
|
"""出站消息重试队列 — 适配器发送失败后持久化,OutboxRetryWorker 按指数退避重试"""
|
|
|
|
|
|
|
|
|
|
|
|
__tablename__ = "channel_outbox"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
|
|
|
|
|
|
message_id = Column(String(64), nullable=False, index=True, comment="关联的原始消息 ID")
|
|
|
|
|
|
session_id = Column(String(64), nullable=False, index=True, comment="会话 ID (thread_id)")
|
|
|
|
|
|
channel_type = Column(String(20), nullable=False, index=True, comment="目标渠道类型")
|
|
|
|
|
|
content = Column(Text, nullable=False, comment="待发送内容 (标准 Markdown)")
|
|
|
|
|
|
status = Column(
|
|
|
|
|
|
String(20), nullable=False, default="pending", index=True,
|
|
|
|
|
|
comment="状态: pending/retrying/sent/failed/dead",
|
|
|
|
|
|
)
|
|
|
|
|
|
retry_count = Column(Integer, nullable=False, default=0, comment="已重试次数")
|
|
|
|
|
|
max_retries = Column(Integer, nullable=False, default=5, comment="最大重试次数")
|
|
|
|
|
|
next_retry_at = Column(DateTime, nullable=True, index=True, comment="下次重试时间")
|
|
|
|
|
|
last_error = Column(Text, nullable=True, comment="最后一次发送错误信息")
|
|
|
|
|
|
trace_id = Column(String(64), nullable=True, index=True, comment="链路追踪 ID")
|
|
|
|
|
|
extra_metadata = Column(JSON, nullable=True, comment="扩展元数据")
|
|
|
|
|
|
|
|
|
|
|
|
created_by = Column(String(64), nullable=True)
|
|
|
|
|
|
updated_by = Column(String(64), nullable=True)
|
|
|
|
|
|
created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间")
|
|
|
|
|
|
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间")
|
|
|
|
|
|
is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是")
|
|
|
|
|
|
deleted_at = Column(DateTime, nullable=True, comment="删除时间")
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": self.id,
|
|
|
|
|
|
"message_id": self.message_id,
|
|
|
|
|
|
"session_id": self.session_id,
|
|
|
|
|
|
"channel_type": self.channel_type,
|
|
|
|
|
|
"content": self.content,
|
|
|
|
|
|
"status": self.status,
|
|
|
|
|
|
"retry_count": self.retry_count,
|
|
|
|
|
|
"max_retries": self.max_retries,
|
|
|
|
|
|
"next_retry_at": format_utc_datetime(self.next_retry_at),
|
|
|
|
|
|
"last_error": self.last_error,
|
|
|
|
|
|
"trace_id": self.trace_id,
|
|
|
|
|
|
"extra_metadata": self.extra_metadata or {},
|
|
|
|
|
|
"created_by": self.created_by,
|
|
|
|
|
|
"updated_by": self.updated_by,
|
|
|
|
|
|
"created_at": format_utc_datetime(self.created_at),
|
|
|
|
|
|
"updated_at": format_utc_datetime(self.updated_at),
|
|
|
|
|
|
"is_deleted": self.is_deleted,
|
|
|
|
|
|
"deleted_at": format_utc_datetime(self.deleted_at),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChannelMessageLog(Base):
|
|
|
|
|
|
"""渠道消息审计日志 — 记录入站/出站消息处理轨迹,支持 trace_id 链路追踪"""
|
|
|
|
|
|
|
|
|
|
|
|
__tablename__ = "channel_message_logs"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
|
|
|
|
|
|
trace_id = Column(String(64), nullable=False, index=True, comment="链路追踪 ID")
|
|
|
|
|
|
message_id = Column(String(64), nullable=False, index=True, comment="消息 ID")
|
|
|
|
|
|
channel_type = Column(String(20), nullable=False, index=True, comment="渠道类型")
|
|
|
|
|
|
conversation_id = Column(Integer, nullable=True, index=True, comment="关联的 Conversation ID")
|
|
|
|
|
|
session_id = Column(String(64), nullable=True, index=True, comment="会话 ID")
|
|
|
|
|
|
direction = Column(String(10), nullable=False, index=True, comment="方向: inbound/outbound")
|
|
|
|
|
|
sender_id = Column(String(64), nullable=True, comment="发送者 ID")
|
|
|
|
|
|
content_summary = Column(String(500), nullable=True, comment="内容摘要(前 500 字符)")
|
|
|
|
|
|
agent_config_id = Column(Integer, nullable=True, index=True, comment="Agent 配置 ID")
|
|
|
|
|
|
status = Column(String(20), nullable=False, default="received", index=True, comment="处理状态")
|
|
|
|
|
|
pipeline_result = Column(String(20), nullable=True, comment="管道处理结果: accepted/aborted/skipped")
|
|
|
|
|
|
abort_reason = Column(String(64), nullable=True, comment="管道中止原因码")
|
|
|
|
|
|
worker_result = Column(String(20), nullable=True, comment="Worker 处理结果")
|
|
|
|
|
|
error_message = Column(Text, nullable=True, comment="错误信息")
|
|
|
|
|
|
processing_time_ms = Column(Integer, nullable=True, comment="总处理耗时(毫秒)")
|
|
|
|
|
|
extra_metadata = Column(JSON, nullable=True, comment="扩展元数据")
|
|
|
|
|
|
|
|
|
|
|
|
created_by = Column(String(64), nullable=True)
|
|
|
|
|
|
updated_by = Column(String(64), nullable=True)
|
|
|
|
|
|
created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间")
|
|
|
|
|
|
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间")
|
|
|
|
|
|
is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是")
|
|
|
|
|
|
deleted_at = Column(DateTime, nullable=True, comment="删除时间")
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": self.id,
|
|
|
|
|
|
"trace_id": self.trace_id,
|
|
|
|
|
|
"message_id": self.message_id,
|
|
|
|
|
|
"channel_type": self.channel_type,
|
|
|
|
|
|
"conversation_id": self.conversation_id,
|
|
|
|
|
|
"session_id": self.session_id,
|
|
|
|
|
|
"direction": self.direction,
|
|
|
|
|
|
"sender_id": self.sender_id,
|
|
|
|
|
|
"content_summary": self.content_summary,
|
|
|
|
|
|
"agent_config_id": self.agent_config_id,
|
|
|
|
|
|
"status": self.status,
|
|
|
|
|
|
"pipeline_result": self.pipeline_result,
|
|
|
|
|
|
"abort_reason": self.abort_reason,
|
|
|
|
|
|
"worker_result": self.worker_result,
|
|
|
|
|
|
"error_message": self.error_message,
|
|
|
|
|
|
"processing_time_ms": self.processing_time_ms,
|
|
|
|
|
|
"extra_metadata": self.extra_metadata or {},
|
|
|
|
|
|
"created_by": self.created_by,
|
|
|
|
|
|
"updated_by": self.updated_by,
|
|
|
|
|
|
"created_at": format_utc_datetime(self.created_at),
|
|
|
|
|
|
"updated_at": format_utc_datetime(self.updated_at),
|
|
|
|
|
|
"is_deleted": self.is_deleted,
|
|
|
|
|
|
"deleted_at": format_utc_datetime(self.deleted_at),
|
|
|
|
|
|
}
|
2026-05-31 16:44:13 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChannelPlugin(Base):
|
|
|
|
|
|
"""渠道插件注册表 — 存储插件元数据、状态与配置
|
|
|
|
|
|
|
|
|
|
|
|
对应领域模型:PluginRecord 实体
|
|
|
|
|
|
覆盖来源:workspace(本地开发) + installed(第三方安装)
|
|
|
|
|
|
bundled(内置) 插件不入库,由代码自动注册
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
__tablename__ = "channel_plugins"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
|
|
|
|
|
|
plugin_id = Column(String(64), nullable=False, unique=True, index=True, comment="插件唯一标识")
|
|
|
|
|
|
name = Column(String(128), nullable=False, comment="插件显示名称")
|
|
|
|
|
|
version = Column(String(32), nullable=False, default="0.0.0", comment="插件版本号")
|
|
|
|
|
|
channel_type = Column(String(64), nullable=False, index=True, comment="渠道类型标识")
|
|
|
|
|
|
origin = Column(String(32), nullable=False, comment="来源: bundled/workspace/installed")
|
|
|
|
|
|
status = Column(String(32), nullable=False, default="discovered", comment="状态: discovered/registered/configured/active/error/disabled")
|
|
|
|
|
|
root_dir = Column(String(512), nullable=True, comment="插件根目录路径")
|
|
|
|
|
|
entry_file = Column(String(512), nullable=True, comment="入口文件路径")
|
|
|
|
|
|
manifest = Column(JSON, nullable=True, comment="完整插件清单(yuxi.plugin.yaml内容)")
|
|
|
|
|
|
capabilities = Column(JSON, nullable=True, comment="渠道能力声明")
|
|
|
|
|
|
config_schema = Column(JSON, nullable=True, comment="配置校验schema")
|
|
|
|
|
|
config = Column(JSON, nullable=True, default=dict, comment="当前生效配置")
|
|
|
|
|
|
error = Column(Text, nullable=True, comment="错误信息")
|
|
|
|
|
|
is_enabled = Column(Boolean, nullable=False, default=True, index=True, comment="是否启用")
|
|
|
|
|
|
registered_at = Column(DateTime, nullable=True, comment="注册时间")
|
|
|
|
|
|
activated_at = Column(DateTime, nullable=True, comment="最后激活时间")
|
|
|
|
|
|
|
|
|
|
|
|
created_by = Column(String(64), nullable=True)
|
|
|
|
|
|
updated_by = Column(String(64), nullable=True)
|
|
|
|
|
|
created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间")
|
|
|
|
|
|
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间")
|
|
|
|
|
|
is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是")
|
|
|
|
|
|
deleted_at = Column(DateTime, nullable=True, comment="删除时间")
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": self.id,
|
|
|
|
|
|
"plugin_id": self.plugin_id,
|
|
|
|
|
|
"name": self.name,
|
|
|
|
|
|
"version": self.version,
|
|
|
|
|
|
"channel_type": self.channel_type,
|
|
|
|
|
|
"origin": self.origin,
|
|
|
|
|
|
"status": self.status,
|
|
|
|
|
|
"root_dir": self.root_dir,
|
|
|
|
|
|
"entry_file": self.entry_file,
|
|
|
|
|
|
"manifest": self.manifest or {},
|
|
|
|
|
|
"capabilities": self.capabilities or {},
|
|
|
|
|
|
"config_schema": self.config_schema or {},
|
|
|
|
|
|
"config": self.config or {},
|
|
|
|
|
|
"error": self.error,
|
|
|
|
|
|
"is_enabled": bool(self.is_enabled),
|
|
|
|
|
|
"registered_at": format_utc_datetime(self.registered_at),
|
|
|
|
|
|
"activated_at": format_utc_datetime(self.activated_at),
|
|
|
|
|
|
"created_by": self.created_by,
|
|
|
|
|
|
"updated_by": self.updated_by,
|
|
|
|
|
|
"created_at": format_utc_datetime(self.created_at),
|
|
|
|
|
|
"updated_at": format_utc_datetime(self.updated_at),
|
|
|
|
|
|
"is_deleted": self.is_deleted,
|
|
|
|
|
|
"deleted_at": format_utc_datetime(self.deleted_at),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChannelPluginConfigHistory(Base):
|
|
|
|
|
|
"""插件配置历史表 — 记录配置变更,支持回滚
|
|
|
|
|
|
|
|
|
|
|
|
对应领域事件:PluginConfiguredEvent
|
|
|
|
|
|
生命周期:每次调用 set_config() 时写入一条记录
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
__tablename__ = "channel_plugin_config_history"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
|
|
|
|
|
|
plugin_id = Column(String(64), nullable=False, index=True, comment="插件标识")
|
|
|
|
|
|
config = Column(JSON, nullable=False, comment="配置快照")
|
|
|
|
|
|
changed_by = Column(String(128), nullable=True, comment="变更人")
|
|
|
|
|
|
change_reason = Column(String(255), nullable=True, comment="变更原因")
|
|
|
|
|
|
|
|
|
|
|
|
created_by = Column(String(64), nullable=True)
|
|
|
|
|
|
updated_by = Column(String(64), nullable=True)
|
|
|
|
|
|
created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间")
|
|
|
|
|
|
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间")
|
|
|
|
|
|
is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是")
|
|
|
|
|
|
deleted_at = Column(DateTime, nullable=True, comment="删除时间")
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": self.id,
|
|
|
|
|
|
"plugin_id": self.plugin_id,
|
|
|
|
|
|
"config": self.config or {},
|
|
|
|
|
|
"changed_by": self.changed_by,
|
|
|
|
|
|
"change_reason": self.change_reason,
|
|
|
|
|
|
"created_by": self.created_by,
|
|
|
|
|
|
"updated_by": self.updated_by,
|
|
|
|
|
|
"created_at": format_utc_datetime(self.created_at),
|
|
|
|
|
|
"updated_at": format_utc_datetime(self.updated_at),
|
|
|
|
|
|
"is_deleted": self.is_deleted,
|
|
|
|
|
|
"deleted_at": format_utc_datetime(self.deleted_at),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChannelPluginAuditLog(Base):
|
|
|
|
|
|
"""插件操作审计日志 — 记录插件生命周期关键操作
|
|
|
|
|
|
|
|
|
|
|
|
对应领域事件:PluginRegisteredEvent / PluginActivatedEvent /
|
|
|
|
|
|
PluginDeactivatedEvent / PluginReloadEvent
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
__tablename__ = "channel_plugin_audit_logs"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
|
|
|
|
|
|
plugin_id = Column(String(64), nullable=False, index=True, comment="插件标识")
|
|
|
|
|
|
action = Column(String(32), nullable=False, comment="操作: register/activate/deactivate/reload/error")
|
|
|
|
|
|
old_status = Column(String(32), nullable=True, comment="操作前状态")
|
|
|
|
|
|
new_status = Column(String(32), nullable=True, comment="操作后状态")
|
|
|
|
|
|
old_version = Column(String(32), nullable=True, comment="操作前版本(重载场景)")
|
|
|
|
|
|
new_version = Column(String(32), nullable=True, comment="操作后版本(重载场景)")
|
|
|
|
|
|
detail = Column(JSON, nullable=True, comment="操作详情")
|
|
|
|
|
|
error_message = Column(Text, nullable=True, comment="错误信息")
|
|
|
|
|
|
|
|
|
|
|
|
created_by = Column(String(64), nullable=True)
|
|
|
|
|
|
updated_by = Column(String(64), nullable=True)
|
|
|
|
|
|
created_at = Column(DateTime, nullable=False, default=utc_now_naive, comment="创建时间")
|
|
|
|
|
|
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive, comment="更新时间")
|
|
|
|
|
|
is_deleted = Column(Integer, nullable=False, default=0, index=True, comment="软删除标记: 0=否, 1=是")
|
|
|
|
|
|
deleted_at = Column(DateTime, nullable=True, comment="删除时间")
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
return {
|
|
|
|
|
|
"id": self.id,
|
|
|
|
|
|
"plugin_id": self.plugin_id,
|
|
|
|
|
|
"action": self.action,
|
|
|
|
|
|
"old_status": self.old_status,
|
|
|
|
|
|
"new_status": self.new_status,
|
|
|
|
|
|
"old_version": self.old_version,
|
|
|
|
|
|
"new_version": self.new_version,
|
|
|
|
|
|
"detail": self.detail or {},
|
|
|
|
|
|
"error_message": self.error_message,
|
|
|
|
|
|
"created_by": self.created_by,
|
|
|
|
|
|
"updated_by": self.updated_by,
|
|
|
|
|
|
"created_at": format_utc_datetime(self.created_at),
|
|
|
|
|
|
"updated_at": format_utc_datetime(self.updated_at),
|
|
|
|
|
|
"is_deleted": self.is_deleted,
|
|
|
|
|
|
"deleted_at": format_utc_datetime(self.deleted_at),
|
|
|
|
|
|
}
|