文件概述
图片、语音、视频和文件消息中的文件,用户和群的头像、聊天室的封面,以及群文件,都可以上传到本服务保存。文件内容由上传方直接写入对象存储,下载时也直接从对象存储(或 CDN)读取,不经过 IM 服务的接口。
每个文件在上传时指定用途,用途决定大小上限、谁能下载和保留多久。上传完成后得到文件的地址:
- 文件地址:消息附件和群文件的固定地址,写进消息的
body中。它本身不能直接下载,要由本应用登录的用户或你的服务端换取短期有效的下载地址,见文件地址; - 公开地址:头像和聊天室封面的地址,任何人都可以直接访问,填进用户、群或聊天室的资料中,见公开地址。
本节的接口:
客户端上传
用户在客户端发送图片、更换头像、上传群文件时,由客户端用 User Token 直接调用与 OpenAPI 相同的上传接口(路径前缀为 /client/v1/media,不能指定 owner,并按用户限制频率),SDK 发布后由 SDK 封装。本节只介绍你的服务端通过 OpenAPI 调用的接口。
文件的用途
用途 purpose | 内容 | 允许的类型 kind | 大小上限 | 谁可以下载 | 保留多久 |
|---|---|---|---|---|---|
attachment 消息附件 | 图片、语音、视频、文件消息中的文件 | image、voice、video、file | 运行策略 max_upload_bytes,默认 100 MB;图片另不超过 20 MB,语音另不超过 5 MB | 你的服务端;本应用任何登录的用户(持有文件地址即可) | 从上传时起 attachment_retention_days 天,默认 30 天 |
user_avatar 用户头像 | 用户资料中的头像 | image | 5 MB | 任何人(公开地址) | 被使用期间一直保留,见下文 |
group_avatar 群头像 | 群资料中的头像 | image | 5 MB | 任何人(公开地址) | 同用户头像 |
chatroom_avatar 聊天室封面 | 聊天室资料中的封面 avatar_url,见封面 | image | 5 MB | 任何人(公开地址) | 同用户头像;聊天室解散 7 天后删除 |
group_file 群文件 | 群的共享文件 | image、video、file | 同消息附件 | 你的服务端;群的当前成员 | 默认不过期,可以用 group_file_retention_days 设置天数;群解散后删除 |
运行策略中的各项见运行策略。此外:
- 文件名:创建上传时的
name,1 到 255 个字符,规则与文件消息的name相同(不能只有空白,不能包含/、\、控制字符和改变文字方向的字符)。类型为file的文件和群文件必须填写,下载时作为保存的文件名。 - 群文件:每个群最多 1000 个群文件。运行策略
group_file_enabled关闭时,群成员不能在客户端上传群文件,你的服务端仍然可以上传。客户端只有当前的群成员能查看群文件列表和下载,离开群之后不能再下载。 - 头像(包括聊天室封面):
- 上传后 24 小时内没有被设置到用户、群或聊天室的资料中的,自动删除;
- 资料中的头像被换成其他地址或清空后,原来的头像在 7 天后删除,留给还缓存着旧资料的客户端;
- 用户头像只能由它的所属用户(上传时的
owner)使用,设置到其他用户的资料中不算使用;群头像由第一个使用它的群使用,聊天室封面由第一个使用它的聊天室使用; - 用户被删除后,他的用户头像随即删除;群解散后,群头像保留;聊天室解散 7 天后,封面被删除。
- 归属:文件属于上传它的应用,其他应用的用户和服务端都无法下载。
文件地址
消息附件和群文件完成上传后得到文件地址,图片另有缩略图地址:
https://im.example.com/media/v1/f/{file_id}/{secret}
https://im.example.com/media/v1/f/{file_id}/{secret}/thumbfile_id是文件 ID,查询文件、删除文件等接口用它指定文件;secret是 22 个字符的随机密钥,无法猜出。- 地址的主机由服务的部署决定,不一定与 OpenAPI 的接入地址相同。请原样使用接口返回的
url和thumbnail_url,不要自己拼接或修改,带上查询参数的地址也不再被识别为文件地址。 - 文件地址固定不变,可以长期保存、写进消息、作为客户端缓存的键。文件过期或被删除后,地址仍然不变,只是不能再下载。
文件地址不能直接下载。直接打开文件地址返回 401 unauthenticated,App Token 也不能直接请求它。下载时要先换取下载地址:
你的服务端:调用换取下载地址,一次最多 100 个地址,可以下载本应用的全部附件和群文件。
客户端:用户用 User Token 换取(与 OpenAPI 相同的批量接口
POST /client/v1/media/download-urls),或者带着 User Token 直接请求文件地址GET /media/v1/f/{file_id}/{secret}(缩略图为…/thumb),服务端校验后返回302,跳转到下载地址:httpGET /media/v1/f/99606093402996736/DC0neNm6HBbxGW9bN6g_iA/thumb HTTP/1.1 Host: im.example.com Authorization: Bearer <User Token> HTTP/1.1 302 Found Cache-Control: no-store Location: https://storage.example.com/im-private/t99604914749046784/a99604957103128576/m/99606093402996736_thumb?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20261002T203000Z&X-Amz-Expires=5400&X-Amz-SignedHeaders=host&X-Amz-Signature=52ca87a0...移动端的图片加载库可以这样使用,并按不变的文件地址缓存图片。跟随跳转时不能把
Authorization请求头带到下载地址:对象存储会因请求同时带有签名参数和Authorization而拒绝(返回400),User Token 也会泄露给存储服务。多数 HTTP 库跳转到其他主机时会自动去掉它。文件地址不返回跨域响应头,网页中请用换取接口。
谁可以换取:
| 调用方 | 消息附件 | 群文件 |
|---|---|---|
| 你的服务端(OpenAPI) | 全部 | 全部,不检查群成员身份 |
| 客户端(本应用登录的用户) | 任何用户,不检查他是否在附件所在的会话中 | 当前的群成员,其他用户得到 not_group_member |
附件不按会话判断权限:同一个地址会随转发出现在其他会话中,而地址中的密钥无法猜出,持有地址的用户本来就看到过这条消息。客户端换取下载地址(包括直接请求文件地址)每个用户每分钟最多 600 个文件,超出时得到 429 rate_limited,details.reason 为 download_rate。
换取的结果:可用的文件得到下载地址;文件已过期或已被删除的得到 file_expired(直接请求文件地址时为 410),被屏蔽的得到 file_blocked(403),不是本应用的文件、密钥不对或还在上传中的得到 not_found(404)。
只接受本服务的地址
应用在运行策略中开启 media_url_only 后,用户在客户端发送图片、语音、视频和文件消息时,地址只能是本服务的文件地址(或 media_allowed_hosts 中的主机);客户端设置的用户头像、群头像和聊天室封面只能是本应用的头像公开地址(或 media_allowed_hosts 中主机的 https 地址)。你的服务端写入的地址不受这项限制,见运行策略。
缩略图地址
图片类型的消息附件和群文件有缩略图地址,即文件地址加 /thumb,指向长边不超过 480 像素的缩略图,用于在消息列表中显示。原图本身不超过 480 像素且不超过 100 KB 的,缩略图就是原图。其他类型的文件没有缩略图,请求 /thumb 得到 not_found。
公开地址
头像(用户头像、群头像和聊天室封面)完成上传后得到公开地址,任何人不需要登录就能直接访问:
https://cdn.example.com/t99604914749046784/a99604957103128576/u/99606376459796480_thhts5lb.jpg头像要在会话列表、好友列表、群成员列表中大量显示,每个都换取一次下载地址的代价太大,所以保存在公开的存储中。公开地址同样固定不变,把它填进用户、群或聊天室资料的 avatar_url 即可,响应头带 Cache-Control: public, max-age=86400,CDN 和客户端可以缓存一天。公开地址不需要也不能换取下载地址。
上传流程
上传分三步,见上传文件:
- 创建上传:提交用途、类型、大小和文件名(群文件另带群 ID),服务端检查用途和类型、大小上限和应用的存储额度,返回文件 ID 和上传地址。
- 上传内容:直接向对象存储上传文件内容。
- 单次上传:不超过 32 MB 的文件,按返回的上传地址一次
PUT写入; - 分片上传:超过 32 MB 的文件,每 16 MB 一片(最后一片可以更小),最多 128 片。按需索取各片的上传地址,各片可以并行、按任意顺序上传;网络中断后可以查询已上传的分片,只补传缺少的分片。
- 单次上传:不超过 32 MB 的文件,按返回的上传地址一次
- 完成上传:服务端核对上传的大小、按文件内容识别格式、处理图片,通过后文件变为可用,返回文件对象,其中有文件地址(或头像的公开地址)、缩略图地址、宽高和大小。
是单次上传还是分片上传由服务端按 size 决定。头像、图片和语音的大小上限都不超过 20 MB,总是单次上传;只有视频和 file 类型的文件可能分片上传。
- 有效期:单次上传要在创建后 1 小时内完成,分片上传 24 小时内;每片的上传地址 1 小时内有效,过期后可以重新索取。到期仍未完成的上传自动作废,已上传的内容随后删除。不再需要时可以主动取消上传。
- 重复提交:完成上传可以重试,已完成的返回第一次的结果。创建上传不去重,每次都会创建一个新的文件 ID,没有完成的上传过期后自动删除,不计入存储用量。
- 只有创建者能继续:通过 OpenAPI 创建的上传,只能由同一个应用通过 OpenAPI 继续上传、完成或取消(可以使用该应用的任何 App Token);客户端和控制台创建的上传,OpenAPI 的上传接口查询不到。
- 上传中的文件不能下载,也不计入存储用量。
格式检查与图片处理
完成上传时,服务端读取文件开头的内容识别格式,不采用创建上传时声明的 content_type,也不看文件名的后缀。各类型允许的格式(括号中为识别后记录的 content_type):
类型 kind | 允许的格式 |
|---|---|
image | JPEG(image/jpeg)、PNG(image/png)、GIF(image/gif)、WebP(image/webp) |
voice | AAC(audio/aac)、M4A 等 MP4 容器(audio/mp4)、MP3(audio/mpeg)、AMR(audio/amr)、3GP(audio/3gpp)、Ogg(audio/ogg)、WAV(audio/wav)、WebM(audio/webm) |
video | MP4(video/mp4)、MOV(video/quicktime)、WebM(video/webm)、3GP(video/3gpp) |
file | 任意格式。能识别的记为对应的格式,如 PDF 为 application/pdf;其他按内容判断,如纯文本为 text/plain;无法判断的为 application/octet-stream |
格式不符合类型的,完成上传失败,返回 400 invalid_argument,details.reason 为 unsupported_type,details.content_type 为识别出的格式,要重新创建上传。HEIC 等其他格式的图片请先转换为 JPEG,或作为 file 上传;SVG、HTML 这类可以包含脚本的内容只能作为 file 上传。
图片(类型为 image 的消息附件和群文件):
- 宽乘高不能超过 5000 万像素,否则完成上传失败(
image_too_large);无法解码的图片同样失败(invalid_image)。 - 去掉 EXIF、XMP 等元数据(其中可能有拍摄地点、设备型号),包括 PNG 的文本块和 GIF 的注释,保留方向信息、颜色配置和动图的循环次数。图片不重新压缩,画质不变,但保存后的大小可能比原文件略小,请以完成上传返回的
size为准。 width、height是按方向信息旋转之后的显示宽高。- 生成长边不超过 480 像素的缩略图,格式为 JPEG,有透明像素的为 PNG;GIF 取第一帧。原图长边不超过 480 像素且不超过 100 KB 的,以及动画 WebP,缩略图就是原图。
- 计算保存内容的 SHA-256,即文件对象中的
sha256。
头像:缩放为长边不超过 640 像素(更小的不放大),重新编码为 JPEG,有透明像素的为 PNG;GIF 取第一帧;去掉全部元数据。上传的原图不保留,文件对象中的 size、width、height 和 sha256 都是处理后的值。
语音、视频和 file 类型的文件(包括作为 file 上传的图片)不做任何处理:不转码、不生成封面、不去除元数据(如视频中的拍摄地点),也不检测病毒。需要时请在上传前处理,视频的封面请截取一帧作为图片另外上传。
下载时的响应头:下载地址返回识别出的 Content-Type。图片、语音和视频可以在浏览器中直接显示或播放;file 类型的文件和全部群文件带 Content-Disposition: attachment 和原文件名,浏览器会下载而不是打开。
下载地址
换取得到的下载地址是对象存储(或 CDN)的签名地址,直接 GET 即可,不需要也不能带 Authorization 请求头,支持 Range 请求(断点续传、视频拖动进度)。
- 有效期 60 到 90 分钟:签名时刻按 30 分钟对齐,有效 90 分钟,到期时间见换取结果中的
expires_at。同一个文件在同一个 30 分钟内换到的地址完全相同,浏览器和 CDN 可以按地址缓存。 - 请在到期前重新换取,不要保存下载地址,也不要把它写进消息:消息中应该保存不变的文件地址。
- 缩略图地址换到的是缩略图的下载地址。
- 下载地址泄露后,他人在到期前也能用它下载,请不要转发。
过期与删除
文件在以下情况下被删除:
| 情况 | 删除的文件 |
|---|---|
| 超过保留期 | 上传超过 attachment_retention_days 天(默认 30 天)的消息附件;设置了 group_file_retention_days 时,超过天数的群文件。保留期从上传时算起,不是从发送消息时算起。文件对象中的 expires_at 按应用当前的保留天数算出,到期后由后台任务删除,可能比它晚一些(通常不超过 1 小时);调短保留天数后,按新的天数已经过期的文件随即删除 |
| 头像不再使用 | 上传后 24 小时内没有使用的头像;被替换 7 天后的头像;聊天室解散 7 天后的封面 |
| 删除用户 | 他的用户头像。应用开启了删除用户时擦除消息(erase_messages_on_user_delete)的,所属用户为他的消息附件和群文件也一起删除 |
| 解散群 | 这个群的全部群文件,群头像保留 |
| 主动删除 | 你的服务端删除文件、在控制台删除,或客户端的群主、管理员、上传者删除群文件 |
文件被删除后,消息本身仍然保留,body 中的地址也不变;撤回消息不会删除附件。换取下载地址时得到 file_expired,直接请求文件地址返回 410,客户端应显示“文件已过期”。删除的记录再保留约 30 天,之后换取得到 not_found,客户端按同样的方式显示即可。
删除前已经换取的下载地址,在文件内容从存储中删除之前(约 10 分钟)仍可能使用;部署了 CDN 时,CDN 上已缓存的内容最长可用到下载地址到期。头像的公开地址在 CDN 上的缓存最长 1 天后失效。
违规文件的屏蔽
违规的文件会被屏蔽:内容安全的事后审核判定违规,或审核人员确认违规时,按平台规则或你的规则屏蔽文件;平台也可以直接屏蔽。被屏蔽后:
- 不能再换取下载地址,换取时得到
file_blocked,直接请求文件地址返回403,缩略图同样如此;已经换取的下载地址通常在 1 分钟内失效(部署了 CDN 时,CDN 上已缓存的内容最长可用到下载地址到期)。头像从公开地址移除(通常 1 分钟内),CDN 上的缓存最长 1 天后失效; - 文件内容作为证据保留 180 天,之后删除;期间可以解除屏蔽,解除后文件恢复可用(已超过保留期的随后按保留期删除)。按你的规则屏蔽的(
block_rule_source为tenant),你的审核人员把审核记录改为无违规时自动解除;按平台规则屏蔽的(platform)只能由平台解除; - 你不能删除被屏蔽的文件(返回
403 file_blocked),它也不再计入存储用量; - 屏蔽本身不修改引用它的消息和资料。内容安全处置时会同时撤回消息、从资料中清除被屏蔽的头像;其他情况需要时请自行撤回消息或修改资料。
查询文件时,被屏蔽的文件 status 为 blocked,并给出屏蔽的时间、原因和依据的规则。对平台的屏蔽如有异议请联系我们。
存储用量与额度
用量是应用中可用的文件(status 为 active)占用的空间和文件数,按用途分别统计,图片包括缩略图。上传中、已删除和被屏蔽的文件不计入。用查询存储用量获取,控制台的“文件存储”中也可以查看。
额度取平台为每个应用设置的存储配额(默认 1024 GB,1 GB 按 230 字节计)与应用套餐的存储容量中较小的一个,见套餐与账单。创建上传时,已用的空间加上这次的 size 超过额度的,返回 409 limit_exceeded,message 为“应用的存储额度已满”,details.reason 为 storage_quota,details.limit_bytes 和 details.used_bytes 分别为额度和已用的字节数。
- 额度是软上限:同时进行的多个上传都在创建时检查,最终的用量可能略超额度;
- 超过额度只拒绝新的上传,不影响下载,也不删除已有的文件。请删除不再需要的文件,或缩短附件的保留天数;需要更大的额度时请更换套餐或联系我们。
数据结构
文件对象
完成上传、查询文件、查询群文件列表时返回文件对象:
| 字段 | 类型 | 说明 |
|---|---|---|
file_id | String | 文件 ID |
purpose | String | 用途:attachment、user_avatar、group_avatar、chatroom_avatar、group_file,见文件的用途 |
kind | String | 类型:image、voice、video、file |
status | String | 状态:pending 上传中 / active 可用 / blocked 已屏蔽 / deleted 已删除(包括上传失败、取消和过期的上传) |
url | String | 消息附件和群文件为文件地址,头像和聊天室封面为公开地址。上传中和已删除的文件也有地址,但不能下载 |
thumbnail_url | String | 缩略图地址,只有图片类型的消息附件和群文件有,其他为 null;上传中和已删除的文件也为 null |
name | String | 文件名,创建上传时给出,没有为 null |
content_type | String | 按文件内容识别出的格式,如 image/jpeg,见格式检查与图片处理。完成上传之前为 null |
size | Number | 大小,单位为字节。图片为去掉元数据后的大小,头像为处理后的大小,其他为上传的大小;完成上传之前为创建时声明的大小 |
width | Number | 图片和头像的宽度,像素;其他为 null |
height | Number | 图片和头像的高度,像素;其他为 null |
owner | String | 所属用户的用户名:用户头像为使用它的用户;客户端上传的为上传者;你的服务端上传时可以用 owner 指定。没有或已被删除为 null |
group_id | String | 群文件所在的群 ID,其他为 null |
via | String | 上传方式:client 客户端 / openapi 服务端 / console 控制台 |
created_at | String | 创建上传的时间,保留期从这时算起 |
expires_at | String | 过期时间:消息附件和设置了保留天数的群文件按应用当前的保留天数算出,修改保留天数后随之变化;其他为 null |
in_use | Boolean | 头像是否正在被用户、群或聊天室的资料使用;其他用途为 false |
sha256 | String | 保存的内容的 SHA-256(十六进制),只有图片类型的消息附件、群文件和头像有,其他为 null |
created_by | String | 上传者的标识:通过 OpenAPI 上传的为所用 Client Secret 的内部 ID,控制台上传的为控制台账号的 ID,客户端上传的为 null |
blocked_at | String | 被屏蔽的时间,没有被屏蔽为 null |
blocked_by | String | 屏蔽方:system 系统(内容安全自动屏蔽,或按审核人员的结论屏蔽) / platform 平台,没有被屏蔽为 null |
block_rule_source | String | 屏蔽依据的规则:platform 平台的规则 / tenant 你的应用的规则,没有被屏蔽为 null |
block_reason | String | 屏蔽的原因,没有被屏蔽为 null |
deleted_at | String | 删除时间,没有删除为 null |
delete_reason | String | 删除原因,见下表,没有删除为 null |
delete_reason 的取值:
| 取值 | 说明 |
|---|---|
expired | 消息附件或群文件超过了保留天数 |
deleted | 被你的服务端或控制台删除,或在客户端删除了群文件 |
unused | 头像上传后 24 小时内没有被使用 |
released | 头像被替换 7 天后,或聊天室解散 7 天后的封面 |
user_deleted | 所属用户被删除 |
group_dismissed | 群已解散 |
upload_failed | 完成上传时检查不通过,如格式不符合类型 |
canceled | 上传被取消 |
upload_expired | 上传到期仍未完成 |
evidence_expired | 被屏蔽的文件保留期满 |
一张刚完成上传的图片附件:
{
"file_id": "99606093402996736",
"purpose": "attachment",
"kind": "image",
"status": "active",
"url": "https://im.example.com/media/v1/f/99606093402996736/DC0neNm6HBbxGW9bN6g_iA",
"thumbnail_url": "https://im.example.com/media/v1/f/99606093402996736/DC0neNm6HBbxGW9bN6g_iA/thumb",
"name": "photo.jpg",
"content_type": "image/jpeg",
"size": 269803,
"width": 1080,
"height": 720,
"owner": null,
"group_id": null,
"via": "openapi",
"created_at": "2026-10-02T20:39:03.259Z",
"expires_at": "2026-11-01T20:39:03.259Z",
"in_use": false,
"sha256": "6c458b05f583eca6c08cd7296ec4d2811c68cf59cbc3c8b30feeb8cdc45994ef",
"created_by": "99604957107322880",
"blocked_at": null,
"blocked_by": null,
"block_rule_source": null,
"block_reason": null,
"deleted_at": null,
"delete_reason": null
}