Appearance
用户素材上传
本接口用于将本地图片 / 视频 / 音频上传至 iModelSpace COS,并自动登记至素材库。 登记后的素材可用于 POST /v1/videos/generations 请求体的 image / video 字段(参考图 / 参考视频)。
全链路鉴权方式为 Authorization: Bearer sk-xxx(亦支持 x-api-key 请求头), 不依赖浏览器登录态,适配 OpenAI / Anthropic SDK 调用场景。
端点索引
| 步骤 | 方法 | 路径 | 用途 |
|---|---|---|---|
| 1 | POST | /v1/files/upload_cos_signer | 申请 COS 直传预签名 URL 与 file_key,同步登记占位素材记录(status=uploading) |
| 2 | PUT | <presigned_url> | 将文件二进制流直接上传至腾讯云 COS |
| 3 | POST | /v1/files/upload_cos_confirm | 通过 file_key 触发 COS HeadObject 元数据核对,完成登记并返回完整素材对象 |
状态机说明(2026-08-30 新增)
第一步登记 第三步成功 重复 confirm
uploading ───► active ───► active (幂等,直接返回原对象)
│
└──► failed ◄─── 第三步 HeadObject 失败(允许重新走第一步)disabled 由管理员手动置位,与上传流程无关。
完整上传流程
第一步:申请预签名并登记占位素材
http
POST https://api.imodel.space/v1/files/upload_cos_signer
Authorization: Bearer {key}
Content-Type: application/json
{
"file_suffix": "jpg"
}请求参数
| 字段 | 是否必填 | 说明 |
|---|---|---|
file_suffix | 是 | 不带点的扩展名。支持 jpg / jpeg / png / gif / bmp / webp / mp4 / mov / avi / webm / mp3 / wav / flac / aac |
响应参数
仅展示第二步与第三步所需的字段:
json
{
"presigned_url": "https://imodel-1387889447.cos.ap-shanghai.myqcloud.com/users/0_1000/9/images/20260828153000_xxx.jpg?sign=...",
"file_key": "users/0_1000/9/images/20260828153000_xxx.jpg"
}| 字段 | 用途 |
|---|---|
presigned_url | 第二步用:将文件 PUT 至该 URL,有效期由 configs.Tencent.Cos.Duration 控制(默认 60 秒) |
file_key | 第三步用:原样透传至 /v1/files/upload_cos_confirm |
第二步:上传文件至 COS
请求 URL
直接使用第一步返回的 presigned_url,禁止对其中的 path、query 或 sign= 参数进行任何修改。 示例(完整 URL,含 sign= 参数):
https://imodel-1387889447.cos.ap-shanghai.myqcloud.com/users/0_1000/9/images/20260828153000_xxx.jpg?sign=...请求构造
| HTTP 字段 | 取值 |
|---|---|
| 方法 | PUT(必填) |
| 请求体 | 文件原始字节流(二进制,非 JSON,非 form-data) |
Content-Type | 可省略;若填写,须与实际文件类型一致(image/jpeg 等)。COS 服务端亦会自行推断 |
| 鉴权头 | 禁止添加 Authorization / x-api-key 等任何鉴权头,COS 鉴权信息已编码于 URL query string |
典型实现示例:
| 客户端 | 请求构造 |
|---|---|
| curl | curl -X PUT --upload-file ./cat.jpg "<presigned_url>" |
| 浏览器 fetch | fetch(presignedUrl, { method: 'PUT', body: file }) |
| Python requests | requests.put(presignedUrl, data=open('./cat.jpg','rb').read()) |
响应处理
| HTTP 状态码 | 含义 | 客户端处理 |
|---|---|---|
200 OK | 上传成功 | 进入第三步 confirm |
204 No Content | 上传成功(部分 COS 区域使用此状态码) | 进入第三步 confirm |
403 SignatureDoesNotMatch | 预签名过期或被篡改 | 重新调用第一步获取新预签名 |
403 AccessDenied | STS 策略拒绝(超出 prefix 边界) | 检查 file_key 是否位于 users/{group}/{uid}/ 子树 |
404 NoSuchKey | 桶或对象键不存在 | 桶配置异常,请联系管理员 |
4xx / 5xx 其他 | COS 标准错误 | 解析响应体中的 <Code> / <Message> 字段 |
成功响应体为空。失败响应体为 XML 格式,示例:
xml
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The provided signature does not match...</Message>
<RequestId>xxx</RequestId>
</Error>第三步:确认上传并完成登记
http
POST https://api.imodel.space/v1/files/upload_cos_confirm
Authorization: Bearer {key}
Content-Type: application/json
{
"file_key": "users/0_1000/9/images/20260828153000_xxx.jpg"
}请求参数
| 字段 | 是否必填 | 说明 |
|---|---|---|
file_key | 是 | 第一步响应中的 file_key,原样透传 |
处理流程
- 所有者校验:
file_key必须以users/{group}/{uid}/起始,防止越权登记他人素材。 - 状态机校验:
| 当前状态 | 处理 |
|---|---|
uploading | 默认路径,继续执行 HeadObject |
active | 幂等命中,直接返回原对象,不再重复调用 HeadObject |
failed | 返回 410 upload_failed,客户端应重新执行第一步 |
disabled | 返回 403 disabled_by_admin |
| 记录不存在 | 返回 404 asset_not_found |
- 调用 COS
HeadObject获取对象真实size/Content-Type/ETag/CRC64-ECMA。 - 回填字段:更新
assets表对应记录的size/mime_type/etag/crc64字段,status置为active,updated_at更新。 - 返回完整对象:
url = file_key,cdn_url由sasset.FillCDNURL装配。
响应参数
| 字段 | 说明 |
|---|---|
id | 素材主键,后续修改 / 删除均使用此 ID |
url | 相对路径(即 file_key) |
cdn_url | 公开 CDN 直链,可直接用于 image / image_url.url 字段 |
etag | COS 对象 ETag(MD5) |
crc64 | COS 对象 CRC64-ECMA 校验值 |
status | active 表示可用;failed 表示 HeadObject 失败,可重新调用 confirm;uploading 表示尚未执行第三步 |
size / mime_type | COS 服务端权威值(覆盖第一步的占位值) |
完整响应示例:
json
{
"id": 1024,
"user_id": 9,
"name": "20260828153000_xxx",
"source_type": "local",
"file_type": "image",
"url": "users/0_1000/9/images/20260828153000_xxx.jpg",
"cdn_url": "https://assets.imodel.emoly.cn/users/0_1000/9/images/20260828153000_xxx.jpg",
"size": 184320,
"mime_type": "image/jpeg",
"ext": "jpg",
"etag": "abc123...",
"crc64": "9876543210",
"status": "active",
"created_at": "2026-08-28T15:30:00+08:00",
"updated_at": "2026-08-28T15:30:00+08:00"
}错误码
| HTTP | code | 含义 | 客户端处理建议 |
|---|---|---|---|
| 400 | invalid_json | 请求体 JSON 格式错误 | 修正请求体 |
| 400 | missing_file_key | file_key 未填写 | 补充该字段 |
| 400 | missing_file_suffix / unsupported_file_suffix | 第一步参数错误 | 重新执行第一步 |
| 401 | invalid_api_key | API Key 无效或缺失 | 重新登录获取 sk |
| 403 | file_key_prefix_mismatch | file_key 不属于当前用户命名空间 | 禁止自行拼装 file_key,仅透传第一步响应值 |
| 403 | disabled_by_admin | 素材已被管理员禁用 | 联系管理员 |
| 404 | asset_not_found | 找不到对应占位记录 | 先执行第一步 |
| 404 | file_not_uploaded | COS 上不存在对应对象 | 先执行第二步 PUT 上传,再调用 confirm |
| - | (无) | 状态为 active 重复 confirm | 幂等,直接使用响应中的 cdn_url |
| 410 | upload_failed | 状态为 failed,前次 HeadObject 失败 | 重新执行第一步 |
| 500 | sts_get_failed / sign_url_failed | 第一步 STS / 预签名生成失败 | 重试 |
| 500 | db_insert_failed | 第一步占位记录登记失败 | 重试 |
| 502 | cos_query_failed | 第三步 HeadObject 失败(网络或桶配置异常) | 稍后重试 confirm |
错误响应遵循 OpenAI 标准格式 { "error": { "type", "code", "message" } },便于 OpenAI SDK 兼容解析。
限额与安全
- 预签名有效期:
presigned_url默认 60 秒(由configs.Tencent.Cos.Duration配置);第三步 confirm 无时间限制(使用长凭证) - 文件大小限制:由网关层(nginx / ALB)与 Go 层共同兜底,建议单文件 ≤ 20MB
- MIME 白名单:仅接受图片(jpg / png / gif / bmp / webp)、视频(mp4 / mov / avi / webm)、音频(mp3 / wav / flac / aac),其他后缀一律返回
400 - 命名空间隔离:
file_key由 UID 自动生成,STS 临时凭证策略限定users/{group}/{uid}/*,无法写入其他用户或任意路径 - CORS / Referer:浏览器跨域直传 COS 需在桶上配置 CORS;若 SDK 在后端服务运行则不受 CORS 限制
- 响应隐蔽:
cdn_url为公开 CDN 域名;响应体禁止返回 COS 上游 URL,防止前端日志或抓包获取后绕过鉴权直连 COS - 状态机防护:第三步严格执行
file_key前缀与user_id双重所有者校验,禁止使用他人上传过的file_key进行 confirm