feat(shared): cross-cutting plumbing and pure domain layer
Typed pydantic-settings config, structlog JSON logging with request/trace id propagation and redaction of phone numbers, tokens and face keys, the unified response envelope and error-code table from 架构文档 §9.2, async SQLAlchemy session management, ORM models for the eight foundation tables, and the OpenTelemetry/Prometheus wiring. packages/domain stays pure logic -- snowflake ids, the quota three-phase protocol and the tk_task state machine -- so it can be tested without a database and is held to mypy strict.
This commit is contained in:
Executable
+49
@@ -0,0 +1,49 @@
|
||||
"""Unified response envelope (架构文档 §9.2).
|
||||
|
||||
{"code": 0, "message": "ok", "data": {...}, "request_id": "..."}
|
||||
{"code": 40301, "message": "今日额度已用完", "data": {...},
|
||||
"request_id": "...", "hint": "明天恢复,或完成任务多换 3 次"}
|
||||
|
||||
Every endpoint returns this shape. Handlers in :mod:`packages.shared.handlers`
|
||||
guarantee it even for unhandled exceptions.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from packages.shared.context import get_request_id
|
||||
from packages.shared.errors import ERROR_META, ErrorCode
|
||||
|
||||
|
||||
class Envelope(BaseModel):
|
||||
code: int = Field(default=int(ErrorCode.OK), description="0 表示成功;4/5/6 开头为错误段")
|
||||
message: str = Field(default="ok", description="用户可读文案")
|
||||
data: Any = Field(default=None)
|
||||
request_id: str | None = Field(default=None)
|
||||
hint: str | None = Field(default=None, description="告诉用户下一步怎么办")
|
||||
|
||||
|
||||
def ok(data: Any = None) -> dict[str, Any]:
|
||||
return Envelope(
|
||||
code=int(ErrorCode.OK), message="ok", data=data, request_id=get_request_id()
|
||||
).model_dump(exclude_none=True)
|
||||
|
||||
|
||||
def fail(
|
||||
code: ErrorCode,
|
||||
*,
|
||||
message: str | None = None,
|
||||
hint: str | None = None,
|
||||
data: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
meta = ERROR_META.get(code, ERROR_META[ErrorCode.INTERNAL_ERROR])
|
||||
return Envelope(
|
||||
code=int(code),
|
||||
message=message or meta.message,
|
||||
data=data,
|
||||
request_id=get_request_id(),
|
||||
hint=hint if hint is not None else meta.hint,
|
||||
).model_dump(exclude_none=True)
|
||||
Reference in New Issue
Block a user