Skip to content

素材管理(Markdown 图片) ​

概述 ​

素材管理是官方提供的一套图片预上传接口:把图片先行上传到平台,拿到 qqbot-media:// 引用链接,之后在 Markdown 消息里反复引用。

相比直接外链,素材管理的优势:

  • 不依赖公网图床,图片由平台托管
  • 同一张图在多条消息中重复使用,不必每次转存
  • 发送时平台自动转存,客户端加载更稳定
本地图片 ──prepare──> upload_id + 预签名 URL
        ──PUT────> COS 直传(返回 ETag = 文件 MD5)
        ──complete──> image_id + qqbot-media:// 引用链接

快速上手 ​

上传并引用 ​

一次性完成全流程(计算大小 / MD5 / SHA1 → 申请上传 → 直传 → 完成):

java
import cn.org.bukkit.bngeuit.Bngeuit;
import cn.org.bukkit.bngeuit.dto.message.media.MediaImageCompleteResponse;
import java.io.File;

MediaImageCompleteResponse img = Bngeuit.uploadMediaImage(new File("kline.png"));
if (img != null) {
    // 宽高可选,省略则按原图比例
    String md = "![分时K线 #800px #400px](" + img.url() + ")";
    Bngeuit.sendGroupMarkdown(groupOpenid, "# 行情\n" + md);
}

img.url() 形如 qqbot-media://image/v1/<token>,直接写进 Markdown 图片语法即可,框架不再提供额外的标签拼接方法。

分步调用 ​

需要自行控制每一步时(例如已有 MD5/SHA1 缓存、或需要打印中间结果):

java
import cn.org.bukkit.bngeuit.Bngeuit;
import cn.org.bukkit.bngeuit.dto.message.media.*;

var api = Bngeuit.api();
File file = new File("kline.png");

// 1. 申请上传
MediaImagePrepareResponse prep = api.prepareMediaImageUpload(
    file.getName(), file.length(), MediaImageFormat.fromFile(file), md5, sha1);

// 2. 直传二进制(Content-Type 必须与申请时一致)
String etag = api.uploadMediaImageBinary(prep.presignedUrl(), MediaImageFormat.PNG, file);

// 3. 完成上传(md5 传上一步返回的 ETag)
MediaImageCompleteResponse done = api.completeMediaImageUpload(prep.uploadId(), etag);
System.out.println(done.url());

md5 必须用 ETag

第 3 步的 md5 必须是第 2 步 PUT 返回的 ETag(服务端会与 COS 对象比对),而不是本地计算的 MD5,否则 complete 会失败。uploadMediaImage(File) 组合方法内部已按此顺序处理,ETag 缺失时才回退为本地 MD5。

查询素材列表 ​

java
import cn.org.bukkit.bngeuit.dto.message.media.MediaImage;
import cn.org.bukkit.bngeuit.dto.message.media.MediaImageListResponse;

MediaImageListResponse page = Bngeuit.getMediaImages();   // 按上传时间倒序
for (MediaImage img : page.images()) {
    System.out.println(img.fileName() + " 过期于 " + img.expiresAt().getValue());
}
System.out.println("当前有效图片数:" + page.total());

// 翻页
String cursor = page.nextCursor();
if (cursor != null && !cursor.isEmpty()) {
    page = Bngeuit.getMediaImages(cursor);
}

删除素材 ​

java
Bngeuit.deleteMediaImage(imageId);   // 释放数量配额

删除后引用链接立即失效,但已发送的消息不受影响(发送时平台已转存副本)。

方法一览 ​

一步步调用 ​

方法返回类型说明
prepareMediaImageUpload(fileName, fileSize, format, md5, sha1)MediaImagePrepareResponse申请上传
uploadMediaImageBinary(presignedUrl, format, data)String直传字节数组,返回 ETag
uploadMediaImageBinary(presignedUrl, format, file)String直传本地文件,返回 ETag
completeMediaImageUpload(uploadId, md5)MediaImageCompleteResponse完成上传
getMediaImages(cursor)MediaImageListResponse查询列表
deleteMediaImage(imageId)EmptyResponse删除
uploadMediaImage(File)MediaImageCompleteResponse组合方法(以上 1-3 步)

