Files
meida_backend/packages
xsl 367b610e93 feat(storage): COS presigned PUT URL endpoint for direct upload
POST /uploads/presign:场景/类型/大小三重校验 + 可审计 object_key 布局 +
SDK 隔离在 CosSigner Protocol 后(FR-SYS-03,T-P1-27)。

实现:packages/storage/schemas.py(PresignRequest/PresignResponse)、
packages/storage/service.py(CosSigner Protocol / QcloudCosSigner /
StorageService.build_presign,bucket 四别名映射到 cos_bucket_*,
object_key 写死 {scene}/{user_id}/{yyyyMMdd}/{uuid4hex}.{ext})、
apps/api/routers/uploads.py(POST /presign,CurrentUserId 认证 + ok 信封)。

测试:tests/unit/test_upload_presign.py 16 例全部经 FakeSigner 注入,
零网络、零 SDK client 构造;覆盖 key 布局逐段断言、三重校验统一 40010、
bucket 别名映射、白名单字符串解析(空串/空格容错)、配置错误分支、
schema 层 size>=1。门禁全绿:ruff check/format、mypy(宽松 48 文件 +
domain strict 7 文件)、lint-imports 4 契约、pytest 185 passed
(基线 169 + 新增 16)。

偏差说明:
1) SDK 实际无 qcloud_cos.method 模块,Method 按官方文档传字符串 "PUT";
   get_presigned_url 参数名为 Expired(非任务文档写的 Expire);配置类
   导出名为 CosConfig(非 CosS3Config)。按文档 §3 "以实际 SDK 签名为准"
   处理,已用假密钥端到端冒烟验证(纯本地签名计算,零网络)。
2) build_presign 增加可选 keyword-only 参数 now(默认 None=当前 UTC),
   供单测注入固定时钟(对齐 DailyBudgetGuard 的 clock 注入惯例),
   不改变文档签名的调用兼容性。
3) 两处防御分支文档未规定,均配了单测:场景 bucket 别名非法报
   CONFIG_ERROR 而非 40010(配置错误不应让客户端背 4xx);COS 密钥为空
   在 SDK 构造前报 CONFIG_ERROR,避免未处理的裸 500。
2026-08-14 16:17:49 +08:00
..

packages/ — 业务域与能力包

架构 §3.2:单体不等于混乱。这里的每个子目录都是一个边界,越界会被 CI 拦下。

目录含义

包 类型 内容
domain/ 纯逻辑层 领域模型与业务规则。无 IO、无框架、可纯单测
shared/ 基础设施 配置、日志、错误码、统一响应、DB、Redis、可观测
llm/ AI 能力 LLM 网关(已实现)
retrieval/ rules/ memory/ AI 能力 混合检索 / 规则引擎 / 记忆系统(占位)
compliance/ 业务域 统一合规出口(占位,CR-05/CR-06)
identity/ billing/ task/ agent/ 业务域 已有实现
profile/ report/ tryon/ kb/ notify/ wardrobe/ 业务域 占位

每个业务域包的标准结构:

packages/<域>/
├─ __init__.py
├─ README.md      这个域负责什么、不负责什么
├─ service.py     对外唯一入口。其他域只能调这里
└─ repo.py        SQL。只有本域可以 import

硬性规则(由 make importlint 机器校验)

  1. 禁止跨域直接查表。要用别的域的数据,调它的 service,不要 import 它的 repo,更不要在自己的 SQL 里 JOIN 别人的表。表名的域前缀(id_ / bl_ / tk_ / …)就是为将来拆库预留的边界。
  2. packages/domain/ 必须保持纯净。不许 import fastapi、sqlalchemy、 httpx、redis、celery,也不许 import packages/shared。放不进去说明 它不是领域逻辑,是应用逻辑,应该待在 service.py。
  3. compliance 不得依赖任何业务域。它必须能被任何模块安全调用。
  4. identity 不得依赖任何其他业务域。登录链路不能被业务拖累。
  5. AI 能力包(llm / retrieval / rules)不得依赖业务域,也不得 import apps。它们要能在 V2 原样拆成独立服务。

加一个新业务域时

  1. 建目录,写 README.md 说明职责与不负责的事。
  2. 在 pyproject.toml 的 [tool.importlinter] 契约里把它加进相应的 layers / forbidden_modules 列表 —— 新域默认没有约束,忘了加就等于没有边界。
  3. 领域规则先写进 packages/domain/<域>/ 并配单测,再写 service.py。
  4. 表加域前缀,走 Alembic,迁移必须向后兼容(NFR-MAINT-06)。