1. 新增独立的渠道账户模型文件,重构基类导入逻辑 2. 在Postgres管理器中集成渠道表元数据,新增渠道Schema初始化与升级方法 3. 为用户表、会话表、消息表新增渠道关联扩展字段 4. 格式化优化现有模型文件的代码排版与注释
1551 lines
68 KiB
Python
1551 lines
68 KiB
Python
"""外部系统集成相关的 PostgreSQL 数据模型。
|
||
|
||
表名统一以 `ext_` 为前缀;字段类型复用 `yuxi.storage.postgres.base` 中的
|
||
`Base` 与 `JSON_VALUE`。所有表统一包含审计字段(created_by/updated_by/
|
||
created_at/updated_at/is_deleted/deleted_at),支持软删除与操作追溯。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import base64
|
||
from typing import Any
|
||
|
||
from sqlalchemy import (
|
||
BigInteger,
|
||
Boolean,
|
||
Column,
|
||
DateTime,
|
||
ForeignKey,
|
||
Index,
|
||
Integer,
|
||
LargeBinary,
|
||
String,
|
||
Text,
|
||
UniqueConstraint,
|
||
func,
|
||
select,
|
||
)
|
||
from sqlalchemy.dialects.postgresql import JSON, JSONB
|
||
from sqlalchemy.orm import column_property
|
||
from yuxi.storage.postgres.models_business import Base
|
||
from yuxi.utils.datetime_utils import format_utc_datetime, utc_now_naive
|
||
|
||
JSON_VALUE = JSON().with_variant(JSONB, "postgresql")
|
||
|
||
|
||
class ExternalToolConfig(Base):
|
||
"""外部系统工具配置(通用模型,HTTP 为首个适配器实现)。"""
|
||
|
||
__tablename__ = "ext_tool_configs"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 基础信息
|
||
slug = Column(String(128), nullable=False, unique=True, index=True, comment="工具唯一标识")
|
||
name = Column(String(128), nullable=False, comment="展示名称")
|
||
description = Column(Text, nullable=False, comment="工具描述(给 LLM 看)")
|
||
category = Column(String(64), nullable=False, default="default", comment="展示分类")
|
||
adapter_type = Column(String(32), nullable=False, default="http", comment="适配器类型:http/grpc/...")
|
||
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
|
||
|
||
# 通用执行配置
|
||
timeout = Column(Integer, nullable=False, default=30, comment="超时时间(秒)")
|
||
retry_policy = Column(JSON_VALUE, nullable=False, default=dict, comment="重试策略")
|
||
auth_type = Column(String(32), nullable=False, default="none", comment="认证类型")
|
||
auth_config = Column(JSON_VALUE, nullable=True, comment="认证配置(敏感字段加密存储)")
|
||
|
||
# 适配器专属配置(首期由 HTTP 适配器使用)
|
||
adapter_config = Column(JSON_VALUE, nullable=False, default=dict, comment="适配器专属配置")
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
index=True,
|
||
comment="所属系统 ID(为空时自包含运行)",
|
||
)
|
||
|
||
# 审计字段
|
||
created_by = Column(String(64), nullable=True)
|
||
updated_by = Column(String(64), nullable=True)
|
||
created_at = Column(DateTime, nullable=False, default=utc_now_naive)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index("ix_ext_tool_configs_system_id_enabled", "system_id", "enabled"),
|
||
Index("ix_ext_tool_configs_adapter_type_enabled", "adapter_type", "enabled"),
|
||
Index("ix_ext_tool_configs_category_enabled", "category", "enabled"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为字典;敏感字段的加解密由 persistence 层承接。"""
|
||
return {
|
||
"id": self.id,
|
||
"slug": self.slug,
|
||
"name": self.name,
|
||
"description": self.description,
|
||
"category": self.category,
|
||
"adapter_type": self.adapter_type,
|
||
"enabled": self.enabled,
|
||
"timeout": self.timeout,
|
||
"retry_policy": self.retry_policy or {},
|
||
"auth_type": self.auth_type,
|
||
"auth_config": self.auth_config or {},
|
||
"adapter_config": self.adapter_config or {},
|
||
"system_id": self.system_id,
|
||
"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 ExternalToolConfigVersion(Base):
|
||
"""外部工具配置版本历史;每次保存时记录完整快照。"""
|
||
|
||
__tablename__ = "ext_tool_config_versions"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
tool_slug = Column(
|
||
String(128),
|
||
ForeignKey("ext_tool_configs.slug", ondelete="CASCADE", onupdate="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="工具 slug",
|
||
)
|
||
|
||
# 版本信息
|
||
version_id = Column(String(64), nullable=False, comment="展示版本号,如 2026-06-15-001")
|
||
snapshot = Column(JSON_VALUE, nullable=False, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (UniqueConstraint("tool_slug", "version_id", name="uq_ext_tool_config_versions_tool_version"),)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典;snapshot 中的敏感字段保持加密状态,由服务层按需解密。"""
|
||
return {
|
||
"id": self.id,
|
||
"tool_slug": self.tool_slug,
|
||
"version_id": self.version_id,
|
||
"snapshot": self.snapshot 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),
|
||
}
|
||
|
||
|
||
class ExternalAdapterAsset(Base):
|
||
"""外部适配器资产(如 proto/reflection/wsdl/openapi 等)。"""
|
||
|
||
__tablename__ = "ext_adapter_assets"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 资产基础信息
|
||
adapter_type = Column(String(32), nullable=False, index=True, comment="适配器类型:http/grpc/soap/...")
|
||
asset_type = Column(String(32), nullable=False, comment="资产类型:proto/reflection/wsdl/openapi/...")
|
||
name = Column(String(256), nullable=False, comment="资产名称(通常取自上传文件名)")
|
||
content = Column(LargeBinary, nullable=False, comment="资产原始内容(二进制)")
|
||
size = Column(Integer, default=0, comment="内容字节数")
|
||
checksum = Column(String(64), nullable=True, comment="内容 SHA-256 校验和")
|
||
|
||
# 状态信息
|
||
status = Column(
|
||
String(32),
|
||
nullable=False,
|
||
default="active",
|
||
index=True,
|
||
comment="资产状态:active/parse_failed/inactive",
|
||
)
|
||
status_message = Column(String(512), 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, index=True)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为字典;content 以 base64 编码返回,便于 JSON 传输。"""
|
||
content_b64 = base64.b64encode(self.content).decode("utf-8") if self.content else ""
|
||
return {
|
||
"id": self.id,
|
||
"adapter_type": self.adapter_type,
|
||
"asset_type": self.asset_type,
|
||
"name": self.name,
|
||
"content": content_b64,
|
||
"size": self.size,
|
||
"checksum": self.checksum,
|
||
"status": self.status,
|
||
"status_message": self.status_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),
|
||
}
|
||
|
||
def to_list_dict(self) -> dict[str, Any]:
|
||
"""序列化为列表项字典;不包含 content 字段,避免大体积响应。"""
|
||
return {
|
||
"id": self.id,
|
||
"adapter_type": self.adapter_type,
|
||
"asset_type": self.asset_type,
|
||
"name": self.name,
|
||
"size": self.size,
|
||
"checksum": self.checksum,
|
||
"status": self.status,
|
||
"status_message": self.status_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),
|
||
}
|
||
|
||
|
||
class ExternalSystem(Base):
|
||
"""外部系统一级实体。"""
|
||
|
||
__tablename__ = "ext_systems"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 基础信息
|
||
slug = Column(String(128), nullable=False, unique=True, index=True, comment="系统唯一标识")
|
||
name = Column(String(128), nullable=False, comment="展示名称")
|
||
description = Column(Text, nullable=False, comment="系统描述")
|
||
category = Column(String(64), nullable=False, default="default", comment="展示分类")
|
||
adapter_type = Column(String(32), nullable=False, comment="适配器类型")
|
||
source_type = Column(String(64), nullable=True, comment="渠道 source_type,用于统一操作分派")
|
||
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
|
||
|
||
# 连接与认证配置
|
||
connection_config = Column(JSON_VALUE, nullable=False, default=dict, comment="系统级连接配置")
|
||
auth_type = Column(String(32), nullable=False, default="none", comment="认证类型")
|
||
auth_config = Column(JSON_VALUE, nullable=True, comment="认证配置(敏感字段加密存储)")
|
||
secret_refs = Column(JSON_VALUE, nullable=False, default=dict, comment="具名 secret 引用")
|
||
|
||
# 运维治理配置
|
||
rate_limit = Column(JSON_VALUE, nullable=False, default=dict, comment="限流配置")
|
||
circuit_breaker = Column(JSON_VALUE, nullable=False, default=dict, comment="熔断配置")
|
||
pool_config = Column(JSON_VALUE, nullable=False, default=dict, comment="连接池配置")
|
||
observability = Column(JSON_VALUE, nullable=False, default=dict, comment="可观测性配置")
|
||
|
||
# 通用执行配置
|
||
timeout = Column(Integer, nullable=False, default=30, comment="超时时间(秒)")
|
||
retry_policy = Column(JSON_VALUE, nullable=False, default=dict, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
return {
|
||
"id": self.id,
|
||
"slug": self.slug,
|
||
"name": self.name,
|
||
"description": self.description,
|
||
"category": self.category,
|
||
"adapter_type": self.adapter_type,
|
||
"source_type": self.source_type,
|
||
"enabled": self.enabled,
|
||
"connection_config": self.connection_config or {},
|
||
"auth_type": self.auth_type,
|
||
"auth_config": self.auth_config or {},
|
||
"secret_refs": self.secret_refs or {},
|
||
"rate_limit": self.rate_limit or {},
|
||
"circuit_breaker": self.circuit_breaker or {},
|
||
"pool_config": self.pool_config or {},
|
||
"observability": self.observability or {},
|
||
"timeout": self.timeout,
|
||
"retry_policy": self.retry_policy or {},
|
||
"env_count": self.env_count or 0,
|
||
"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 ExternalSystemEnvironment(Base):
|
||
"""外部系统环境配置(dev / staging / prod)。"""
|
||
|
||
__tablename__ = "ext_system_environments"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="所属系统 ID",
|
||
)
|
||
|
||
# 环境基础信息
|
||
env_key = Column(String(32), nullable=False, comment="环境标识")
|
||
name = Column(String(128), nullable=False, comment="环境名称")
|
||
is_default = Column(Boolean, nullable=False, default=False, comment="是否默认环境")
|
||
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
|
||
|
||
# 环境级配置
|
||
connection_config = Column(JSON_VALUE, nullable=False, default=dict, comment="环境级连接配置")
|
||
auth_config = Column(JSON_VALUE, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
UniqueConstraint("system_id", "env_key", name="uq_ext_system_environments_system_env"),
|
||
# 每个外部系统只能有一个默认环境
|
||
Index(
|
||
"uq_ext_system_environments_default",
|
||
"system_id",
|
||
unique=True,
|
||
postgresql_where=is_default.is_(True),
|
||
),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
return {
|
||
"id": self.id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"name": self.name,
|
||
"connection_config": self.connection_config or {},
|
||
"auth_config": self.auth_config or {},
|
||
"is_default": self.is_default,
|
||
"enabled": 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),
|
||
}
|
||
|
||
|
||
# 外部系统环境数:使用关联子查询,避免列表查询时出现 N+1 问题
|
||
ExternalSystem.env_count = column_property(
|
||
select(func.count(ExternalSystemEnvironment.id))
|
||
.where(ExternalSystemEnvironment.system_id == ExternalSystem.id)
|
||
.correlate(ExternalSystem)
|
||
.scalar_subquery()
|
||
)
|
||
|
||
|
||
class ExternalSystemToken(Base):
|
||
"""外部系统 Token 持久化。"""
|
||
|
||
__tablename__ = "ext_system_tokens"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
comment="所属系统 ID",
|
||
)
|
||
|
||
# Token 基础信息
|
||
env_key = Column(String(32), nullable=False, default="default", comment="环境标识")
|
||
token_type = Column(String(32), nullable=False, comment="Token 类型")
|
||
access_token = Column(Text, nullable=False, comment="Access Token(加密存储)")
|
||
refresh_token = Column(Text, nullable=True, comment="Refresh Token(加密存储)")
|
||
expires_at = Column(DateTime, nullable=True, comment="过期时间")
|
||
extra = Column(JSON_VALUE, nullable=False, default=dict, comment="额外信息")
|
||
|
||
# 状态信息
|
||
is_invalidated = Column(Boolean, nullable=False, default=False, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (UniqueConstraint("system_id", "env_key", name="uq_ext_system_tokens_system_env"),)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为字典;不返回 Token 明文,仅返回是否存在。
|
||
|
||
Token 值加密存储于 ``access_token`` / ``refresh_token``,模型层
|
||
``to_dict`` 不直接暴露密文或明文;需要明文的场景由服务层
|
||
(如 ``token_service.get_token_detail``)按权限解密后返回。
|
||
"""
|
||
return {
|
||
"id": self.id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"token_type": self.token_type,
|
||
"has_access_token": bool(self.access_token),
|
||
"has_refresh_token": bool(self.refresh_token),
|
||
"expires_at": format_utc_datetime(self.expires_at) if self.expires_at else None,
|
||
"extra": self.extra or {},
|
||
"is_invalidated": self.is_invalidated,
|
||
"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 ExternalSystemMetric(Base):
|
||
"""外部系统调用指标(按时间桶聚合)。"""
|
||
|
||
__tablename__ = "ext_system_metrics"
|
||
|
||
# 主键
|
||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
comment="所属系统 ID",
|
||
)
|
||
|
||
# 桶维度
|
||
env_key = Column(String(32), nullable=False, default="default", comment="环境标识")
|
||
bucket_at = Column(DateTime, nullable=False, comment="时间桶起始时间")
|
||
|
||
# 调用计数
|
||
total_calls = Column(Integer, nullable=False, default=0)
|
||
success_calls = Column(Integer, nullable=False, default=0)
|
||
failed_calls = Column(Integer, nullable=False, default=0)
|
||
auth_failed_calls = Column(Integer, nullable=False, default=0)
|
||
ssrf_blocked_calls = Column(Integer, nullable=False, default=0)
|
||
timeout_calls = Column(Integer, nullable=False, default=0)
|
||
|
||
# 延迟统计
|
||
p99_latency_ms = Column(Integer, nullable=True, comment="当前桶的近似峰值延迟")
|
||
latency_sum_ms = Column(
|
||
BigInteger, nullable=False, default=0, comment="当前桶调用延迟总和(毫秒),用于原子累加计算真实平均值"
|
||
)
|
||
latency_count = Column(Integer, nullable=False, default=0, comment="当前桶参与统计的调用次数")
|
||
|
||
# Token 与限流统计
|
||
token_refresh_count = Column(Integer, nullable=False, default=0)
|
||
token_refresh_failed = Column(Integer, nullable=False, default=0)
|
||
throttled_count = Column(Integer, nullable=False, default=0)
|
||
circuit_open_count = Column(Integer, nullable=False, default=0)
|
||
|
||
# 审计字段
|
||
created_by = Column(String(64), nullable=True)
|
||
updated_by = Column(String(64), nullable=True)
|
||
created_at = Column(DateTime, nullable=False, default=utc_now_naive)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (UniqueConstraint("system_id", "env_key", "bucket_at", name="uq_ext_system_metrics_bucket"),)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典;avg_latency_ms 由 latency_sum_ms/latency_count 计算,避免存储不一致。"""
|
||
latency_count = self.latency_count or 0
|
||
latency_sum_ms = self.latency_sum_ms or 0
|
||
avg_latency_ms = round(latency_sum_ms / latency_count, 2) if latency_count > 0 else 0.0
|
||
return {
|
||
"id": self.id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"bucket_at": format_utc_datetime(self.bucket_at),
|
||
"total_calls": self.total_calls,
|
||
"success_calls": self.success_calls,
|
||
"failed_calls": self.failed_calls,
|
||
"auth_failed_calls": self.auth_failed_calls,
|
||
"ssrf_blocked_calls": self.ssrf_blocked_calls,
|
||
"timeout_calls": self.timeout_calls,
|
||
"p99_latency_ms": self.p99_latency_ms,
|
||
"latency_sum_ms": latency_sum_ms,
|
||
"latency_count": latency_count,
|
||
"avg_latency_ms": avg_latency_ms,
|
||
"token_refresh_count": self.token_refresh_count,
|
||
"token_refresh_failed": self.token_refresh_failed,
|
||
"throttled_count": self.throttled_count,
|
||
"circuit_open_count": self.circuit_open_count,
|
||
"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 ExternalSystemAuditLog(Base):
|
||
"""外部系统审计日志。
|
||
|
||
记录系统级变更、Token 失效、缓存清理等运维事件;detail 中不应包含密钥、
|
||
Token、密码等敏感信息,由调用方在写入前完成脱敏。
|
||
"""
|
||
|
||
__tablename__ = "ext_system_audit_logs"
|
||
|
||
# 主键
|
||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
index=True,
|
||
comment="所属系统 ID",
|
||
)
|
||
|
||
# 事件基础信息
|
||
env_key = Column(String(32), nullable=True, comment="环境标识")
|
||
event_type = Column(String(64), nullable=False, index=True, comment="事件类型")
|
||
action_type = Column(
|
||
String(64),
|
||
nullable=True,
|
||
index=True,
|
||
comment="操作类型:CREATE/UPDATE/DELETE/ROTATE_SECRET/INVALIDATE_TOKEN/EXPORT/BULK_OPERATION/AUTH/EXECUTE/OTHER",
|
||
)
|
||
resource = Column(String(256), nullable=True, comment="操作对象,如系统:crm_prod")
|
||
user = Column(String(64), nullable=True, comment="操作人")
|
||
|
||
# 事件详情
|
||
detail = Column(JSON_VALUE, nullable=False, default=dict, comment="事件详情(已脱敏)")
|
||
snapshot_before = Column(JSON_VALUE, nullable=True, comment="变更前快照(已脱敏)")
|
||
snapshot_after = Column(JSON_VALUE, nullable=True, comment="变更后快照(已脱敏)")
|
||
description = 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, index=True)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index("ix_ext_system_audit_logs_system_id_created_at", "system_id", "created_at"),
|
||
Index("ix_ext_system_audit_logs_action_type_created_at", "action_type", "created_at"),
|
||
Index("ix_ext_system_audit_logs_user_created_at", "user", "created_at"),
|
||
Index("ix_ext_system_audit_logs_env_key_created_at", "env_key", "created_at"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。"""
|
||
return {
|
||
"id": self.id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"event_type": self.event_type,
|
||
"action_type": self.action_type,
|
||
"resource": self.resource,
|
||
"user": self.user,
|
||
"detail": self.detail or {},
|
||
"snapshot_before": self.snapshot_before,
|
||
"snapshot_after": self.snapshot_after,
|
||
"description": self.description,
|
||
"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 ExternalToolExecution(Base):
|
||
"""外部系统工具单次执行记录。"""
|
||
|
||
__tablename__ = "ext_tool_executions"
|
||
|
||
# 主键
|
||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||
|
||
# 执行标识
|
||
execution_id = Column(String(64), nullable=False, unique=True, index=True, comment="单次执行唯一标识")
|
||
trace_id = Column(String(64), nullable=False, index=True, comment="调用链路唯一标识")
|
||
correlation_id = Column(String(64), nullable=True, index=True, comment="业务关联 ID,如 Agent 运行 ID")
|
||
|
||
# 关联关系
|
||
system_id = Column(Integer, nullable=False, index=True, comment="所属系统 ID")
|
||
env_key = Column(String(32), nullable=False, default="default", comment="环境标识")
|
||
tool_slug = Column(String(128), nullable=False, index=True, comment="工具 slug")
|
||
|
||
# 调用方信息
|
||
caller = Column(String(32), nullable=False, comment="调用方:agent/user/workflow/simulate/sync_job/retry")
|
||
caller_id = Column(String(64), nullable=True, comment="调用方实体 ID")
|
||
|
||
# 执行状态与时间
|
||
status = Column(
|
||
String(32), nullable=False, index=True, comment="pending/running/success/failed/timeout/throttled/cancelled"
|
||
)
|
||
started_at = Column(DateTime, nullable=False, index=True, comment="开始时间")
|
||
ended_at = Column(DateTime, nullable=True, comment="结束时间")
|
||
duration_ms = Column(Integer, nullable=True, comment="耗时毫秒")
|
||
|
||
# 操作类型
|
||
operation = Column(String(32), nullable=False, default="execute", comment="execute/health_check/test/simulate")
|
||
|
||
# 请求/响应摘要(已脱敏)
|
||
request_summary = Column(JSON_VALUE, nullable=False, default=dict, comment="请求摘要(已脱敏)")
|
||
response_summary = Column(JSON_VALUE, nullable=True, comment="响应摘要(已脱敏)")
|
||
error = Column(JSON_VALUE, nullable=True, comment="错误信息")
|
||
|
||
# 重试与标签
|
||
retry_of = Column(String(64), nullable=True, index=True, comment="重试来源 execution_id")
|
||
retry_count = Column(Integer, nullable=False, default=0, comment="第几次重试")
|
||
tags = Column(JSON_VALUE, nullable=False, default=dict, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index("ix_ext_tool_executions_system_started", "system_id", "started_at"),
|
||
Index("ix_ext_tool_executions_tool_started", "tool_slug", "started_at"),
|
||
Index("ix_ext_tool_executions_status_started", "status", "started_at"),
|
||
)
|
||
|
||
def to_dict(self, *, tool_name: str | None = None) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。
|
||
|
||
为前端列表/详情展示提供与字段名语义一致的别名:
|
||
- execution_time -> started_at
|
||
- environment -> env_key
|
||
- tool_name 默认回退到 tool_slug,可由服务层传入真实工具名覆盖。
|
||
"""
|
||
return {
|
||
"execution_id": self.execution_id,
|
||
"trace_id": self.trace_id,
|
||
"correlation_id": self.correlation_id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"environment": self.env_key,
|
||
"tool_slug": self.tool_slug,
|
||
"tool_name": tool_name if tool_name is not None else self.tool_slug,
|
||
"caller": self.caller,
|
||
"caller_id": self.caller_id,
|
||
"status": self.status,
|
||
"operation": self.operation,
|
||
"started_at": format_utc_datetime(self.started_at),
|
||
"execution_time": format_utc_datetime(self.started_at),
|
||
"ended_at": format_utc_datetime(self.ended_at) if self.ended_at else None,
|
||
"duration_ms": self.duration_ms,
|
||
"request_summary": self.request_summary or {},
|
||
"response_summary": self.response_summary,
|
||
"error": self.error,
|
||
"retry_of": self.retry_of,
|
||
"retry_count": self.retry_count,
|
||
"tags": self.tags 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),
|
||
}
|
||
|
||
|
||
class ExternalToolExecutionTrace(Base):
|
||
"""外部系统工具执行调用链路。"""
|
||
|
||
__tablename__ = "ext_execution_traces"
|
||
|
||
# 主键
|
||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
execution_id = Column(String(64), nullable=False, unique=True, comment="关联执行记录")
|
||
trace_id = Column(String(64), nullable=False, index=True, comment="调用链路 ID")
|
||
|
||
# 链路数据
|
||
spans = Column(JSON_VALUE, nullable=False, comment="完整 Span 树")
|
||
total_duration_ms = Column(Integer, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。"""
|
||
return {
|
||
"execution_id": self.execution_id,
|
||
"trace_id": self.trace_id,
|
||
"total_duration_ms": self.total_duration_ms,
|
||
"spans": self.spans 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),
|
||
}
|
||
|
||
|
||
class ExternalSystemHealthCheck(Base):
|
||
"""外部系统健康检查历史记录。
|
||
|
||
每个外部系统保留最近一次健康检查结果,用于全局健康检查页的一览展示。
|
||
"""
|
||
|
||
__tablename__ = "ext_system_health_checks"
|
||
|
||
# 主键
|
||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="所属系统 ID",
|
||
)
|
||
env_key = Column(String(32), nullable=False, default="default", comment="环境标识")
|
||
|
||
# 探测结果
|
||
health_status = Column(
|
||
String(32),
|
||
nullable=False,
|
||
default="not_checked",
|
||
comment="健康状态:healthy/auth_failure/connectivity_issue/not_checked",
|
||
)
|
||
duration_ms = Column(Integer, nullable=True, comment="探测耗时毫秒")
|
||
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="探测时间")
|
||
|
||
# 审计字段
|
||
created_by = Column(String(64), nullable=True)
|
||
updated_by = Column(String(64), nullable=True)
|
||
created_at = Column(DateTime, nullable=False, default=utc_now_naive)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
UniqueConstraint("system_id", "env_key", name="uq_ext_system_health_checks_system_env"),
|
||
Index("ix_ext_system_health_checks_status", "health_status"),
|
||
Index("ix_ext_system_health_checks_checked_at", "checked_at"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。"""
|
||
return {
|
||
"id": self.id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"health_status": self.health_status,
|
||
"duration_ms": self.duration_ms,
|
||
"detail": self.detail,
|
||
"error_code": self.error_code,
|
||
"error_message": self.error_message,
|
||
"checked_at": format_utc_datetime(self.checked_at) if self.checked_at else None,
|
||
"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 ExternalWebhookSubscription(Base):
|
||
"""外部系统事件订阅(Webhook 入站)。
|
||
|
||
承载 Yuxi 对外部系统事件的订阅关系:哪个系统/环境的什么事件、由哪个
|
||
handler(agent/workflow)处理,以及外部系统返回的订阅 ID、验签密钥、
|
||
过期与续期信息。与 ExternalToolExecution 的"主动调用"链路互补,共同
|
||
覆盖双向集成场景。
|
||
"""
|
||
|
||
__tablename__ = "ext_webhook_subscriptions"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 基础信息
|
||
slug = Column(String(128), nullable=False, unique=True, index=True, comment="订阅唯一标识,用于回调路径")
|
||
name = Column(String(128), nullable=False, comment="展示名称")
|
||
description = Column(Text, nullable=False, default="", comment="订阅描述")
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="所属外部系统 ID",
|
||
)
|
||
env_key = Column(String(32), nullable=False, default="default", comment="环境标识")
|
||
|
||
# 事件订阅信息
|
||
source_event_type = Column(
|
||
String(128), nullable=False, index=True, comment="外部事件类型,如 salesforce.account.updated"
|
||
)
|
||
subscription_id = Column(String(256), nullable=True, index=True, comment="外部系统返回的订阅 ID")
|
||
|
||
# 处理方信息
|
||
target_handler_type = Column(String(32), nullable=False, comment="处理方类型:agent/workflow/webhook_forward")
|
||
target_handler_id = Column(String(64), nullable=True, comment="处理方实体 ID")
|
||
|
||
# 回调与验签
|
||
callback_path = Column(String(256), nullable=False, comment="Yuxi 接收路径,如 /api/external/webhooks/{slug}")
|
||
secret = Column(Text, nullable=True, comment="验签密钥(加密存储,to_dict 不暴露明文)")
|
||
secret_algorithm = Column(String(32), nullable=False, default="hmac_sha256", comment="验签算法")
|
||
|
||
# 过滤与转换规则
|
||
filter_rules = Column(JSON_VALUE, nullable=False, default=dict, comment="事件过滤规则")
|
||
transform_rules = Column(JSON_VALUE, nullable=False, default=dict, comment="事件转换规则")
|
||
|
||
# 状态信息
|
||
status = Column(
|
||
String(32),
|
||
nullable=False,
|
||
default="pending",
|
||
comment="订阅状态:pending/active/paused/expired/disabled",
|
||
)
|
||
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
|
||
|
||
# 过期与续期
|
||
expires_at = Column(DateTime, nullable=True, index=True, comment="外部系统订阅过期时间")
|
||
auto_renew = Column(Boolean, nullable=False, default=True, comment="是否自动续期")
|
||
last_renewed_at = Column(DateTime, nullable=True, comment="上次续期时间")
|
||
last_renew_error = Column(Text, nullable=True, comment="上次续期错误信息")
|
||
renewal_attempts = Column(Integer, nullable=False, default=0, comment="续期连续失败次数")
|
||
|
||
# 接收统计
|
||
last_received_at = Column(DateTime, nullable=True, comment="最近一次收到事件时间")
|
||
received_count = Column(BigInteger, nullable=False, default=0, comment="累计收到事件数")
|
||
retention_days = Column(Integer, nullable=False, default=30, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index("ix_ext_webhook_subscriptions_system_env_event", "system_id", "env_key", "source_event_type"),
|
||
Index("ix_ext_webhook_subscriptions_status_enabled", "status", "enabled"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典;secret 仅返回是否存在,明文由服务层按权限解密。"""
|
||
return {
|
||
"id": self.id,
|
||
"slug": self.slug,
|
||
"name": self.name,
|
||
"description": self.description,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"source_event_type": self.source_event_type,
|
||
"subscription_id": self.subscription_id,
|
||
"target_handler_type": self.target_handler_type,
|
||
"target_handler_id": self.target_handler_id,
|
||
"callback_path": self.callback_path,
|
||
"has_secret": bool(self.secret),
|
||
"secret_algorithm": self.secret_algorithm,
|
||
"filter_rules": self.filter_rules or {},
|
||
"transform_rules": self.transform_rules or {},
|
||
"status": self.status,
|
||
"enabled": self.enabled,
|
||
"expires_at": format_utc_datetime(self.expires_at) if self.expires_at else None,
|
||
"auto_renew": self.auto_renew,
|
||
"last_renewed_at": format_utc_datetime(self.last_renewed_at) if self.last_renewed_at else None,
|
||
"last_renew_error": self.last_renew_error,
|
||
"renewal_attempts": self.renewal_attempts,
|
||
"last_received_at": format_utc_datetime(self.last_received_at) if self.last_received_at else None,
|
||
"received_count": self.received_count,
|
||
"retention_days": self.retention_days,
|
||
"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 ExternalWebhookEvent(Base):
|
||
"""Webhook 事件落地。
|
||
|
||
外部系统推送的原始事件持久化,支持基于 external_event_id 的幂等去重、
|
||
重放、关联到智能体运行。payload/headers 由调用方在写入前完成脱敏,
|
||
不得包含密钥、Token 等敏感信息。
|
||
"""
|
||
|
||
__tablename__ = "ext_webhook_events"
|
||
|
||
# 主键
|
||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
subscription_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_webhook_subscriptions.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="所属订阅 ID",
|
||
)
|
||
subscription_slug = Column(String(128), nullable=False, index=True, comment="订阅 slug(冗余,便于查询)")
|
||
|
||
# 事件基础信息
|
||
external_event_id = Column(String(256), nullable=True, index=True, comment="外部系统事件 ID,用于幂等去重")
|
||
event_type = Column(String(128), nullable=False, index=True, comment="事件类型")
|
||
payload = Column(JSON_VALUE, nullable=False, comment="事件载荷(已脱敏)")
|
||
payload_hash = Column(
|
||
String(64), nullable=True, index=True, comment="payload SHA-256,用于无 external_event_id 时的去重"
|
||
)
|
||
headers = Column(JSON_VALUE, nullable=False, default=dict, comment="请求头(已脱敏)")
|
||
signature = Column(String(512), nullable=True, comment="请求签名")
|
||
signature_verified = Column(Boolean, nullable=False, default=False, comment="是否验签通过")
|
||
|
||
# 接收信息
|
||
received_at = Column(DateTime, nullable=False, index=True, comment="收到时间")
|
||
source_ip = Column(String(64), nullable=True, comment="来源 IP")
|
||
|
||
# 处理状态
|
||
processing_status = Column(
|
||
String(32),
|
||
nullable=False,
|
||
default="pending",
|
||
index=True,
|
||
comment="处理状态:pending/processing/processed/failed/ignored/duplicate",
|
||
)
|
||
processing_attempts = Column(Integer, nullable=False, default=0, comment="处理尝试次数")
|
||
last_processed_at = Column(DateTime, nullable=True, comment="上次处理时间")
|
||
processing_error = Column(Text, nullable=True, comment="处理错误信息")
|
||
|
||
# 触发关联
|
||
correlation_id = Column(String(64), nullable=True, index=True, comment="业务关联 ID,如触发的 agent_run_id")
|
||
triggered_handler_type = Column(String(32), nullable=True, comment="实际触发的处理方类型")
|
||
triggered_handler_id = Column(String(64), nullable=True, comment="实际触发的处理方 ID")
|
||
triggered_execution_id = Column(String(64), nullable=True, index=True, comment="触发的执行记录 ID")
|
||
|
||
# 审计字段
|
||
created_by = Column(String(64), nullable=True)
|
||
updated_by = Column(String(64), nullable=True)
|
||
created_at = Column(DateTime, nullable=False, default=utc_now_naive)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
UniqueConstraint("subscription_id", "external_event_id", name="uq_ext_webhook_events_sub_ext_event"),
|
||
Index("ix_ext_webhook_events_received_status", "received_at", "processing_status"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。"""
|
||
return {
|
||
"id": self.id,
|
||
"subscription_id": self.subscription_id,
|
||
"subscription_slug": self.subscription_slug,
|
||
"external_event_id": self.external_event_id,
|
||
"event_type": self.event_type,
|
||
"payload": self.payload or {},
|
||
"payload_hash": self.payload_hash,
|
||
"headers": self.headers or {},
|
||
"signature": self.signature,
|
||
"signature_verified": self.signature_verified,
|
||
"received_at": format_utc_datetime(self.received_at),
|
||
"source_ip": self.source_ip,
|
||
"processing_status": self.processing_status,
|
||
"processing_attempts": self.processing_attempts,
|
||
"last_processed_at": format_utc_datetime(self.last_processed_at) if self.last_processed_at else None,
|
||
"processing_error": self.processing_error,
|
||
"correlation_id": self.correlation_id,
|
||
"triggered_handler_type": self.triggered_handler_type,
|
||
"triggered_handler_id": self.triggered_handler_id,
|
||
"triggered_execution_id": self.triggered_execution_id,
|
||
"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 ExternalSystemQuotaUsage(Base):
|
||
"""外部系统 API 配额跟踪。
|
||
|
||
按时间窗口跟踪外部系统 API 配额使用情况(如 Salesforce 24h 限额、
|
||
Microsoft Graph 每日限额)。配额值可从响应头解析或手动配置,用于
|
||
调用前预检与接近上限时降级。
|
||
"""
|
||
|
||
__tablename__ = "ext_system_quota_usage"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="所属外部系统 ID",
|
||
)
|
||
env_key = Column(String(32), nullable=False, default="default", comment="环境标识")
|
||
|
||
# 配额定义
|
||
quota_key = Column(String(128), nullable=False, comment="配额项标识,如 salesforce.api_calls.24h")
|
||
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="窗口结束时间(重置时间)")
|
||
|
||
# 配额数值
|
||
limit_value = Column(BigInteger, nullable=False, default=0, comment="配额上限")
|
||
used_value = Column(BigInteger, nullable=False, default=0, comment="已用值")
|
||
unit = Column(String(32), nullable=False, default="calls", comment="配额单位:calls/bytes/records")
|
||
|
||
# 配额来源
|
||
source = Column(String(32), nullable=False, default="config", comment="配额来源:response_header/manual/config")
|
||
source_header_name = Column(String(128), nullable=True, comment="响应头字段名,如 X-RateLimit-Remaining")
|
||
|
||
# 告警阈值
|
||
warning_threshold = Column(Integer, nullable=False, default=80, comment="告警阈值百分比(0-100)")
|
||
critical_threshold = Column(Integer, nullable=False, default=95, comment="严重告警阈值百分比(0-100)")
|
||
|
||
# 时间戳
|
||
last_updated_at = Column(DateTime, nullable=True, comment="配额数据最后更新时间")
|
||
last_response_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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
UniqueConstraint("system_id", "env_key", "quota_key", name="uq_ext_system_quota_usage_system_env_key"),
|
||
Index("ix_ext_system_quota_usage_window_end", "window_end"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典;usage_ratio 由 used/limit 计算,避免存储不一致。"""
|
||
limit_value = self.limit_value or 0
|
||
used_value = self.used_value or 0
|
||
usage_ratio = round(used_value / limit_value, 4) if limit_value > 0 else 0.0
|
||
return {
|
||
"id": self.id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"quota_key": self.quota_key,
|
||
"quota_name": self.quota_name,
|
||
"quota_window": self.quota_window,
|
||
"window_start": format_utc_datetime(self.window_start) if self.window_start else None,
|
||
"window_end": format_utc_datetime(self.window_end) if self.window_end else None,
|
||
"limit_value": limit_value,
|
||
"used_value": used_value,
|
||
"remaining_value": max(limit_value - used_value, 0),
|
||
"usage_ratio": usage_ratio,
|
||
"unit": self.unit,
|
||
"source": self.source,
|
||
"source_header_name": self.source_header_name,
|
||
"warning_threshold": self.warning_threshold,
|
||
"critical_threshold": self.critical_threshold,
|
||
"last_updated_at": format_utc_datetime(self.last_updated_at) if self.last_updated_at else None,
|
||
"last_response_at": format_utc_datetime(self.last_response_at) if self.last_response_at else None,
|
||
"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 ExternalSystemAlert(Base):
|
||
"""外部系统告警事件。
|
||
|
||
记录健康检查失败、熔断打开、Token 连续刷新失败、配额接近上限、连续
|
||
超时等运维事件。同一 dedup_key 在 firing 状态下唯一,避免重复告警;
|
||
resolved 后允许再次触发。用于前端运维大盘展示与通知驱动。
|
||
"""
|
||
|
||
__tablename__ = "ext_system_alerts"
|
||
|
||
# 主键
|
||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="SET NULL"),
|
||
nullable=True,
|
||
index=True,
|
||
comment="所属外部系统 ID(系统删除时置空,保留告警历史)",
|
||
)
|
||
env_key = Column(String(32), nullable=True, comment="环境标识")
|
||
|
||
# 告警分类与状态
|
||
alert_type = Column(
|
||
String(64),
|
||
nullable=False,
|
||
index=True,
|
||
comment="告警类型:health_check_failed/circuit_open/token_refresh_failed/quota_near_limit/continuous_timeout/webhook_expired",
|
||
)
|
||
severity = Column(String(32), nullable=False, default="warning", comment="严重程度:info/warning/critical/fatal")
|
||
status = Column(
|
||
String(32),
|
||
nullable=False,
|
||
default="firing",
|
||
index=True,
|
||
comment="告警状态:firing/acknowledged/resolved/suppressed",
|
||
)
|
||
|
||
# 告警内容
|
||
title = Column(String(256), nullable=False, comment="告警标题")
|
||
description = Column(Text, nullable=True, comment="告警描述")
|
||
detail = Column(JSON_VALUE, nullable=False, default=dict, comment="告警详情")
|
||
|
||
# 关联资源
|
||
resource_type = Column(String(32), nullable=True, comment="关联资源类型:system/tool/webhook/sync_job/quota")
|
||
resource_id = Column(String(128), nullable=True, comment="关联资源 ID")
|
||
related_execution_id = Column(String(64), nullable=True, comment="关联执行记录 ID")
|
||
related_trace_id = Column(String(64), nullable=True, comment="关联链路 ID")
|
||
metric_snapshot = Column(JSON_VALUE, nullable=True, comment="触发时的指标快照")
|
||
|
||
# 去重与时间
|
||
dedup_key = Column(String(256), nullable=True, index=True, comment="去重键,相同 key 的 firing 告警合并")
|
||
triggered_at = Column(DateTime, nullable=False, index=True, comment="告警触发时间")
|
||
resolved_at = Column(DateTime, nullable=True, comment="告警恢复时间")
|
||
|
||
# 确认与恢复
|
||
acknowledged_by = Column(String(64), nullable=True, comment="确认人")
|
||
acknowledged_at = Column(DateTime, nullable=True, comment="确认时间")
|
||
resolution_note = Column(Text, nullable=True, comment="恢复备注")
|
||
|
||
# 通知状态
|
||
notification_status = Column(
|
||
String(32), nullable=False, default="pending", comment="通知状态:pending/sent/failed/skipped"
|
||
)
|
||
notification_sent_at = Column(DateTime, nullable=True, comment="通知发送时间")
|
||
notification_channels = Column(JSON_VALUE, nullable=False, default=dict, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index("ix_ext_system_alerts_status_severity_triggered", "status", "severity", "triggered_at"),
|
||
Index("ix_ext_system_alerts_system_status", "system_id", "status"),
|
||
Index("ix_ext_system_alerts_alert_type_triggered", "alert_type", "triggered_at"),
|
||
# 同一 dedup_key 在 firing 状态下唯一,避免重复告警
|
||
Index(
|
||
"uq_ext_system_alerts_dedup_firing",
|
||
"dedup_key",
|
||
unique=True,
|
||
postgresql_where="status = 'firing'",
|
||
),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。"""
|
||
return {
|
||
"id": self.id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"alert_type": self.alert_type,
|
||
"severity": self.severity,
|
||
"status": self.status,
|
||
"title": self.title,
|
||
"description": self.description,
|
||
"detail": self.detail or {},
|
||
"resource_type": self.resource_type,
|
||
"resource_id": self.resource_id,
|
||
"related_execution_id": self.related_execution_id,
|
||
"related_trace_id": self.related_trace_id,
|
||
"metric_snapshot": self.metric_snapshot,
|
||
"dedup_key": self.dedup_key,
|
||
"triggered_at": format_utc_datetime(self.triggered_at),
|
||
"resolved_at": format_utc_datetime(self.resolved_at) if self.resolved_at else None,
|
||
"acknowledged_by": self.acknowledged_by,
|
||
"acknowledged_at": format_utc_datetime(self.acknowledged_at) if self.acknowledged_at else None,
|
||
"resolution_note": self.resolution_note,
|
||
"notification_status": self.notification_status,
|
||
"notification_sent_at": format_utc_datetime(self.notification_sent_at)
|
||
if self.notification_sent_at
|
||
else None,
|
||
"notification_channels": self.notification_channels 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),
|
||
}
|
||
|
||
|
||
class ExternalSecretRotationPolicy(Base):
|
||
"""外部系统密钥轮换策略。
|
||
|
||
管理外部系统密钥(OAuth client secret、API key、mTLS 证书等)的轮换
|
||
周期与状态。secret_ref 指向 ExternalSystem.secret_refs 中的具名 secret;
|
||
auto 模式由适配器执行轮换,manual 模式仅提醒。用于满足企业安全合规
|
||
要求,避免密钥过期导致的生产事故。
|
||
"""
|
||
|
||
__tablename__ = "ext_secret_rotation_policies"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="所属外部系统 ID",
|
||
)
|
||
env_key = Column(String(32), nullable=False, default="default", comment="环境标识")
|
||
|
||
# 密钥定义
|
||
secret_type = Column(
|
||
String(32), nullable=False, comment="密钥类型:client_secret/api_key/cert/password/connection_string"
|
||
)
|
||
secret_name = Column(String(128), nullable=False, comment="密钥名称,如 salesforce_consumer_secret")
|
||
secret_ref = Column(String(128), nullable=False, comment="引用 ExternalSystem.secret_refs 中的 key")
|
||
|
||
# 轮换策略
|
||
rotation_mode = Column(String(32), nullable=False, default="manual", comment="轮换模式:auto/manual")
|
||
rotation_interval_days = Column(Integer, nullable=False, default=90, comment="轮换周期(天)")
|
||
notify_before_days = Column(Integer, nullable=False, default=7, comment="提前通知天数")
|
||
notify_channels = Column(JSON_VALUE, nullable=False, default=dict, comment="通知渠道配置")
|
||
|
||
# 轮换状态
|
||
last_rotated_at = Column(DateTime, nullable=True, comment="上次成功轮换时间(仅成功时更新)")
|
||
next_rotation_at = Column(DateTime, nullable=True, index=True, comment="下次轮换时间")
|
||
last_rotation_at = Column(DateTime, nullable=True, comment="上次轮换尝试时间(含失败,每次尝试均更新)")
|
||
last_rotation_status = Column(String(32), nullable=True, comment="上次轮换结果:success/failed/skipped")
|
||
last_rotation_error = Column(Text, nullable=True, comment="上次轮换错误信息")
|
||
rotation_count = Column(Integer, nullable=False, default=0, comment="成功轮换次数")
|
||
max_rotation_retries = Column(Integer, nullable=False, default=3, comment="最大重试次数")
|
||
|
||
# 策略状态
|
||
status = Column(
|
||
String(32),
|
||
nullable=False,
|
||
default="active",
|
||
index=True,
|
||
comment="策略状态:active/rotating/overdue/failed/disabled",
|
||
)
|
||
|
||
# 审计字段
|
||
created_by = Column(String(64), nullable=True)
|
||
updated_by = Column(String(64), nullable=True)
|
||
created_at = Column(DateTime, nullable=False, default=utc_now_naive)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
UniqueConstraint(
|
||
"system_id",
|
||
"env_key",
|
||
"secret_type",
|
||
"secret_name",
|
||
name="uq_ext_secret_rotation_policies_system_env_secret",
|
||
),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。"""
|
||
return {
|
||
"id": self.id,
|
||
"system_id": self.system_id,
|
||
"env_key": self.env_key,
|
||
"secret_type": self.secret_type,
|
||
"secret_name": self.secret_name,
|
||
"secret_ref": self.secret_ref,
|
||
"rotation_mode": self.rotation_mode,
|
||
"rotation_interval_days": self.rotation_interval_days,
|
||
"notify_before_days": self.notify_before_days,
|
||
"notify_channels": self.notify_channels or {},
|
||
"last_rotated_at": format_utc_datetime(self.last_rotated_at) if self.last_rotated_at else None,
|
||
"next_rotation_at": format_utc_datetime(self.next_rotation_at) if self.next_rotation_at else None,
|
||
"last_rotation_at": format_utc_datetime(self.last_rotation_at) if self.last_rotation_at else None,
|
||
"last_rotation_status": self.last_rotation_status,
|
||
"last_rotation_error": self.last_rotation_error,
|
||
"rotation_count": self.rotation_count,
|
||
"max_rotation_retries": self.max_rotation_retries,
|
||
"status": self.status,
|
||
"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 ExternalToolAccessRule(Base):
|
||
"""外部工具访问控制规则。
|
||
|
||
基于 principal(user/role/department/agent)+ tool + env 的细粒度访问
|
||
控制。effect 为 allow/deny,priority 越高优先级越高(deny 默认优先)。
|
||
conditions 承载时间窗口、只读约束、调用上限等复杂条件。用于多部门
|
||
共用平台时的合规管控。
|
||
"""
|
||
|
||
__tablename__ = "ext_tool_access_rules"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
tool_slug = Column(
|
||
String(128),
|
||
ForeignKey("ext_tool_configs.slug", ondelete="CASCADE", onupdate="CASCADE"),
|
||
nullable=True,
|
||
index=True,
|
||
comment="工具 slug(为空表示系统下所有工具)",
|
||
)
|
||
system_id = Column(
|
||
Integer,
|
||
ForeignKey("ext_systems.id", ondelete="CASCADE"),
|
||
nullable=True,
|
||
index=True,
|
||
comment="所属系统 ID(为空表示全局规则)",
|
||
)
|
||
|
||
# 主体信息
|
||
principal_type = Column(
|
||
String(32), nullable=False, index=True, comment="主体类型:user/role/department/agent/service_account"
|
||
)
|
||
principal_id = Column(String(64), nullable=False, index=True, comment="主体 ID")
|
||
principal_name = Column(String(128), nullable=True, comment="主体名称(冗余,便于展示)")
|
||
|
||
# 约束条件
|
||
env_key = Column(String(32), nullable=True, comment="约束到特定环境,为空表示所有环境")
|
||
effect = Column(String(16), nullable=False, default="allow", comment="规则效果:allow/deny")
|
||
priority = Column(Integer, nullable=False, default=0, comment="优先级,数字越大优先级越高")
|
||
conditions = Column(
|
||
JSON_VALUE, nullable=False, default=dict, comment="附加条件,如 {time_window, read_only, max_calls_per_day}"
|
||
)
|
||
expires_at = Column(DateTime, nullable=True, index=True, comment="规则过期时间,为空表示永久")
|
||
|
||
# 状态信息
|
||
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
|
||
description = 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index("ix_ext_tool_access_rules_tool_enabled", "tool_slug", "enabled"),
|
||
Index("ix_ext_tool_access_rules_principal", "principal_type", "principal_id"),
|
||
Index("ix_ext_tool_access_rules_system_enabled", "system_id", "enabled"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。"""
|
||
return {
|
||
"id": self.id,
|
||
"tool_slug": self.tool_slug,
|
||
"system_id": self.system_id,
|
||
"principal_type": self.principal_type,
|
||
"principal_id": self.principal_id,
|
||
"principal_name": self.principal_name,
|
||
"env_key": self.env_key,
|
||
"effect": self.effect,
|
||
"priority": self.priority,
|
||
"conditions": self.conditions or {},
|
||
"expires_at": format_utc_datetime(self.expires_at) if self.expires_at else None,
|
||
"enabled": self.enabled,
|
||
"description": self.description,
|
||
"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 ExternalToolTestCase(Base):
|
||
"""外部工具回归测试用例。
|
||
|
||
为关键工具配置请求 fixture 与期望断言,定期运行以监控外部系统 API
|
||
变更导致的静默失败(如返回 200 但结构变化)。支持 jsonpath/status_code
|
||
/response_time 等多种断言类型;连续失败达到阈值时触发告警。
|
||
"""
|
||
|
||
__tablename__ = "ext_tool_test_cases"
|
||
|
||
# 主键
|
||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||
|
||
# 关联关系
|
||
tool_slug = Column(
|
||
String(128),
|
||
ForeignKey("ext_tool_configs.slug", ondelete="CASCADE", onupdate="CASCADE"),
|
||
nullable=False,
|
||
index=True,
|
||
comment="被测工具 slug",
|
||
)
|
||
|
||
# 用例基础信息
|
||
name = Column(String(128), nullable=False, comment="用例名称")
|
||
description = Column(Text, nullable=True, comment="用例说明")
|
||
env_key = Column(String(32), nullable=True, comment="测试环境,为空使用默认环境")
|
||
|
||
# 请求与断言
|
||
request_fixture = Column(JSON_VALUE, nullable=False, comment="请求参数 fixture")
|
||
expected_status = Column(String(32), nullable=True, comment="期望状态:success/failed/具体状态码")
|
||
assertions = Column(
|
||
JSON_VALUE, nullable=False, default=list, comment="断言列表,如 [{type: jsonpath, path, op, value}]"
|
||
)
|
||
timeout = Column(Integer, nullable=False, default=30, comment="超时时间(秒)")
|
||
|
||
# 调度与启用
|
||
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
|
||
schedule_cron = Column(String(64), nullable=True, index=True, comment="运行计划 cron 表达式,为空表示手动运行")
|
||
|
||
# 上次运行结果
|
||
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_duration_ms = Column(Integer, nullable=True, comment="上次运行耗时毫秒")
|
||
last_run_response = Column(JSON_VALUE, nullable=True, comment="上次运行响应(已脱敏)")
|
||
last_run_error = Column(Text, nullable=True, comment="上次运行错误信息")
|
||
last_run_assertions_result = Column(JSON_VALUE, nullable=True, comment="上次运行断言明细")
|
||
|
||
# 告警配置
|
||
consecutive_failures = Column(Integer, nullable=False, default=0, comment="连续失败次数")
|
||
alert_on_failure = Column(Boolean, nullable=False, default=True, comment="失败是否触发告警")
|
||
alert_threshold = Column(Integer, nullable=False, default=3, 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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index("ix_ext_tool_test_cases_tool_enabled", "tool_slug", "enabled"),
|
||
Index("ix_ext_tool_test_cases_last_run_status", "last_run_status"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典。"""
|
||
return {
|
||
"id": self.id,
|
||
"tool_slug": self.tool_slug,
|
||
"name": self.name,
|
||
"description": self.description,
|
||
"env_key": self.env_key,
|
||
"request_fixture": self.request_fixture or {},
|
||
"expected_status": self.expected_status,
|
||
"assertions": self.assertions or [],
|
||
"timeout": self.timeout,
|
||
"enabled": self.enabled,
|
||
"schedule_cron": self.schedule_cron,
|
||
"last_run_at": format_utc_datetime(self.last_run_at) if self.last_run_at else None,
|
||
"last_run_status": self.last_run_status,
|
||
"last_run_duration_ms": self.last_run_duration_ms,
|
||
"last_run_response": self.last_run_response,
|
||
"last_run_error": self.last_run_error,
|
||
"last_run_assertions_result": self.last_run_assertions_result,
|
||
"consecutive_failures": self.consecutive_failures,
|
||
"alert_on_failure": self.alert_on_failure,
|
||
"alert_threshold": self.alert_threshold,
|
||
"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 ExternalNotificationChannel(Base):
|
||
"""外部系统通知渠道配置。
|
||
|
||
管理告警通知渠道(Slack webhook、Email SMTP、企业机器人等)的配置,
|
||
供 ``NotificationService`` 在发送告警通知时按 ``channel_type`` 分派。
|
||
``config`` 为渠道特定配置 dict,可能含敏感字段(webhook URL、SMTP 密码
|
||
等),由 persistence 边界加解密、use_cases mapper 脱敏。
|
||
"""
|
||
|
||
__tablename__ = "ext_notification_channels"
|
||
|
||
# 主键
|
||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||
|
||
# 渠道基础信息
|
||
channel_type = Column(
|
||
String(64), nullable=False, index=True, comment="渠道类型:slack/email/feishu/dingtalk/webhook/..."
|
||
)
|
||
name = Column(String(128), nullable=False, comment="展示名称")
|
||
description = Column(Text, nullable=True, comment="渠道描述")
|
||
|
||
# 渠道配置(敏感字段加密存储)
|
||
config = Column(JSON_VALUE, nullable=False, default=dict, comment="渠道配置(敏感字段加密存储)")
|
||
|
||
# 启用状态
|
||
enabled = Column(Boolean, nullable=False, default=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)
|
||
updated_at = Column(DateTime, nullable=False, default=utc_now_naive, onupdate=utc_now_naive)
|
||
is_deleted = Column(Integer, nullable=False, default=0, index=True)
|
||
deleted_at = Column(DateTime, nullable=True)
|
||
|
||
__table_args__ = (
|
||
Index("ix_ext_notification_channels_type_enabled", "channel_type", "enabled"),
|
||
Index("ix_ext_notification_channels_name", "name"),
|
||
)
|
||
|
||
def to_dict(self) -> dict[str, Any]:
|
||
"""序列化为 API 响应字典;config 不返回明文,仅返回是否存在。"""
|
||
return {
|
||
"id": self.id,
|
||
"channel_type": self.channel_type,
|
||
"name": self.name,
|
||
"description": self.description,
|
||
"has_config": bool(self.config),
|
||
"enabled": 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),
|
||
}
|