Files
hunjing/backend/app/core/dto.py
T
chenxin 28127949db ✨ feat: 数据库基线与通用 DTO(WP-01 第 3、4 步)
- 通用 DTO:Money(最小货币单位整数)、CursorPage 泛型、9 个平台 ID Annotated 类型(前缀+UUIDv7/v4 正则,与 openapi 契约对齐)
- db 基础:naming_convention Base(pk_/uq_/ix_/fk_,AGENTS 8.6)+ async engine + 事务作用域 session
- 首批 6 张表 typed mapping:user/personal_domain/session/service_account/audit_event/outbox_event;Session ID 为 CSPRNG opaque;审计与 Outbox 不可变
- Alembic async 迁移(含表注释);docker 起 hunjing-postgres 并 upgrade 验证:7 表、约束/索引命名全部符合约定
- 按 database-design skill 清单交叉检查:外键索引全覆盖(复合唯一最左前缀)、审计聚合时间线与 Outbox 投递扫描均为专用复合索引
- 集成测试 8 项(唯一约束、FK restrict/cascade、检查约束、同事务提交)连 PG 测试库运行;pytest 15 项、ruff、mypy strict 全绿
2026-09-10 10:00:17 +08:00

44 lines
1.6 KiB
Python

"""通用 DTO:与 openapi/openapi.yaml 契约对齐的 Pydantic 类型。
对齐由 tests/test_dto_contract.py 用 bundled 契约校验关键字段;
禁止在此复制会漂移的完整值集(以契约为准)。
"""
from typing import Annotated
from pydantic import BaseModel, ConfigDict, Field
def _id_pattern(prefix: str, version: int) -> str:
ver = "7" if version == 7 else "4"
tail = r"[0-9a-f]{12}-[0-9a-f]{4}-" + ver + r"[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
return f"^{prefix}" + tail
UserId = Annotated[str, Field(pattern=_id_pattern("user_", 7))]
PersonalDomainId = Annotated[str, Field(pattern=_id_pattern("domain_", 7))]
ConversationId = Annotated[str, Field(pattern=_id_pattern("conversation_", 7))]
BranchId = Annotated[str, Field(pattern=_id_pattern("branch_", 7))]
MessageId = Annotated[str, Field(pattern=_id_pattern("message_", 7))]
ContentBlockId = Annotated[str, Field(pattern=_id_pattern("block_", 7))]
RunId = Annotated[str, Field(pattern=_id_pattern("run_", 7))]
TurnId = Annotated[str, Field(pattern=_id_pattern("turn_", 7))]
RequestId = Annotated[str, Field(pattern=_id_pattern("request_", 4))]
class Money(BaseModel):
"""金额:最小货币单位整数 + ISO 4217 币种;禁止浮点(契约第 4 节)。"""
model_config = ConfigDict(extra="forbid")
amount_minor: int = Field(ge=0)
currency: str = Field(pattern=r"^[A-Z]{3}$")
class CursorPage[T](BaseModel):
"""opaque cursor 分页(契约第 4.1 节);客户端不得解析 cursor。"""
data: list[T]
next_cursor: str | None
has_more: bool