Bngeuit 门面提供 uploadMediaImage / getMediaImages / deleteMediaImage 的静态版本,多步流程走 Bngeuit.api()。

限制 ​

项目限制错误码
支持格式png / jpeg(jpg) / gif / webp,扩展名必须与 Content-Type 一致40035001
单张大小≤ 10MB40035002
单机器人数量100 张40035007
上传任务有效期4 小时40035003
引用链接有效期滑动 90 天,每次被消息引用后顺延40035009

40035003 的框架释义是「上传任务不存在、已过期(超过 4 小时)或不属于当前机器人」。遇到该错误时,除了确认上传任务是否已超时,也要确认传给 complete 的 md5 是否为 PUT 返回的 ETag。

直传 403 的常见原因

PUT 时的 Content-Type 必须与 prepare 阶段声明的格式完全一致,例如申请时用 image/png,直传也必须发 image/png。不一致时 COS 直接返回 403,框架会在日志中提示该原因。

数据结构 ​

均位于 cn.org.bukkit.bngeuit.dto.message.media。

MediaImageFormat ​

图片格式枚举,getValue() 即请求参数 content_type:

枚举MIME允许的扩展名
PNGimage/png.png
JPEGimage/jpeg.jpeg / .jpg
GIFimage/gif.gif
WEBPimage/webp.webp

静态方法:

方法说明
fromValue(mimeType)MIME → 枚举(未知类型抛 IllegalArgumentException)
fromFileName(fileName)按扩展名推断(无法识别返回 null)
fromFile(file)按文件扩展名推断(无法识别返回 null)

实例方法:

方法说明
getValue()该格式的 MIME 字符串(请求参数 content_type)
getExtensions()允许的扩展名数组
matchesExtension(ext)扩展名是否属于该格式(大小写不敏感,可带前导点)

MediaImagePrepareResponse ​

字段类型说明
uploadId()String上传任务 ID(4 小时内有效)
presignedUrl()String预签名上传 URL
expiresIn()long预签名 URL 有效期(秒)

MediaImageCompleteResponse ​

字段类型说明
imageId()String图片 ID,用于查询与删除
url()String引用链接 qqbot-media://image/v1/<token>
ttl()long滑动有效期窗口(秒)
previewUrl()String短时效外网预览链接,仅供预览,勿持久化

MediaImage ​

字段类型说明
imageId()String图片 ID
url()String引用链接
fileName()String上传时的文件名
previewUrl()String短时效预览链接
createdAt()Timestamp上传时间
expiresAt()Timestamp过期时间(被引用后顺延)

MediaImageListResponse ​

字段类型说明
images()List<MediaImage>图片列表
nextCursor()String下一页游标(空串 = 末页)
total()long当前有效图片总数

事件 ​

素材管理有 3 对前置 / 后置事件:

操作前置(可取消)后置(成功后)
申请上传MediaImagePrepareEventMediaImagePrepareNotifyEvent
完成上传MediaImageCompleteEventMediaImageCompleteNotifyEvent
删除素材MediaImageDeleteEventMediaImageDeleteNotifyEvent

前置事件字段:

事件字段
MediaImagePrepareEventgetFileName() / getFileSize() / getFormat() / getMd5() / getSha1()
MediaImageCompleteEventgetUploadId() / getMd5()
MediaImageDeleteEventgetImageId()

查询列表(getMediaImages)不触发事件;PUT 直传单独调用时也不触发事件——直传属于上传流程内部步骤,事件挂在 prepare 与 complete 两端。

java
@SubscribeEvent
public void onPrepare(MediaImagePrepareEvent e) {
    if (e.getFileSize() > 5 * 1024 * 1024) {
        e.setCancelled(true);   // 超过 5MB 的素材一律拒绝
    }
}