2026-08-06 00:17:49 +08:00
|
|
|
|
"""统一编号服务 —— 原子取号,替代各处分散扫描式生成(§3.3 编号约定)。
|
|
|
|
|
|
|
|
|
|
|
|
取号基于 bre_sequence(name/current_seq)表 + PostgreSQL 事务级 advisory lock:
|
|
|
|
|
|
pg_advisory_xact_lock 在事务内串行化同名取号,锁随事务提交/回滚自动释放,
|
|
|
|
|
|
与 CRUD 同事务提交 ⇒ 并发请求不重复、失败自动回滚不留空号。
|
|
|
|
|
|
|
|
|
|
|
|
存量数据迁移:首次使用某序列名时由 seed_fn(服务端传入)从既有记录推算
|
|
|
|
|
|
起始号(原扫描逻辑),此后全部走原子递增,不再扫描。
|
|
|
|
|
|
"""
|
2026-08-07 21:47:42 +08:00
|
|
|
|
from collections.abc import Awaitable, Callable
|
2026-08-06 00:17:49 +08:00
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import select, text
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
|
|
from app.api.v1.module_bre.sequence.model import SequenceModel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NumberGenService:
|
|
|
|
|
|
"""原子序号发生器。实例化于 HTTP/服务层,随当前事务取号。"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, db: AsyncSession) -> None:
|
|
|
|
|
|
self.db = db
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def _acquire_lock(db: AsyncSession, name: str) -> None:
|
|
|
|
|
|
# hashtextextended 对文本做稳定 64 位哈希,同名序列恒映射同一把锁
|
|
|
|
|
|
await db.execute(text("SELECT pg_advisory_xact_lock(hashtextextended(:n, 0))"), {"n": name})
|
|
|
|
|
|
|
|
|
|
|
|
async def next_seq(
|
|
|
|
|
|
self,
|
|
|
|
|
|
name: str,
|
|
|
|
|
|
*,
|
|
|
|
|
|
start: int = 1,
|
|
|
|
|
|
seed_fn: Callable[[], Awaitable[int | None]] | None = None,
|
|
|
|
|
|
) -> int:
|
|
|
|
|
|
"""原子取号。返回本序列名下一个应使用的序号。
|
|
|
|
|
|
|
|
|
|
|
|
name: 序列名(如 "combination:2026" / "tree:3"),按编号维度天然分片;
|
|
|
|
|
|
start: 新序列的默认起始值;
|
|
|
|
|
|
seed_fn: 仅当序列行尚不存在时调用,从存量数据推算起始号(兼容既有数据迁移)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
await self._acquire_lock(self.db, name)
|
|
|
|
|
|
row = await self.db.scalar(select(SequenceModel).where(SequenceModel.name == name))
|
|
|
|
|
|
if row is None:
|
|
|
|
|
|
init = await seed_fn() if seed_fn is not None else start
|
|
|
|
|
|
if init is None or init < 1:
|
|
|
|
|
|
init = start
|
|
|
|
|
|
self.db.add(SequenceModel(name=name, current_seq=init))
|
|
|
|
|
|
await self.db.flush()
|
|
|
|
|
|
return init
|
|
|
|
|
|
row.current_seq += 1
|
|
|
|
|
|
await self.db.flush()
|
|
|
|
|
|
return int(row.current_seq)
|
|
|
|
|
|
|
|
|
|
|
|
async def exists(self, name: str) -> bool:
|
|
|
|
|
|
"""序列行是否已初始化(供服务端判断是否需要 seed_fn 兜底)。"""
|
|
|
|
|
|
return (await self.db.scalar(select(SequenceModel.id).where(SequenceModel.name == name))) is not None
|