# 腾讯云 TIIA 图库（PlateOrder 图片上传 & 搜图）说明

## 背景与目标

本项目接入腾讯云 TIIA（Image and Video AI）的“图库”能力，用于：

- 将 `printing.PlateOrder` 的图片（通过 **ImageUrl**）上传到 TIIA 图库（CreateImage）
- 通过输入图片（URL 或 Base64）在图库中做相似图检索（SearchImage）

---

## 一、配置（settings + .env）

配置集中在 `flower/settings.py`，建议通过 `.env` 注入：

- `TENCENTCLOUD_SECRET_ID`
- `TENCENTCLOUD_SECRET_KEY`
- `TENCENTCLOUD_TOKEN`（可选，STS）
- `TENCENTCLOUD_TIIA_GROUP_ID`（当前默认 `168`）
- `TENCENTCLOUD_TIIA_REGION`（默认 `ap-guangzhou`）
- `TENCENTCLOUD_TIIA_ENDPOINT`（默认 `tiia.tencentcloudapi.com`）
- `TENCENTCLOUD_TIIA_PIC_NAME_PREFIX`（默认 `plate_order`）
- `TENCENTCLOUD_TIIA_QPS`（默认 `10`，用于限速）

说明：

- 本项目通过 `django-environ` 读取 `.env`（`Env.read_env(BASE_DIR / '.env')`）
- 本项目依赖 `tencentcloud-sdk-python`；在容器/服务启动方式不同的情况下，请确保运行使用的是同一套依赖环境（推荐 `uv run ...`）。

---

## 二、上传到图库（CreateImage）

### 2.1 基础上传函数（上传单张 ImageUrl）

位置：`api_v1/utils/tencentcloud_tiia.py`

- `upload_image_url_to_tencent_tiia(image_url, entity_id, tags=None, custom_content=None)`

行为（当前约定）：

- `GroupId` 固定从 settings 读取
- `EntityId` 由调用方传入（在 PlateOrder 场景中= `str(plate_order_id)`）
- `PicName` 自动生成（带 `TENCENTCLOUD_TIIA_PIC_NAME_PREFIX`）
- `ImageUrl` 使用传入的 `image_url`
- **`CustomContent` 默认写入完整 `image_url`**
- **`Tags` 默认不写入**（避免触发“标签值长度过长”）

> 注意：腾讯云 `SearchImage` 的返回结构默认不包含 `ImageUrl`；如果你希望在搜图结果里拿到可访问 URL，建议依赖 `CustomContent`（前提是腾讯云搜图返回里会回传该字段）。

### 2.2 PlateOrder 批量上传（读取 plate_image）

位置：`api_v1/utils/tencentcloud_tiia.py`

- `upload_plate_order_images_to_tencent_tiia(plate_order_id, rate_limiter=None)`

行为：

- 查询 `printing.PlateOrder(id=plate_order_id)`
- 从 `plate_image(JSONField)` 提取所有图片 URL/Path（兼容多种字段名与格式），并标准化为公网可访问 URL
- 对每张图片调用 `upload_image_url_to_tencent_tiia(...)`
- 单张失败不抛出，返回每张图片的结果列表（便于落库/排查）

---

## 三、定时任务（Celery Beat：每天 03:00 上传“昨天创建的订单”）

### 3.1 Task 本体

位置：`printing/tasks.py`

- `upload_yesterday_plate_order_images_to_tencent_tiia`

行为：

- 计算“昨天”的日期范围 `[start, end)`
- 遍历 `created_at` 落在该范围内的所有 `PlateOrder`
- 逐单上传 `plate_image` 中所有图片
- 遇错：记录失败并继续处理下一个订单
- 使用 `SimpleRateLimiter(qps=TENCENTCLOUD_TIIA_QPS)` 控制 QPS（默认 10）

### 3.2 Beat 调度配置

位置：`flower/settings.py`

- `CELERY_BEAT_SCHEDULE['daily_plate_order_tiia_image_upload']`
  - `task = 'printing.tasks.upload_yesterday_plate_order_images_to_tencent_tiia'`
  - `schedule = crontab(hour=3, minute=0)`

说明：

- 仅配置 schedule 不代表一定在跑；需要线上同时运行 **celery worker** + **celery beat**。

---

## 四、失败记录（可追踪/可重试）

位置：`printing/models.py`

- `PlateOrderTiiaUploadFailure`
  - `run_date / plate_order_id / error / details / attempts / last_attempt_at`
  - 唯一约束：`(run_date, plate_order_id)`，同日重复失败会累加 `attempts`

可在 Django Admin 中查看（`printing/admin.py` 已注册）。

---

## 四点五、MDY 开版暂存（MDYPlateOrderStaging）图片上传到图库（CreateImage）

