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
+85
@@ -0,0 +1,85 @@
|
||||
"""Snowflake IDs (架构文档 §6.1 原则 2).
|
||||
|
||||
Auto-increment keys invite enumeration attacks and collide when the database is
|
||||
later sharded; UUID primary keys have poor index locality. A 64-bit snowflake
|
||||
avoids both.
|
||||
|
||||
Layout: 41 bits ms since epoch | 10 bits node id | 12 bits sequence.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
|
||||
#: 2025-01-01T00:00:00Z in milliseconds. Do not change once IDs are issued.
|
||||
DEFAULT_EPOCH_MS = 1_735_689_600_000
|
||||
|
||||
_NODE_BITS = 10
|
||||
_SEQUENCE_BITS = 12
|
||||
_MAX_NODE_ID = (1 << _NODE_BITS) - 1
|
||||
_SEQUENCE_MASK = (1 << _SEQUENCE_BITS) - 1
|
||||
|
||||
|
||||
def _wall_clock_ms() -> int:
|
||||
return int(time.time() * 1000)
|
||||
|
||||
|
||||
class SnowflakeGenerator:
|
||||
"""Thread-safe monotonic 64-bit id generator."""
|
||||
|
||||
__slots__ = ("_clock", "_epoch_ms", "_last_ms", "_lock", "_node_id", "_sequence")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
node_id: int = 1,
|
||||
epoch_ms: int = DEFAULT_EPOCH_MS,
|
||||
clock: Callable[[], int] | None = None,
|
||||
) -> None:
|
||||
if not 0 <= node_id <= _MAX_NODE_ID:
|
||||
msg = f"node_id must be within 0..{_MAX_NODE_ID}"
|
||||
raise ValueError(msg)
|
||||
self._node_id = node_id
|
||||
self._epoch_ms = epoch_ms
|
||||
self._sequence = 0
|
||||
self._last_ms = -1
|
||||
self._lock = threading.Lock()
|
||||
self._clock = clock or _wall_clock_ms
|
||||
|
||||
def _now_ms(self) -> int:
|
||||
return self._clock()
|
||||
|
||||
def next_id(self) -> int:
|
||||
with self._lock:
|
||||
now = self._now_ms()
|
||||
if now < self._last_ms:
|
||||
# Clock moved backwards: wait it out rather than issue duplicates.
|
||||
now = self._last_ms
|
||||
if now == self._last_ms:
|
||||
self._sequence = (self._sequence + 1) & _SEQUENCE_MASK
|
||||
if self._sequence == 0:
|
||||
while now <= self._last_ms:
|
||||
now = self._now_ms()
|
||||
else:
|
||||
self._sequence = 0
|
||||
self._last_ms = now
|
||||
elapsed = now - self._epoch_ms
|
||||
return (
|
||||
(elapsed << (_NODE_BITS + _SEQUENCE_BITS))
|
||||
| (self._node_id << _SEQUENCE_BITS)
|
||||
| self._sequence
|
||||
)
|
||||
|
||||
|
||||
_default_generator = SnowflakeGenerator()
|
||||
|
||||
|
||||
def next_id() -> int:
|
||||
"""Process-wide default generator."""
|
||||
return _default_generator.next_id()
|
||||
|
||||
|
||||
def timestamp_ms_of(snowflake_id: int, epoch_ms: int = DEFAULT_EPOCH_MS) -> int:
|
||||
"""Extract the creation timestamp from an id (useful for partition pruning)."""
|
||||
return (snowflake_id >> (_NODE_BITS + _SEQUENCE_BITS)) + epoch_ms
|
||||
Reference in New Issue
Block a user