refactor(storage): 完善多ORM模型定义与注释

1. 为ChannelAccount添加乐观锁配置
2. 补充ScheduledTask与幂等表的注释与约束
3. 修正ExternalAdapterAsset状态枚举与相关方法
4. 新增外部配额与密钥轮换策略的字段与序列化支持
This commit is contained in:
Kris 2026-07-11 05:42:51 +08:00
parent 977724d7c3
commit a829e96a50
3 changed files with 63 additions and 49 deletions

View File

@ -123,6 +123,11 @@ class ChannelAccount(Base):
Index("ix_channel_accounts_plugin_status", "plugin_status"),
)
# ORM 乐观锁UPDATE 时自动附加 ``WHERE version = ?`` 并自增 version
# 并发状态转换冲突时抛 ``StaleDataError``,由适配器层翻译为 ``ConflictError``。
# 与 ``ChannelPairing`` / ``ChannelRouteBinding`` 保持一致的并发控制风格。
__mapper_args__ = {"version_id_col": version}
def to_dict(self) -> dict[str, Any]:
"""序列化为字典config 中的敏感字段由 persistence 层解密to_dict 不返回明文。"""
return {

View File

@ -7,7 +7,6 @@ created_at/updated_at/is_deleted/deleted_at支持软删除与操作追溯
from __future__ import annotations
import base64
from typing import Any
from sqlalchemy import (
@ -171,7 +170,7 @@ class ExternalAdapterAsset(Base):
nullable=False,
default="active",
index=True,
comment="资产状态active/parse_failed/inactive",
comment="资产状态active/invalid",
)
status_message = Column(String(512), nullable=True, comment="状态说明,如解析失败原因")
@ -183,42 +182,6 @@ class ExternalAdapterAsset(Base):
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):
"""外部系统一级实体。"""
@ -1033,6 +996,12 @@ class ExternalSystemQuotaUsage(Base):
# 配额来源
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")
source_header_strategy = Column(
String(32),
nullable=False,
default="remaining",
comment="响应头解析策略remaining/used/limit/fraction_used_limit",
)
# 告警阈值
warning_threshold = Column(Integer, nullable=False, default=80, comment="告警阈值百分比0-100")
@ -1076,6 +1045,7 @@ class ExternalSystemQuotaUsage(Base):
"unit": self.unit,
"source": self.source,
"source_header_name": self.source_header_name,
"source_header_strategy": self.source_header_strategy,
"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,
@ -1115,7 +1085,7 @@ class ExternalSystemAlert(Base):
String(64),
nullable=False,
index=True,
comment="告警类型health_check_failed/circuit_open/token_refresh_failed/quota_near_limit/continuous_timeout/webhook_expired",
comment="告警类型health_check_failed/circuit_open/token_refresh_failed/quota_near_limit/continuous_timeout/webhook_expired/webhook_event_failed",
)
severity = Column(String(32), nullable=False, default="warning", comment="严重程度info/warning/critical/fatal")
status = Column(
@ -1256,6 +1226,12 @@ class ExternalSecretRotationPolicy(Base):
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="最大重试次数")
current_retry_count = Column(
Integer,
nullable=False,
default=0,
comment="当前连续重试次数(失败时递增,成功/重置时清零)",
)
# 策略状态
status = Column(
@ -1304,6 +1280,7 @@ class ExternalSecretRotationPolicy(Base):
"last_rotation_error": self.last_rotation_error,
"rotation_count": self.rotation_count,
"max_rotation_retries": self.max_rotation_retries,
"current_retry_count": self.current_retry_count,
"status": self.status,
"created_by": self.created_by,
"updated_by": self.updated_by,

View File

@ -47,7 +47,11 @@ class ScheduledTask(Base):
一条记录对应一次任务注册`next_run_at` 由调度器在创建/执行后按
`schedule_kind`cron / at计算`status` 表示任务状态机当前所处状态
active / paused / dead_letter `enabled` 共同决定是否被 tick 扫描
active / paused / dead_letter`enabled` `status` 通过应用层联动
``scheduler_service.update_task`` 方案 E``enabled=False``
``status=active`` 同步置 ``paused````enabled=True`` ``status=paused``
同步置 ``active``tick 扫描条件为 ``enabled=True AND status='active'
AND next_run_at <= now``两者共同决定可执行性
"""
__tablename__ = "scheduled_tasks"
@ -59,7 +63,9 @@ class ScheduledTask(Base):
task_id = Column(
String(64),
nullable=False,
comment="业务 UUIDsystem 任务使用约定前缀如 system:run_log_cleanup唯一性由部分唯一索引 uq_scheduled_tasks_task_id_active 保障is_deleted=0软删除后允许重建",
comment="业务 UUIDsystem 任务使用约定前缀如 system:run_log_cleanup"
"唯一性由部分唯一索引 uq_scheduled_tasks_task_id_active 保障is_deleted=0"
"软删除后允许重建",
)
handler_name = Column(
String(128), nullable=False, index=True, comment="引用已注册 handler非外键worker 执行时校验)"
@ -75,7 +81,8 @@ class ScheduledTask(Base):
run_at = Column(
DateTime,
nullable=True,
comment="一次性执行时间UTC与 tz 无关at 类型必填且必须未来时间。at 类型创建时 next_run_at 同步设为此值,执行后 next_run_at 置 NULL",
comment="一次性执行时间UTC与 tz 无关at 类型必填且必须未来时间。"
"at 类型创建时 next_run_at 同步设为此值,执行后 next_run_at 置 NULL",
)
tz = Column(
String(64),
@ -86,12 +93,20 @@ class ScheduledTask(Base):
payload = Column(JSON_VALUE, nullable=False, default=dict, comment="handler 入参≤64KB")
# 运行时控制
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用dead_letter 时为 False")
enabled = Column(
Boolean,
nullable=False,
default=True,
comment="是否启用;与 status 通过应用层联动(方案 E"
"enabled=False 且 status=active 同步置 paused"
"dead_letter 任务需通过 resume_task 恢复,不能通过 enabled=True 直接启用",
)
delete_after_run = Column(
Boolean,
nullable=False,
default=False,
comment="执行成功后是否删除任务记录DB 默认 Falseat 类型由应用层显式设置 True",
comment="执行成功后是否删除任务记录;仅对 at 类型有意义,"
"cron 类型应用层拒绝设置为 TrueDB 默认 Falseat 类型由应用层显式设置 True",
)
block_strategy = Column(
String(32),
@ -111,11 +126,19 @@ class ScheduledTask(Base):
)
# 执行轨迹
last_run_at = Column(DateTime, nullable=True, comment="上次执行开始时间")
last_run_at = Column(
DateTime,
nullable=True,
comment="上次执行开始时间(方案 B语义统一为执行开始时间"
"由 record_run_result 的 started_at 参数写入,非执行结束时间)",
)
next_run_at = Column(
DateTime,
nullable=True,
comment="下次执行时间UTC。cron 类型由调度器按 cron 表达式计算at 类型创建时=run_at执行后置 NULL失败退避时按退避序列计算",
comment="下次执行时间UTC。cron 类型由调度器按 cron 表达式计算;"
"at 类型创建时=run_at成功执行后置 NULL方案 A通过 "
"finalize_one_shot_task 置 NULL + status=paused 避免无限循环);"
"失败退避时按退避序列计算",
)
last_error = Column(Text, nullable=True, comment="最近一次错误信息")
@ -338,13 +361,15 @@ class ScheduledTaskIdempotency(Base):
# 操作信息
operation = Column(
String(32), nullable=False, comment="操作类型create / update / delete / pause / resume / trigger"
String(32),
nullable=False,
comment="操作类型create / update / delete / pause / resume / trigger",
)
response_body = Column(JSON_VALUE, nullable=True, comment="首次请求的响应体;用于重复请求重放")
# 审计字段
created_by = Column(String(64), nullable=True)
updated_by = Column(String(64), nullable=True)
created_by = Column(String(64), nullable=True, comment="发起幂等请求的操作人 UID")
updated_by = Column(String(64), nullable=True, comment="回写响应体时的操作人 UID")
created_at = Column(
DateTime, nullable=False, default=utc_now_naive, index=True, comment="创建时间;用于清理过期记录"
)
@ -352,6 +377,13 @@ class ScheduledTaskIdempotency(Base):
is_deleted = Column(Integer, nullable=False, default=0, index=True)
deleted_at = Column(DateTime, nullable=True)
__table_args__ = (
CheckConstraint(
"operation IN ('create', 'update', 'delete', 'pause', 'resume', 'trigger')",
name="ck_scheduled_task_idempotency_operation",
),
)
def to_dict(self) -> dict[str, Any]:
"""序列化为 API 响应字典。"""
return {