diff --git a/backend/package/yuxi/storage/postgres/models_channels.py b/backend/package/yuxi/storage/postgres/models_channels.py index adfda530..84d7888e 100644 --- a/backend/package/yuxi/storage/postgres/models_channels.py +++ b/backend/package/yuxi/storage/postgres/models_channels.py @@ -284,9 +284,10 @@ class ChannelUserIdentity(Base): default=dict, comment="渠道绑定映射:{channel_type: [peer_id, ...]}", ) - merged_from = Column(JSON_VALUE, nullable=False, default=list, comment="合并来源 identity_id 列表") + merged_from = Column(JSON_VALUE, nullable=False, default=list, comment="合并来源结构化证据列表") confidence = Column(String(16), nullable=False, default="low", comment="身份置信度:low/medium/high") version = Column(Integer, nullable=False, default=1, comment="乐观锁版本号") + pending_review = Column(Boolean, nullable=False, default=False, comment="低置信度合并待审批标记(H-21)") # 审计字段 created_by = Column(String(64), nullable=True) @@ -306,6 +307,19 @@ class ChannelUserIdentity(Base): unique=True, postgresql_where=text("is_deleted = 0"), ), + # GIN 索引:加速 channel_bindings / merged_from 的 @> 包含查询 + Index( + "ix_channel_user_identities_channel_bindings_gin", + "channel_bindings", + postgresql_using="gin", + postgresql_ops={"channel_bindings": "jsonb_path_ops"}, + ), + Index( + "ix_channel_user_identities_merged_from_gin", + "merged_from", + postgresql_using="gin", + postgresql_ops={"merged_from": "jsonb_path_ops"}, + ), ) def to_dict(self) -> dict[str, Any]: @@ -323,6 +337,7 @@ class ChannelUserIdentity(Base): "merged_from": self.merged_from or [], "confidence": self.confidence, "version": self.version, + "pending_review": self.pending_review, "created_by": self.created_by, "updated_by": self.updated_by, "created_at": format_utc_datetime(self.created_at), @@ -481,6 +496,9 @@ class ChannelAuditLog(Base): Index("ix_channel_audit_logs_operation_time", "operation", "timestamp"), Index("ix_channel_audit_logs_operator_time", "operator", "timestamp"), Index("ix_channel_audit_logs_target_time", "target_channel", "timestamp"), + # 高频过滤列索引(H-8):target_account 为审计日志查询的高频过滤条件, + # 无索引会触发全表扫描。 + Index("ix_channel_audit_logs_target_account", "target_account"), ) def to_dict(self) -> dict[str, Any]: @@ -614,6 +632,13 @@ class ChannelOutboxEntry(Base): nullable=True, comment="降级原因摘要(BEST_EFFORT 持久化失败、markDeliveryUnconfirmedFailed 等)", ) + delivered_parts = Column( + JSONB, + nullable=False, + default=list, + server_default="[]", + comment="多分片投递已成功投递的分片序号列表,重试时仅发送未投递分片(H-15)", + ) # 过期与追踪 expires_at = Column(DateTime, nullable=False, comment="过期时间(默认创建时间 + 24h)") @@ -635,6 +660,10 @@ class ChannelOutboxEntry(Base): Index("ix_channel_outbox_locked_at", "locked_at"), Index("ix_channel_outbox_latency_ms", "latency_ms"), Index("ix_channel_outbox_funnel_node", "funnel_node"), + # 外键列索引(H-8):_applyOutboxQueryFilter 按 channel_session_id 过滤, + # conversation_id 为高频 JOIN/级联列,无索引会触发全表扫描。 + Index("ix_channel_outbox_channel_session", "channel_session_id"), + Index("ix_channel_outbox_conversation", "conversation_id"), ) def to_dict(self) -> dict[str, Any]: @@ -666,6 +695,7 @@ class ChannelOutboxEntry(Base): "partial_failure": self.partial_failure, "stream_aborted_at_chunk": self.stream_aborted_at_chunk, "degraded_reason": self.degraded_reason, + "delivered_parts": self.delivered_parts, "expires_at": format_utc_datetime(self.expires_at), "trace_id": self.trace_id, "version": self.version, diff --git a/backend/package/yuxi/storage/postgres/models_external.py b/backend/package/yuxi/storage/postgres/models_external.py index dcbf8ca2..a156f45f 100644 --- a/backend/package/yuxi/storage/postgres/models_external.py +++ b/backend/package/yuxi/storage/postgres/models_external.py @@ -744,7 +744,7 @@ class ExternalSystemHealthCheck(Base): detail = Column(Text, nullable=True, comment="探测结果说明") error_code = Column(String(64), nullable=True, comment="错误码") error_message = Column(Text, nullable=True, comment="错误信息") - checked_at = Column(DateTime, nullable=True, index=True, comment="探测时间") + checked_at = Column(DateTime, nullable=True, comment="探测时间") # 审计字段 created_by = Column(String(64), nullable=True) @@ -1023,7 +1023,7 @@ class ExternalSystemQuotaUsage(Base): quota_name = Column(String(128), nullable=False, comment="配额展示名称") quota_window = Column(String(32), nullable=False, comment="窗口类型:daily/hourly/rolling_24h/minute/custom") window_start = Column(DateTime, nullable=True, comment="窗口起始时间") - window_end = Column(DateTime, nullable=True, index=True, comment="窗口结束时间(重置时间)") + window_end = Column(DateTime, nullable=True, comment="窗口结束时间(重置时间)") # 配额数值 limit_value = Column(BigInteger, nullable=False, default=0, comment="配额上限") @@ -1440,7 +1440,7 @@ class ExternalToolTestCase(Base): # 上次运行结果 last_run_at = Column(DateTime, nullable=True, comment="上次运行时间") - last_run_status = Column(String(32), nullable=True, index=True, comment="上次运行结果:passed/failed/error/skipped") + last_run_status = Column(String(32), nullable=True, comment="上次运行结果:passed/failed/error/skipped") last_run_duration_ms = Column(Integer, nullable=True, comment="上次运行耗时毫秒") last_run_response = Column(JSON_VALUE, nullable=True, comment="上次运行响应(已脱敏)") last_run_error = Column(Text, nullable=True, comment="上次运行错误信息")