feat: resolver回退链接入platform_settings(三级回退/快照热刷新)

- app/platform/settings_store.py 新模块: lifespan 启动把 platform_settings
  全表快照进进程内存(load_snapshot/get/refresh,workers=1 单进程纪律);
  PUT /api/platform/settings 提交后 refresh 热刷新,回退链即时生效
- config_views email/ai 两组改三级回退: 租户槽位 → 平台设置快照
  (键名对齐平台设置页推荐键 ai.*/smtp.*)→ env settings(env 键名
  不变做最终兜底);push 无平台推荐键保持两级;smtp.port 自由 KV 容错
  归一(数字串转 int,脏值视同未设置落 env)
- conftest autouse 复位快照(resolver 直调测试隔离);新增
  test_platform_settings_fallback 六测(启动装载/三级链/端口容错/
  push 不接线钉死/PUT 即时生效)
- 平台设置页"仅存储"注记移除(CLAUDE.md 与前端页头同步改为已接线)
- 回归: 后端 627 passed(621+6)/ 前端 vitest 156 / build 零错误

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-14 10:10:52 +08:00
parent 7808f8a299
commit d734ed6d2a
9 changed files with 341 additions and 37 deletions
+9 -5
View File
@@ -23,9 +23,10 @@
resolve_email_settings/resolve_push_settings 的当前配置真发测试邮件 /
探测推送连通性——**push 探测绝不携带线索数据**(空 messages 批量),
两端点不落库不 bump 版本,成败如实返回(400 只用于"未配置"预检);
- GET/PUT /api/platform/settings:PlatformSetting CRUD,仅超管。本阶段只落
存储与接口(value 以 JSON Text 直存,读回还原类型);resolver 的平台默认
回退链接线归 Phase 4(当前仅存储,admin 平台设置页页头有注记)。
- GET/PUT /api/platform/settings:PlatformSetting CRUD,仅超管。value 以
JSON Text 直存(读回还原类型);PUT 提交后 settings_store.refresh 热刷新
进程内存快照——resolver 的平台默认回退链(Phase 4 Task 5A 接线,键名
ai.*/smtp.* 对齐 admin 平台设置页)即时生效,无需重启。
鉴权矩阵(spec §3):config 系 = require_tenant(slug)(租户全开——本租户
管理员可自助改配置);settings = require_super。409 门经 platform_user 连锁。
@@ -69,6 +70,7 @@ from app.platform.db import (
)
from app.platform.defaults import DEFAULT_CONFIG
from app.platform.routes.tenant_routes import _get_tenant_or_404, _tenant_guard
from app.platform import settings_store
from app.platform.tenant_source import _profile_snapshot
router = APIRouter(prefix="/api/platform/tenants", tags=["platform-config"])
@@ -602,7 +604,7 @@ async def test_push(slug: str, request: Request,
return {"slug": slug, **await _probe_push(cfg)}
# ---- 平台设置(仅超管;resolver 回退链接线归 Phase 4——当前仅存储) ------------------
# ---- 平台设置(仅超管;Phase 4 Task 5A 起 resolver 回退链已接线) ---------------------
def _decode_setting(raw: str):
@@ -632,7 +634,8 @@ async def list_settings(request: Request, user=Depends(require_super)):
@settings_router.put("")
async def put_setting(request: Request, user=Depends(require_super)):
"""upsert 单个设置项:key 1-64 字符;value 任意 JSON(json.dumps 入
Text,读回还原)。同 key 再写 = 整值替换。
Text,读回还原)。同 key 再写 = 整值替换。提交后 settings_store.refresh
热刷新内存快照——resolver 的平台默认回退链即时生效(Phase 4 Task 5A)。
密钥哨兵(Phase 3 Task 1):key 命中密钥名且值恰为 MASKED,或嵌套
dict 内密钥位置为 MASKED → 还原为现存同路径值(无库存 → None)。
@@ -661,4 +664,5 @@ async def put_setting(request: Request, user=Depends(require_super)):
else:
row.value = encoded
await session.commit()
await settings_store.refresh(engine)
return {"key": key, "value": _mask_setting_item(key, value)}