ForcePilot/backend/package/yuxi/storage/postgres/model_channel.py
Kris bab30f2715
Some checks failed
Deploy VitePress site to Pages / build (push) Has been cancelled
Ruff Format Check / Ruff Format & Lint (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
feat:0715
2026-07-15 12:30:58 +08:00

279 lines
11 KiB
Python

"""PostgreSQL 渠道相关数据模型"""
from typing import Any
from sqlalchemy import (
JSON,
Boolean,
Column,
DateTime,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
func,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.ext.declarative import declarative_base
from yuxi.channel.transport.qr_login import LoginState
from yuxi.utils.datetime_utils import format_utc_datetime, utc_now_naive
Base = declarative_base()
JSON_VALUE = JSON().with_variant(JSONB, "postgresql")
class ChannelConfig(Base):
"""渠道配置表"""
__tablename__ = "channel_configs"
__table_args__ = (
UniqueConstraint("channel_type", "account_id", name="uq_channel_configs_type_account"),
Index("ix_channel_configs_enabled", "enabled"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=func.gen_random_uuid())
channel_type = Column(String(32), nullable=False)
account_id = Column(String(128), nullable=False)
name = Column(String(100), nullable=True)
config_json = Column(JSON_VALUE, nullable=False, default=dict)
enabled = Column(Boolean, nullable=False, default=False)
created_by = Column(String(100), nullable=True)
updated_by = Column(String(100), nullable=True)
created_at = Column(DateTime(timezone=False), default=utc_now_naive)
updated_at = Column(DateTime(timezone=False), default=utc_now_naive, onupdate=utc_now_naive)
def to_dict(self) -> dict[str, Any]:
return {
"id": str(self.id) if self.id else None,
"channel_type": self.channel_type,
"account_id": self.account_id,
"name": self.name,
"config_json": self.config_json or {},
"enabled": bool(self.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),
}
class ChannelSession(Base):
"""渠道会话映射表"""
__tablename__ = "channel_sessions"
__table_args__ = (
UniqueConstraint("session_key", name="uq_channel_sessions_session_key"),
UniqueConstraint("conversation_id", name="uq_channel_sessions_conversation"),
Index("ix_channel_sessions_key", "session_key"),
Index("ix_channel_sessions_type_account", "channel_type", "account_id"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=func.gen_random_uuid())
session_key = Column(String(512), nullable=False)
channel_type = Column(String(32), nullable=False)
account_id = Column(String(128), nullable=False)
chat_type = Column(String(32), nullable=True)
channel_sender_id = Column(String(255), nullable=True)
conversation_id = Column(Integer, ForeignKey("conversations.id", ondelete="CASCADE"), nullable=False)
channel_metadata = Column(JSON_VALUE, nullable=True)
created_at = Column(DateTime(timezone=False), default=utc_now_naive)
updated_at = Column(DateTime(timezone=False), default=utc_now_naive, onupdate=utc_now_naive)
def to_dict(self) -> dict[str, Any]:
return {
"id": str(self.id) if self.id else None,
"session_key": self.session_key,
"channel_type": self.channel_type,
"account_id": self.account_id,
"chat_type": self.chat_type,
"channel_sender_id": self.channel_sender_id,
"conversation_id": self.conversation_id,
"channel_metadata": self.channel_metadata or {},
"created_at": format_utc_datetime(self.created_at),
"updated_at": format_utc_datetime(self.updated_at),
}
def update_channel_metadata(self, updates: dict[str, Any]) -> None:
"""合并更新 channel_metadata 并标记字段已修改。"""
from sqlalchemy.orm.attributes import flag_modified
merged = dict(self.channel_metadata or {})
merged.update(updates)
self.channel_metadata = merged
flag_modified(self, "channel_metadata")
class ChannelBinding(Base):
"""运行时绑定记录"""
__tablename__ = "channel_bindings"
__table_args__ = (
UniqueConstraint(
"channel_type",
"account_id",
"binding_rule_hash",
"session_key_pattern",
name="uq_channel_bindings_rule",
),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=func.gen_random_uuid())
channel_type = Column(String(32), nullable=False)
account_id = Column(String(128), nullable=False)
binding_rule_hash = Column(String(64), nullable=False)
agent_id = Column(String(80), nullable=False)
session_key_pattern = Column(String(512), nullable=False, default="")
match = Column(JSON_VALUE, nullable=True)
session_override = Column(JSON_VALUE, nullable=True)
created_by = Column(String(100), nullable=True)
created_at = Column(DateTime(timezone=False), default=utc_now_naive)
updated_at = Column(DateTime(timezone=False), default=utc_now_naive, onupdate=utc_now_naive)
def to_dict(self) -> dict[str, Any]:
return {
"id": str(self.id) if self.id else None,
"channel_type": self.channel_type,
"account_id": self.account_id,
"binding_rule_hash": self.binding_rule_hash,
"agent_id": self.agent_id,
"session_key_pattern": self.session_key_pattern,
"match": self.match or {},
"session_override": self.session_override or {},
"created_by": self.created_by,
"created_at": format_utc_datetime(self.created_at),
"updated_at": format_utc_datetime(self.updated_at),
}
class ChannelPairingRecord(Base):
"""配对码记录表"""
__tablename__ = "channel_pairing_records"
__table_args__ = (
Index(
"ix_channel_pairing_records_lookup",
"channel_type",
"account_id",
"peer_id",
"status",
),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=func.gen_random_uuid())
channel_type = Column(String(32), nullable=False)
account_id = Column(String(128), nullable=False)
peer_id = Column(String(255), nullable=False)
pairing_code = Column(String(6), nullable=False)
pairing_token = Column(String(64), nullable=False)
status = Column(String(20), nullable=False, default="pending")
expires_at = Column(DateTime(timezone=False), nullable=False)
paired_at = Column(DateTime(timezone=False), nullable=True)
platform_user_id = Column(String(255), nullable=True)
qr_content = Column(String(2048), nullable=True)
pairing_mode = Column(String(32), nullable=False, default="code")
created_at = Column(DateTime(timezone=False), default=utc_now_naive)
def to_dict(self) -> dict[str, Any]:
return {
"id": str(self.id) if self.id else None,
"channel_type": self.channel_type,
"account_id": self.account_id,
"peer_id": self.peer_id,
"pairing_code": self.pairing_code,
"pairing_token": self.pairing_token,
"status": self.status,
"expires_at": format_utc_datetime(self.expires_at),
"paired_at": format_utc_datetime(self.paired_at),
"platform_user_id": self.platform_user_id,
"qr_content": self.qr_content,
"pairing_mode": self.pairing_mode,
"created_at": format_utc_datetime(self.created_at),
}
class ChannelIdentityLink(Base):
"""渠道用户身份与平台用户身份的绑定关系"""
__tablename__ = "channel_identity_links"
__table_args__ = (
UniqueConstraint(
"channel_type",
"account_id",
"channel_sender_id",
name="uq_channel_identity_link",
),
Index("ix_channel_identity_links_user", "platform_user_id"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=func.gen_random_uuid())
channel_type = Column(String(32), nullable=False)
account_id = Column(String(128), nullable=False)
channel_sender_id = Column(String(255), nullable=False)
platform_user_id = Column(String(255), nullable=False)
paired_by = Column(String(32), nullable=False, default="code")
created_at = Column(DateTime(timezone=False), default=utc_now_naive)
updated_at = Column(DateTime(timezone=False), default=utc_now_naive, onupdate=utc_now_naive)
def to_dict(self) -> dict[str, Any]:
return {
"id": str(self.id) if self.id else None,
"channel_type": self.channel_type,
"account_id": self.account_id,
"channel_sender_id": self.channel_sender_id,
"platform_user_id": self.platform_user_id,
"paired_by": self.paired_by,
"created_at": format_utc_datetime(self.created_at),
"updated_at": format_utc_datetime(self.updated_at),
}
class ChannelLoginRecord(Base):
"""渠道二维码登录记录表"""
__tablename__ = "channel_login_records"
__table_args__ = (
UniqueConstraint("channel_type", "account_id", name="uq_channel_login_records_type_account"),
Index("ix_channel_login_records_status", "status"),
Index("ix_channel_login_records_ticket", "current_ticket"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=func.gen_random_uuid())
channel_type = Column(String(32), nullable=False)
account_id = Column(String(128), nullable=False)
status = Column(String(32), nullable=False, default=LoginState.PENDING.value)
current_ticket = Column(String(512), nullable=True)
qr_content = Column(String(2048), nullable=True)
qr_image_url = Column(String(2048), nullable=True)
qr_image_base64 = Column(Text, nullable=True)
credentials = Column(JSON_VALUE, nullable=True)
credential_expires_at = Column(DateTime(timezone=False), nullable=True)
expires_at = Column(DateTime(timezone=False), nullable=True)
scanned_at = Column(DateTime(timezone=False), nullable=True)
logged_in_at = Column(DateTime(timezone=False), nullable=True)
error_message = Column(String(1024), nullable=True)
created_at = Column(DateTime(timezone=False), default=utc_now_naive)
updated_at = Column(DateTime(timezone=False), default=utc_now_naive, onupdate=utc_now_naive)
def to_dict(self) -> dict[str, Any]:
return {
"id": str(self.id) if self.id else None,
"channel_type": self.channel_type,
"account_id": self.account_id,
"status": self.status,
"current_ticket": self.current_ticket,
"qr_content": self.qr_content,
"qr_image_url": self.qr_image_url,
"qr_image_base64": self.qr_image_base64,
"credentials": self.credentials or {},
"credential_expires_at": format_utc_datetime(self.credential_expires_at),
"expires_at": format_utc_datetime(self.expires_at),
"scanned_at": format_utc_datetime(self.scanned_at),
"logged_in_at": format_utc_datetime(self.logged_in_at),
"error_message": self.error_message,
"created_at": format_utc_datetime(self.created_at),
"updated_at": format_utc_datetime(self.updated_at),
}