279 lines
11 KiB
Python
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),
|
|
}
|