Files
track-LICA/backend/app/core/config.py
duxingchen 4c1b59d74c feat(backend): 人员选择器屏蔽查看用中转账号
依锐思(yiruisi)、石利LICA(shililica) 归属 LICA 部门,但只是用于查看内容的
中转账号。它们出现在「移动端转交接收人」和「分组候选」里会被误选,也可能
被指派任务。在 config 增加 HIDDEN_USER_ACCOUNTS(逗号分隔、可运维覆盖),
并在两个查 MOM sys_user 的端点(/users/、/groups/member-candidates)按
账号后缀排除。

按账号而非姓名匹配:MOM 里存在同名不同人(IRIS 的 石利/shili 与 LICA 的
石利LICA/shililica),按姓名会误伤别的部门。仅影响候选显示 —— 不阻止已有
的成员关系,也不硬拒绝任务指派。
2026-10-08 16:28:52 +08:00

108 lines
5.6 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.

"""核心配置 — 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 须等于此值
# ---- MOM 内部接口(Track 作为**调用方**,发起生产报废) ----
# ⚠️ 这是 Track **唯一**一处主动写 MOM 的通道。读数据一律走直连 MOM 只读库
# (app/core/mom_database.py)—— 不要因为有了这个客户端就把「读」也搬过来:
# MOM 的查询接口要 JWT + permission_required,且对非特权账号按 consumer_name
# 做行级隔离,服务账号只能拿到自己名下的数据。而「写」必须走接口:
# 跨库直写会绕过 MOM 的全部业务校验、权限与审批。
MOM_INTERNAL_API_URL: str = "http://inventory_api:8000"
# 请求头 X-API-Key 的值,须与 MOM 侧 config.MOM_INTERNAL_API_KEY 一致。
# ⚠️ 本实例与 IRIS 实例**共用同一个 MOM**,但 source_ref 带各自的公司前缀
# (`LICA:` / `IRIS:`),所以两边的幂等锚点不会撞车。
# ⚠️ 未配置 → 报废提交直接 503(Fail-Closed),不静默降级。
MOM_INTERNAL_API_KEY: str | None = None
# ---- 组织隔离 ----
# 同一套代码部署给不同部门时,只需改这两个值(+ 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"
# ---- 隐藏账号(不进人员选择器 / 分组候选)----
# 这些账号虽归属本部门(department = ORG_DEPARTMENT),但只是用于「查看内容」的
# 中转账号,不应被指派任务、也不应加入业务组 —— 从各人员选择器里隐藏。
# 按 MOM 登录账号精确匹配,即 username 中 "/" 之后那一段("依锐思/yiruisi" → yiruisi)。
# ⚠️ 必须用账号而非姓名:MOM 里存在同名不同人的情况
# (IRIS 的 石利/shili 与 LICA 的 石利LICA/shililica),按姓名匹配会误伤别的部门。
# 逗号分隔,便于运维增删而不用改代码;留空 = 不隐藏任何人。
HIDDEN_USER_ACCOUNTS: str = "yiruisi,shililica"
# ---- 业务分组数据范围 ----
# 未被分进任何业务组的**普通用户**(非超管、非主管)能看到什么:
# "ALL" = 过渡期先放行,同时打 WARNING 日志把「谁还没分组」暴露出来
# "NONE" = 目标态:看不到任何数据(列表返回空,不是 403)
# ⚠️ 直接上 NONE 会让所有未分组工人当场看不到自己的任务,现场停摆。
# 推荐节奏:先 ALL 上线 → 看日志收集未分组名单 → 配好组 → 再翻 NONE。
# 翻开关只需改这里 + 重启,不需要改代码、不需要迁移,回滚同样瞬时。
DATA_SCOPE_UNGROUPED: str = "ALL"
@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"]
@property
def HIDDEN_USER_ACCOUNT_SET(self) -> frozenset[str]:
"""隐藏账号集合(去空白、丢弃空项)。配置留空时返回空集 = 不隐藏任何人。"""
return frozenset(
a.strip() for a in self.HIDDEN_USER_ACCOUNTS.split(",") if a.strip()
)
@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()