fix(P1-G/H/I/K): 运行时配置解密/Redis JSON/eval summary/UPSERT
P1-G runtime_config:DB 故障回落 env;解密失败在生产环境抛出(ValueError), 非生产回落;DB 异常与解密异常不再共用 except Exception。 P1-H Redis 预测缓存:去掉 pickle 改用 JSON + dataclasses.asdict, datetime 字段 ISO 序列化。 P1-I eval get_eval_summary:默认 limit=None(不冒充全集);新增 run_type(默认 live) 与 season 过滤;_build_filters 同步扩展。 P1-K Team/League get_or_create:INSERT 改为 PG UPSERT(ON CONFLICT DO NOTHING) 防并发重复插入。 测试 test_p1_g_runtime_config(4/4) + test_p1_k_upsert(4/4);全量绿。
This commit is contained in:
+35
-34
@@ -104,58 +104,59 @@ async def get_runtime_value(key: str) -> str:
|
||||
"""读运行时配置:DB 覆盖值 → .env 默认值 → 空串。
|
||||
|
||||
敏感项入库时是密文,读出后自动解密;旧明文(迁移前)由 decrypt_value 透传。
|
||||
P1-G:DB 故障回落 env;但解密失败在生产环境必须抛出(不得与 DB 异常共用 except)。
|
||||
"""
|
||||
defn = SETTING_DEFS.get(key)
|
||||
db_value = None
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
row = await db.get(AppSetting, key)
|
||||
if row and row.value:
|
||||
value = crypto.decrypt_value(row.value) if defn and defn.sensitive else row.value
|
||||
if value:
|
||||
return value
|
||||
db_value = row.value
|
||||
except Exception:
|
||||
logger.warning("读取运行时配置 %s 失败,回落环境变量", key)
|
||||
logger.warning("读取运行时配置 %s 失败(DB 故障),回落环境变量", key)
|
||||
return getattr(settings, key, "") or ""
|
||||
|
||||
if db_value is None:
|
||||
return getattr(settings, key, "") or ""
|
||||
|
||||
# 解密逻辑独立于 DB 异常处理(P1-G:解密失败生产环境必须抛出)
|
||||
value = crypto.decrypt_value(db_value) if defn and defn.sensitive else db_value
|
||||
if value:
|
||||
return value
|
||||
return getattr(settings, key, "") or ""
|
||||
|
||||
|
||||
async def set_runtime_value(key: str, value: str) -> None:
|
||||
"""写入/更新 DB 覆盖值(调用方需先校验 key 在白名单内)。
|
||||
async def get_setting_origin(key: str) -> tuple[str, str]:
|
||||
"""返回 (origin, 当前生效值)。origin ∈ db / env / none。
|
||||
|
||||
敏感项(SETTING_DEFS.sensitive)以 Fernet 加密存储,库里不落明文。
|
||||
P1-G:DB 故障回落 env;解密失败在生产环境抛出。
|
||||
"""
|
||||
defn = SETTING_DEFS.get(key)
|
||||
stored = crypto.encrypt_value(value) if defn and defn.sensitive else value
|
||||
async with AsyncSessionLocal() as db:
|
||||
stmt = pg_insert(AppSetting).values(key=key, value=stored)
|
||||
stmt = stmt.on_conflict_do_update(index_elements=["key"], set_={"value": stored})
|
||||
await db.execute(stmt)
|
||||
await db.commit()
|
||||
logger.info("运行时配置 %s 已更新", key)
|
||||
|
||||
|
||||
async def clear_runtime_value(key: str) -> None:
|
||||
"""删除 DB 覆盖值,回落 .env(调用方需先校验 key 在白名单内)。"""
|
||||
async with AsyncSessionLocal() as db:
|
||||
row = await db.get(AppSetting, key)
|
||||
if row is not None:
|
||||
await db.delete(row)
|
||||
await db.commit()
|
||||
logger.info("运行时配置 %s 已清除覆盖", key)
|
||||
|
||||
|
||||
async def get_setting_origin(key: str) -> tuple[str, str]:
|
||||
"""返回 (origin, 当前生效值)。origin ∈ db / env / none。"""
|
||||
defn = SETTING_DEFS.get(key)
|
||||
db_value = None
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
row = await db.get(AppSetting, key)
|
||||
if row and row.value:
|
||||
value = crypto.decrypt_value(row.value) if defn and defn.sensitive else row.value
|
||||
return "db", value
|
||||
db_value = row.value
|
||||
except Exception:
|
||||
logger.warning("读取运行时配置 %s 来源失败,按环境变量处理", key)
|
||||
env_value = getattr(settings, key, "") or ""
|
||||
return ("env", env_value) if env_value else ("none", "")
|
||||
logger.warning("读取运行时配置 %s 来源失败(DB 故障),按环境变量处理", key)
|
||||
env_value = getattr(settings, key, "") or ""
|
||||
return ("env", env_value) if env_value else ("none", "")
|
||||
|
||||
if db_value is None:
|
||||
return ("none", "")
|
||||
|
||||
try:
|
||||
value = crypto.decrypt_value(db_value) if defn and defn.sensitive else db_value
|
||||
return "db", value
|
||||
except ValueError as e:
|
||||
# P1-G:解密失败(SECRET_KEY 不一致)在生产环境必须抛出,不得静默回落
|
||||
if settings.APP_ENV == "production":
|
||||
raise
|
||||
logger.warning("解密 %s 失败(非生产环境回落): %s", key, e)
|
||||
env_value = getattr(settings, key, "") or ""
|
||||
return ("env", env_value) if env_value else ("none", "")
|
||||
|
||||
|
||||
async def migrate_plaintext_sensitive_settings() -> int:
|
||||
|
||||
Reference in New Issue
Block a user