Files
track-LICA/backend/app/services/counter_service.py
duxingchen f9f3d90f96 fix: 二维码接口去鉴权(修复破图)+ 产品序列号加部门前缀
1) 二维码破图
   /products/qrcode/{sn} 带了 Depends(get_current_user),而前端是用
   <img src="/api/v1/products/qrcode/{sn}"> 引用它的 —— <img> 无法携带
   Authorization 头,请求必然 401,页面上就是破图(同时刷大量 401 审计)。
   该接口不查库、只把调用方传进来的字符串渲染成二维码,没有数据泄露面,
   故去掉鉴权。刻意不做 ?token= 兜底:JWT 进 URL 会渗进访问日志、浏览器
   历史与 Referer,比它想解决的问题更糟。
   实测:HTTP 200 / image/png / 300x300。

2) 序列号部门前缀
   新增 config.SERIAL_PREFIX(LICA 为 "L"),counter_service 生成
   {前缀}{15 位 HEX},总长仍严格 16 位 —— products.serial_number 是
   String(16),前端 TaskTreeViewer / ManualInput / ScanPage 多处按 16 位
   校验,不能改总长。IRIS 实例该值为空串,格式保持原样。
   实测 LICA 生成 L000000000000002。
   前端本就兼容带字母的序列号:TaskTreeViewer 的占位符示例就是
   X20260801000001,ScanPage 的 replace(/[^a-zA-Z0-9]/g,"") 也不会滤掉 L。
2026-09-21 16:34:56 +08:00

47 lines
1.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""序列号计数器 — 基于 PostgreSQL Sequence生成定长序列号产品身份证"""
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
SEQUENCE_NAME = "product_hex_counter"
# 序列号总长度(与 products.serial_number String(16)、前端的 16 位校验一致)
SERIAL_LENGTH = 16
async def ensure_sequence(db: AsyncSession) -> None:
"""确保 counter sequence 存在(幂等)"""
await db.execute(
text(f"CREATE SEQUENCE IF NOT EXISTS {SEQUENCE_NAME} START 1;")
)
async def next_hex_id(db: AsyncSession, length: int = SERIAL_LENGTH) -> str:
"""
生成下一个序列号:{部门前缀}{递增计数器的 16 进制},总长固定为 length。
前缀来自 settings.SERIAL_PREFIXLICA 是 "L"IRIS 留空),
用来在一眼扫号时区分部门来源;计数器部分保持 HEX便于人工核对数量。
示例SERIAL_PREFIX = "L":
1 → "L000000000000001"
255 → "L0000000000000FF"
示例SERIAL_PREFIX = ""IRIS 实例):
1 → "0000000000000001"
255 → "00000000000000FF"
⚠️ 长度必须严格等于 lengthproducts.serial_number 是 String(16)
且 /products/qrcode/{sn} 接口会校验 16 位,前缀是从 HEX 位里让出来的。
"""
result = await db.execute(text(f"SELECT nextval('{SEQUENCE_NAME}');"))
counter: int = result.scalar_one()
prefix = settings.SERIAL_PREFIX or ""
hex_length = length - len(prefix)
if hex_length <= 0:
raise ValueError(
f"SERIAL_PREFIX({prefix!r}) 过长,没有给计数器留下位数"
)
return f"{prefix}{format(counter, f'0{hex_length}X')}"