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。
70 lines
2.9 KiB
Python
70 lines
2.9 KiB
Python
"""核心配置 — Pydantic Settings 自动从 .env 读取"""
|
||
import json
|
||
from pydantic import model_validator
|
||
from pydantic_settings import BaseSettings
|
||
|
||
|
||
class Settings(BaseSettings):
|
||
# ---- 数据库 ----
|
||
DATABASE_URL: str = "postgresql+asyncpg://track:track_prod_2026@localhost:5433/track_production"
|
||
|
||
# ---- JWT ----
|
||
SECRET_KEY: str = "change-me-in-production"
|
||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 120 # Access Token: 2 小时
|
||
REFRESH_TOKEN_EXPIRE_DAYS: int = 7 # Refresh Token: 7 天
|
||
|
||
# ---- 调试 ----
|
||
DEBUG: bool = True
|
||
|
||
# ---- 应用元信息 ----
|
||
APP_VERSION: str = "1.0.0"
|
||
|
||
# ---- 日志 ----
|
||
LOG_LEVEL: str = "INFO"
|
||
LOG_JSON: bool = True # 生产保持 True(便于采集);本地调试可设 False 换可读格式
|
||
|
||
# ---- 错误追踪(可选,不装 sentry-sdk 则自动跳过)----
|
||
SENTRY_DSN: str | None = None
|
||
SENTRY_TRACES_SAMPLE_RATE: float = 0.0
|
||
|
||
# ---- CORS 跨域白名单(JSON 数组字符串,直接从 .env 的 CORS_ORIGINS 读取) ----
|
||
CORS_ORIGINS: str = '["http://localhost:1420", "tauri://localhost"]'
|
||
|
||
# ---- MOM 仓储系统回调 Webhook(Track 作为接收方,验签用) ----
|
||
TRACK_WEBHOOK_KEY: str | None = None # MOM 回调 POST 时 Header X-API-Key 须等于此值
|
||
|
||
# ---- 组织隔离 ----
|
||
# 同一套代码部署给不同部门时,只需改这两个值(+ compose 里的项目名/容器名/端口)。
|
||
# 全仓库的部门过滤点只有三处 SQL:登录、人员列表、物料选择器,全部引用这里。
|
||
ORG_DEPARTMENT: str = "LICA" # MOM sys_user.department 的取值
|
||
MATERIAL_CATEGORY_PREFIX: str = "LICA/" # MOM material_base.category 的部门前缀
|
||
# 产品序列号前缀(产品身份证 / 二维码内容)。留空则生成纯 16 位 HEX。
|
||
# LICA 用 "L" 打头,一眼区分部门来源;IRIS 实例保持空串不受影响。
|
||
SERIAL_PREFIX: str = "L"
|
||
|
||
@property
|
||
def CORS_ORIGINS_LIST(self) -> list[str]:
|
||
"""将 JSON 字符串解析为 Python list,供 CORSMiddleware 使用"""
|
||
try:
|
||
return json.loads(self.CORS_ORIGINS)
|
||
except (json.JSONDecodeError, TypeError):
|
||
return ["http://localhost:1420", "tauri://localhost"]
|
||
|
||
@model_validator(mode="after")
|
||
def _validate_production_secret(self):
|
||
"""生产环境强制校验:SECRET_KEY 禁止使用默认值"""
|
||
if not self.DEBUG and self.SECRET_KEY == "change-me-in-production":
|
||
raise ValueError(
|
||
"生产环境 (DEBUG=False) 禁止使用默认 SECRET_KEY。"
|
||
"请在 .env 中设置 SECRET_KEY 为至少 32 字符的随机值。"
|
||
"示例: python -c \"import secrets; print(secrets.token_urlsafe(32))\""
|
||
)
|
||
return self
|
||
|
||
class Config:
|
||
env_file = ".env"
|
||
extra = "ignore"
|
||
|
||
|
||
settings = Settings()
|