### 4.5.1 目标数据源与字段（明道云暂存）

数据源：

- 模型：`api_v1.models.MDYPlateOrderStaging`
- 图片字段：`raw["62d52f4b8d2972284492dd27"]`（明道云开版主表字段“开版图”，Attachment）

单个附件项的 URL 取值策略（当前约定）：

- **优先**：`original_file_full_path`（你确认的主用字段）
- **候补**：`DownloadUrl`
- 不再使用：`preview_url`（通常带 token/过期参数，不适合作为长期回显 URL）

### 4.5.2 Service（上传单条暂存记录）

位置：`api_v1/mdy_plate_order_staging_tiia_upload.py`

- `upload_mdy_plate_order_staging_plate_images_to_tencent_tiia(staging, rate_limiter=None, dry_run=False)`
  - `EntityId`：使用 `staging.mdy_rowid`
  - 每张图调用：`api_v1.utils.tencentcloud_tiia.upload_image_url_to_tencent_tiia(image_url, entity_id, custom_content=image_url)`
  - 限流：`SimpleRateLimiter(qps=settings.TENCENTCLOUD_TIIA_QPS)`（默认 10）
  - 单张失败不影响其它图片，返回 `successes/failures` 明细

### 4.5.3 定时任务（Celery Beat）

位置：`api_v1/tasks.py`

- task：`upload_mdy_plate_order_staging_images_to_tencent_tiia(batch_size=200, dry_run=False)`
  - **游标**：`api_v1.models.DataSync`（`table_name=mdy_plate_order_staging_tiia_upload`），用 `last_rowid` 存储 staging 表自增 `id`
  - **失败落库**：`api_v1.models.MDYPlateOrderStagingTiiaUploadFailure`（同日同 rowid 去重，attempts 累加）

Beat 配置位置：`flower/settings.py`

- `CELERY_BEAT_SCHEDULE['daily_mdy_plate_order_staging_tiia_image_upload']`
  - 默认：**03:20** 运行
  - kwargs 默认值：
    - `batch_size=500`
    - `dry_run=False`

手动触发（示例）：

- `uv run python manage.py shell -c "from api_v1.tasks import upload_mdy_plate_order_staging_images_to_tencent_tiia; r=upload_mdy_plate_order_staging_images_to_tencent_tiia.delay(batch_size=50, dry_run=True); print(r.id)"`

### 4.5.4 迁移与 Admin

- 迁移：`api_v1/migrations/0010_mdy_plate_order_staging_tiia_upload_failure.py`
- Admin：`api_v1/admin.py` 已注册 `MDYPlateOrderStagingTiiaUploadFailure`

---

## 五、手动跑批（推荐：management command）

位置：`printing/management/commands/tiia_upload_plate_order_images.py`

常用：

- 全量跑批：`uv run python manage.py tiia_upload_plate_order_images --all`
- 跑昨天：`uv run python manage.py tiia_upload_plate_order_images --yesterday`
- 跑某天：`uv run python manage.py tiia_upload_plate_order_images --date 2026-01-15`
- 跑单个：`uv run python manage.py tiia_upload_plate_order_images --plate-order-id 123`
- 小批量验证：`uv run python manage.py tiia_upload_plate_order_images --all --limit 20`
- Dry-run：`uv run python manage.py tiia_upload_plate_order_images --all --dry-run`

说明：

- 同样遵守 `TENCENTCLOUD_TIIA_QPS` 的限速，并会落库失败记录。

---

## 六、搜图 API（简化版：SearchImage）

### 6.1 接口

- `POST /api/v1/tiia/search-image/`

参数（JSON body）：

- `imageUrl`：图片 URL（优先）
- `imageBase64`：Base64（可选，若无 imageUrl）
- `limit`：可选，1~100（默认腾讯云 10；本项目已透传）
- `offset`：可选，>=0
- `matchThreshold`：可选，0~100

### 6.2 返回

- 直接返回腾讯云 `SearchImage` 的原始响应 JSON

### 6.3 已知限制/注意点

- 腾讯云 `SearchImage` 返回结果通常以 `EntityId/PicName/Score/Tags/CustomContent` 为主，不一定直接提供可访问 `ImageUrl`
- 如果期望在搜图结果里拿到可访问 URL，请优先依赖上传时写入的 `CustomContent`

---

## 七、排障清单（常见问题）

- SDK 导入失败（`No module named 'tencentcloud'`）：
  - 确认使用 `uv run python ...`（确保解释器与依赖一致）
- 腾讯云返回“标签值长度过长”：
  - 不要默认写入 `Tags`；本项目已改为默认不写 `Tags`
- 任务未触发：
  - 检查是否同时运行 celery worker + celery beat
  - 检查 `CELERY_BROKER_URL / CELERY_RESULT_BACKEND` 是否可用

