# AI 修图接口文档 ## 接口列表 | # | 方法 | 路径 | 用途 | |---|---|---|---| | 1 | `GET` | `/api/yf-handset-app/photog/travel-album/material-list` | 获取相册网格素材和预览初始数据 | | 2 | `GET` | `/api/yf-handset-app/photog/travel-album/material-info` | 获取/刷新当前原图及关联图 | | 3 | `GET` | `/api/yf-handset-app/photog/travel-album/ai-retouch-options` | 获取 AI 模板、选择规则和剩余额度 | | 4 | `POST` | `/api/yf-handset-app/photog/travel-album/ai-retouch` | 提交首次修图或重新修图任务 | | 5 | `GET` | `/api/yf-handset-app/photog/travel-album/ai-retouch-job-info` | 查询 AI 修图任务进度和结果 | | 6 | `POST` | `/api/yf-handset-app/photog/travel-album/delete-material` | 单张/批量删除素材及关联图 | ## 接口详情 ### 获取相册素材列表 ```http GET /api/yf-handset-app/photog/travel-album/material-list?user_equity_travel_id=88&limit=30&cursor=opaque_cursor&sort=created_at_desc ``` | 参数 | 必填 | 说明 | |---|---|---| | `user_equity_travel_id` | 是 | 相册 ID | | `limit` | 否 | 默认 30,最大 100 | | `cursor` | 否 | 服务端返回的不透明游标,首页不传 | | `sort` | 否 | `created_at_desc` 或 `created_at_asc` | | `material_type` | 否 | `original`、`cover`;不传表示全部 | 响应: ```json { "code": 100000, "msg": "success", "data": { "items": [ { "id": "2031", "user_equity_travel_id": "88", "material_type": "original", "original_asset": { "id": "asset_8001", "url": "https://cdn.example.com/albums/88/IMG_1024.jpg", "thumbnail_url": "https://cdn.example.com/albums/88/IMG_1024_thumb.jpg", "file_name": "IMG_1024.jpg", "mime_type": "image/jpeg", "file_size": 4821931, "width": 4032, "height": 3024, "version": 1, "created_at": "2026-08-09T01:00:00.000Z" }, "display_status": "uploaded", "variant_slots": [], "revision": 7, "created_at": "2026-08-09T01:00:00.000Z", "updated_at": "2026-08-11T07:30:18.000Z" } ], "next_cursor": "next_opaque_cursor", "has_more": true } } ``` - 只返回网格顶层素材:原图和独立封面。 - 原图如存在关联图,`variant_slots` 包含预览页 Tab 初始数据。 - 建议使用游标分页,因为 AI 任务完成时可能实时插入封面素材。 ### 获取/刷新单个原图项目 ```http GET /api/yf-handset-app/photog/travel-album/material-info?user_equity_travel_id=88&material_id=2031 ``` 查询参数 `user_equity_travel_id` 和 `material_id` 均必填。响应的 `data` 直接返完整 `material_project`,字段与列表 Item 完全一致。该接口供预览页刷新和提交后定向更新使用。 如 `material_id` 已删除,返回 `404 MATERIAL_NOT_FOUND`。 ### 获取 AI 修图选项 ```http GET /api/yf-handset-app/photog/travel-album/ai-retouch-options?user_equity_travel_id=88&scope=batch&source_count=4 ``` | `scope` | 使用场景 | |---|---| | `batch` | 网格多选 AI 修图 | | `all_variants` | 预览页无 Tab,或当前是原图 Tab | | `refined_only` | 当前是精修后 Tab | | `atmosphere_only` | 当前是氛围感 Tab | `user_equity_travel_id`、`scope` 和 `source_count` 均必填。`source_count` 用于计算封面模板是否显示和必选。 响应: ```json { "code": 100000, "msg": "success", "data": { "scope": "batch", "source_count": 4, "groups": [ { "type": "refined", "title": "原图精修", "required": true, "selection_mode": "single", "quota_cost": { "mode": "per_source", "units": 1 }, "templates": [ { "id": "tpl_refined_12", "name": "清透精修", "preview_url": "https://cdn.example.com/templates/refined_12.jpg", "before_url": "https://cdn.example.com/templates/refined_12_before.jpg", "after_url": "https://cdn.example.com/templates/refined_12_after.jpg", "enabled": true } ] }, { "type": "atmosphere", "title": "氛围感修图", "required": false, "selection_mode": "single", "quota_cost": { "mode": "per_source", "units": 1 }, "templates": [ { "id": "tpl_atmosphere_06", "name": "暖阳", "preview_url": "https://cdn.example.com/templates/atmosphere_06.jpg", "before_url": "https://cdn.example.com/templates/atmosphere_06_before.jpg", "after_url": "https://cdn.example.com/templates/atmosphere_06_after.jpg", "enabled": true } ] }, { "type": "cover", "title": "封面风格模板", "required": true, "selection_mode": "single", "quota_cost": { "mode": "per_job", "units": 0 }, "templates": [ { "id": "tpl_cover_03", "name": "旅行画册", "preview_url": "https://cdn.example.com/templates/cover_03.jpg", "before_url": "https://cdn.example.com/templates/cover_03_before.jpg", "after_url": "https://cdn.example.com/templates/cover_03_after.jpg", "enabled": true } ] } ], "quota": { "unit": "retouch_time", "remaining": 12, "reserved": 3, "display_text": "剩余 12 次", "updated_at": "2026-08-11T07:29:58.000Z" }, "limits": { "minimum_source_count": 1, "maximum_source_count": 50, "cover_minimum_source_count": 4 } } } ``` 设计要点: - 不再让 App 传 `scenic_id`,后端从相册归属景区获取可用模板。 - 由后端返回 `required` 和可见分组,避免多端各写一套“4 张显示封面”规则。 - `preview_url` 用于模板卡片缩略图;`before_url` 与 `after_url` 必须是同尺寸、同构图的配对图片,供客户端滑动对比。 - 客户端可默认选中必选分组的第一个可用模板,可选分组默认不选。 - 此接口用于 UI 配置;提交时后端仍必须根据真实素材 ID 重新校验。 - 同一响应返回当前用户的剩余可用额度和每类输出的额度单价,弹窗无需再发起第二个额度请求。 - `quota.remaining` 是扣除其他未完成任务已预占数量后、当前立即可用的额度;弹窗底部使用此值显示“剩余 N 次”。 - 额度文案位于模板选择弹窗的固定底部区域,不随中间模板列表滚动。 - `quota.reserved` 是当前用户其他未完成任务的预占额度,仅用于解释账户状态。 - 客户端预估本次消耗时,对已选输出求和:`per_source` 类型为 `source_count * units`,`per_job` 类型为 `units`。封面始终为 0。 - 本文档默认精修和氛围感每生成一张各消耗 1 次。如实际商业规则变更,后端只需调整 `quota_cost`,客户端不需改接口。 ### 创建 AI 修图任务 ```http POST /api/yf-handset-app/photog/travel-album/ai-retouch Idempotency-Key: 9A7820CE-78AB-45C6-9E0F-5DF268995379 Content-Type: application/json ``` #### 5.4.1 网格批量修图 ```json { "user_equity_travel_id": "88", "scope": "batch", "source_material_ids": ["2031", "2032", "2033", "2034"], "outputs": [ { "type": "refined", "template_id": "tpl_refined_12" }, { "type": "atmosphere", "template_id": "tpl_atmosphere_06" }, { "type": "cover", "template_id": "tpl_cover_03" } ] } ``` #### 5.4.2 预览页原图 Tab ```json { "user_equity_travel_id": "88", "scope": "all_variants", "source_material_ids": ["2031"], "outputs": [ { "type": "refined", "template_id": "tpl_refined_15" }, { "type": "atmosphere", "template_id": "tpl_atmosphere_09" } ] } ``` 如用户未选氛围感模板,`outputs` 中不传 `atmosphere`。缺失表示“本次不处理”,不表示删除现有氛围感图。 #### 5.4.3 只重新精修 ```json { "user_equity_travel_id": "88", "scope": "refined_only", "source_material_ids": ["2031"], "outputs": [ { "type": "refined", "template_id": "tpl_refined_18" } ] } ``` #### 5.4.4 只重新生成氛围感 ```json { "user_equity_travel_id": "88", "scope": "atmosphere_only", "source_material_ids": ["2031"], "outputs": [ { "type": "atmosphere", "template_id": "tpl_atmosphere_11" } ] } ``` #### 5.4.5 提交校验矩阵 | `scope` | 原图数 | 精修 | 氛围感 | 封面 | |---|---:|---|---|---| | `batch` | 1 至 50 | 必选 | 可选 | 少于 4 张禁止;4 张及以上必选 | | `all_variants` | 必须为 1 | 必选 | 可选 | 禁止 | | `refined_only` | 必须为 1 | 必选 | 禁止 | 禁止 | | `atmosphere_only` | 必须为 1 | 禁止 | 必选 | 禁止 | 另外: - `source_material_ids` 必须去重,且全部为 `original` 类型。 - 模板必须启用、类型匹配,且适用于相册所属景区。 - 后端必须使用提交时的最新额度计费规则重新计算消耗,不信任客户端的预估数值。 - 封面输出不论首次生成、重试或任务结果如何,额度消耗始终为 0。 - 每个输出槽位均采用 `replace_on_success`:成功后替换旧版本,失败保留旧版本。 - 批量选中已有 AI 结果的原图时,允许重新生成,只覆盖本次 `outputs` 列出的类型。 - 同一原图、同一槽位已有 `queued/processing` 任务时,整次请求返回 `409 RETOUCH_TARGET_BUSY`,并列出冲突项。 #### 5.4.6 提交响应 HTTP 状态码:`202 Accepted` ```json { "code": 100000, "msg": "AI 修图任务已提交", "data": { "job": { "id": "job_01K2E4", "user_equity_travel_id": "88", "scope": "all_variants", "status": "queued", "source_material_ids": ["2031"], "progress": { "total": 2, "queued": 2, "processing": 0, "succeeded": 0, "failed": 0 }, "created_at": "2026-08-11T07:30:15.123Z", "started_at": null, "finished_at": null }, "quota_reservation": { "before_remaining": 12, "reserved_units": 2, "remaining_after_reservation": 10, "cover_units": 0 }, "affected_projects": [ { "id": "2031", "user_equity_travel_id": "88", "material_type": "original", "original_asset": { "id": "asset_8001", "url": "https://cdn.example.com/albums/88/IMG_1024.jpg", "thumbnail_url": "https://cdn.example.com/albums/88/IMG_1024_thumb.jpg", "file_name": "IMG_1024.jpg", "mime_type": "image/jpeg", "file_size": 4821931, "width": 4032, "height": 3024, "version": 1, "created_at": "2026-08-09T01:00:00.000Z" }, "display_status": "pending", "variant_slots": [ { "type": "refined", "asset": null, "latest_generation": { "job_id": "job_01K2E4", "status": "queued", "is_replacement": false, "template": { "id": "tpl_refined_15", "name": "自然精修" }, "requested_at": "2026-08-11T07:30:15.123Z", "started_at": null, "finished_at": null, "error": null } }, { "type": "atmosphere", "asset": null, "latest_generation": { "job_id": "job_01K2E4", "status": "queued", "is_replacement": false, "template": { "id": "tpl_atmosphere_09", "name": "暖阳" }, "requested_at": "2026-08-11T07:30:15.123Z", "started_at": null, "finished_at": null, "error": null } } ], "revision": 8, "created_at": "2026-08-09T01:00:00.000Z", "updated_at": "2026-08-11T07:30:15.123Z" } ] } } ``` 必须返回 `job.id`、`quota_reservation` 和更新后的 `affected_projects`。客户端用 `remaining_after_reservation` 立即更新剩余额度,并更新 Cell 状态;已有 `asset` 的重修 Tab 可同步显示处理态,首次生成且 `asset = null` 的槽位不显示 Tab。 ### 查询 AI 修图任务 ```http GET /api/yf-handset-app/photog/travel-album/ai-retouch-job-info?user_equity_travel_id=88&job_id=job_01K2E4 ``` 响应: ```json { "code": 100000, "msg": "success", "data": { "id": "job_01K2E4", "user_equity_travel_id": "88", "scope": "batch", "status": "partially_succeeded", "source_material_ids": ["2031", "2032", "2033", "2034"], "progress": { "total": 9, "queued": 0, "processing": 0, "succeeded": 8, "failed": 1 }, "quota_settlement": { "status": "settled", "reserved_units": 8, "consumed_units": 7, "released_units": 1, "cover_units": 0 }, "targets": [ { "source_material_id": "2031", "output_type": "refined", "status": "succeeded", "result_asset_id": "asset_9201", "error": null }, { "source_material_id": "2032", "output_type": "atmosphere", "status": "failed", "result_asset_id": null, "error": { "code": "AI_PROVIDER_TIMEOUT", "message": "AI 服务处理超时,请重试", "retryable": true } }, { "source_material_id": null, "output_type": "cover", "status": "succeeded", "result_material_id": "cover_301", "error": null } ], "created_at": "2026-08-11T07:30:15.123Z", "started_at": "2026-08-11T07:30:18.000Z", "finished_at": "2026-08-11T07:33:00.000Z" } } ``` | 整体状态 | 含义 | |---|---| | `queued` | 已受理,尚无子任务开始 | | `processing` | 至少一个子任务正在执行,且尚未全部完成 | | `succeeded` | 所有子任务成功 | | `partially_succeeded` | 子任务全部结束,且既有成功也有失败 | | `failed` | 子任务全部结束,没有任何成功结果 | | `canceled` | 任务因素材删除或管理操作被取消 | 额度结算规则: - 任务提交成功时先预占所有非封面子任务的额度,封面不进入预占。 - 每个精修/氛围感子任务成功后,将对应预占转为实际消耗。 - 子任务失败或取消时释放对应预占。因此部分成功示例中,预占 8 次、成功消耗 7 次、失败退回 1 次。 - `quota_settlement.status` 取值为 `reserved | partially_settled | settled`。 客户端如只关心当前预览图,优先调用单素材接口;本接口主要用于批量进度、问题排查和任务详情。 ### 批量删除素材 ```http POST /api/yf-handset-app/photog/travel-album/delete-material Content-Type: application/json ``` 请求: ```json { "user_equity_travel_id": "88", "material_ids": ["2031", "2032", "cover_301"] } ``` 为兼容旧客户端,原单张请求体继续有效: ```json { "id": "2031" } ``` 新请求使用 `user_equity_travel_id + material_ids`,旧请求使用 `id`,两组参数不同时传入。 响应: ```json { "code": 100000, "msg": "删除成功", "data": { "deleted_material_ids": ["2031", "2032", "cover_301"], "deleted_variant_asset_ids": ["asset_9101", "asset_9102"], "canceled_job_ids": ["job_01K2E4"], "oss_cleanup_status": "scheduled" } } ``` 语义: - 传入原图 ID:级联软删除精修图、氛围感图,并取消该原图的未完成子任务。 - 传入独立封面 ID:只删除该封面素材。 - 删除原图不级联删除以它为输入之一的已生成封面。 - 数据库业务删除采用一个事务,默认全部成功或全部失败。 - 任一 ID 不属于当前相册或无权删除时,整次请求失败。已删除的 ID 可视为幂等成功。 - OSS 文件清理异步执行,不影响业务删除成功。