feat: 头像上传(uuid重命名/魔数校验/2MB/配置版本管线)与三级解析链
- POST /api/platform/tenants/{slug}/brand/avatar(require_tenant):
multipart 收文件,魔数判定类型(\x89PNG/\xff\xd8\xff/RIFF....WEBP,
优先于扩展名——改后缀骗不过,存盘扩展名取自内容)、≤2MB(413,
upload.size 预检免拷贝)、服务端 uuid4 重命名(原始文件名不参与
路径),落 data/uploads/<slug>/(目录懒建,gitignore 已忽略)
- 走 Task 6 配置版本管线(复用 config_routes 内部 helper):读当前
快照 → brand 组只替换 avatar 字段 → validate_group →
write_config_version(comment=头像上传) → registry_reload 热生效
- /api/public/avatar 三级解析链:uploads → seed 资产 → 静态兜底;
Phase 1 文件名穿越防线(拒 / \ . ..)扩到 uploads 段
- registry.uploads_root:自 data_dir.parent 派生(生产=<repo>/data/
uploads;测试注入 tmp data_dir 时自动落 tmp 内,隔离免污染)
- 新依赖 python-multipart(multipart 表单解析)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -90,8 +90,8 @@ class TenantRuntime:
|
||||
|
||||
加载期就绪:config(TenantConfig:data=快照⊕出厂默认,含 sites)/
|
||||
db_path(该租户 SQLite 文件路径)/seed_dir(文件源=租户目录;DB 源
|
||||
None——avatar 解析链对其回退静态兜底)/compiled_wall(墙编译产物)/
|
||||
prompts(PromptProvider.from_config(snapshot["prompts"]),两源统一
|
||||
None——avatar 解析链的 seed 资产段对其跳过)/compiled_wall(墙编译
|
||||
产物)/prompts(PromptProvider.from_config(snapshot["prompts"]),两源统一
|
||||
入口——$DEFLECTION_POOL 话术池注入的单一消费口)。
|
||||
懒建:engine/session_factory(Registry 首次 resolve,ensure_schema 已跑)。
|
||||
manager(Task 5b)由装配线另行构造注入,不在本类持有。
|
||||
@@ -123,6 +123,12 @@ class TenantRegistry:
|
||||
self._by_slug: dict[str, TenantRuntime] = {}
|
||||
self._domain_index: dict[str, tuple[str, str]] = {}
|
||||
self._locks: dict[str, asyncio.Lock] = {}
|
||||
# 头像上传根目录(Task 7):生产 data_dir = <repo>/tenants,与
|
||||
# <repo>/data/ 是仓库根兄弟目录(spec 布局,.gitignore 已忽略 data/)
|
||||
# → 取 data_dir.parent 派生;测试注入 tmp data_dir 时上传自动落
|
||||
# tmp 内(DB 源夹具 data_dir=tmp/tenants → tmp/data/uploads),
|
||||
# 隔离免污染仓库目录。布局若变,只改这一处。
|
||||
self.uploads_root = self._data_dir.parent / "data" / "uploads"
|
||||
|
||||
# ---- 加载 -----------------------------------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user