简体中文
扩展存储是单独的扩展库,开发者需手动将uni-cloud-ext-storage扩展库添加到云函数或云对象的依赖中。
操作步骤:
云端在操作扩展存储前,需要先获取 extStorageManager 对象实例,然后再通过 extStorageManager.xxx 调用对应的API
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu", // 扩展存储供应商
domain: "example.com", // 域名地址
});
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
provider | String | 是 | - | 必填,扩展存储供应商,可选 qiniu: 七牛云 |
domain | String | 是 | - | 必填,扩展储存域名(域名地址)如:example.com |
bucketName | String | 否 | - | 选填,扩展储存的bucket名称,不填会自动从绑定的空间中获取(此参数当前仅云端运行时生效) |
bucketSecret | String | 否 | - | 选填,扩展储存的bucket密钥,不填会自动从绑定的空间中获取(此参数当前仅云端运行时生效) |
接口名:getUploadFileOptions
调用此接口可在云端获取前端上传所需参数,将上传参数返回给前端,前端使用 uni.uploadFile 即可上传文件
你可以在调用 extStorageManager.getUploadFileOptions 前执行一些自己的业务逻辑,判断用户是否有上传权限。
云端云对象代码
注意:如果是云函数,写法会略有不同,包括前端调用的代码,故建议使用云对象,跟文档更契合。
module.exports = {
getUploadFileOptions(data = {}) {
let {
cloudPath, // 前端传过来的文件路径
} = data;
// 可以在此先判断下此路径是否允许上传等逻辑
// ...
// 然后获取 extStorageManager 对象实例
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
// 最后调用 extStorageManager.getUploadFileOptions
let uploadFileOptionsRes = extStorageManager.getUploadFileOptions({
cloudPath: cloudPath,
allowUpdate: false, // 是否允许覆盖更新,如果返回前端,建议设置false,代表仅新增,不可覆盖
});
return uploadFileOptionsRes;
}
}
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
cloudPath | String | 否 | - | 云端文件路径(不填会自动生成) |
allowUpdate | Boolean | 否 | false | 是否允许覆盖更新 true:可覆盖 false:仅新增,不可覆盖 |
fsizeLimit | Number | 否 | - | 限定上传文件大小最大值,单位Byte。超过限制上传文件大小的最大值会被判为上传失败,返回 413 状态码。值最大为1GB |
响应参数
字段 | 类型 | 说明 |
---|---|---|
uploadFileOptions | Object | uni.uploadFile所需的参数 |
cloudPath | String | 文件云端路径 |
fileID | String | 文件ID |
fileURL | String | 文件URL(如果是私有权限的文件,则此URL是无法直接访问的) |
响应参数 uploadFileOptions 详情
{
"expTime": 1704459639, // 过期时间,已秒为单位的时间戳
"cloudPath": "public/test/1704456039122.jpg", // 文件云端路径
"fileID": "qiniu://public/test/1704456039122.jpg", // 文件ID
"fileURL": "https://cdn.example.com/public/test/1704456039122.jpg", // 文件URL(如果是私有权限的文件,则此URL是无法直接访问的)
"uploadFileOptions": {
"url": "https://upload.qiniup.com", // 上传网关地址,对应uni.uploadFile的url参数
"name": "file", // 文件对应的 key,对应uni.uploadFile的name参数
// HTTP 请求中其他额外的 form data,对应uni.uploadFile的formData参数
"formData": {
"token": "xxxxxxxx",
"key": "public/test/1704456039122.jpg",
}
}
}
前端上传代码
uni-app
uni.chooseImage({
count: 1,
success: async (res) => {
const filePath = res.tempFilePaths[0];
uni.showLoading({ title: "上传中...", mask: true });
// ext-storage-co 是你自己写的云对象(参考上面的云端代码)
const uniCloudStorageExtCo = uniCloud.importObject("ext-storage-co");
const uploadFileOptionsRes = await uniCloudStorageExtCo.getUploadFileOptions({
cloudPath: `test/${Date.now()}.jpg`, // 支持自定义目录
});
const uploadTask = uni.uploadFile({
...uploadFileOptionsRes.uploadFileOptions, // 上传文件所需参数
filePath: filePath, // 本地文件路径
success: () => {
const res = {
cloudPath: uploadFileOptionsRes.cloudPath, // 文件云端路径
fileID: uploadFileOptionsRes.fileID, // 文件ID
fileURL: uploadFileOptionsRes.fileURL, // 文件URL(如果是私有权限,则此URL是无法直接访问的)
};
// 数据库里可直接保存 fileURL 或 fileID
console.log("上传成功", res);
},
fail: (err) => {
console.log("上传失败", err);
}
});
// 监听上传进度
uploadTask.onProgressUpdate((res) => {
console.log("监听上传进度", res);
});
uni.hideLoading();
}
});
uni-app x
uni.chooseImage({
count: 1,
success: (res) => {
const filePath = res.tempFilePaths[0];
uni.showLoading({ title: "上传中...", mask: true });
// ext-storage-co 是你自己写的云对象(参考上面的云端代码)
const uniCloudStorageExtCo = uniCloud.importObject("ext-storage-co");
uniCloudStorageExtCo.getUploadFileOptions({
cloudPath: `test/${Date.now()}.jpg`, // 支持自定义目录
}).then((uploadFileOptionsRes : UTSJSONObject) => {
const uploadFileOptions = uploadFileOptionsRes['uploadFileOptions'] as UTSJSONObject;
const url = uploadFileOptions['url'] as string;
const name = uploadFileOptions['name'] as string;
const formData = uploadFileOptions['formData'] as UTSJSONObject;
const cloudPath = uploadFileOptionsRes['cloudPath'] as string; // 文件云端路径
const fileID = uploadFileOptionsRes['fileID'] as string; // 文件ID
const fileURL = uploadFileOptionsRes['fileURL'] as string; // 文件URL(如果是私有权限,则此URL是无法直接访问的)
const uploadTask = uni.uploadFile({
url,
name,
formData,
filePath,
success: () => {
const uploadRes = {
cloudPath, // 文件云端路径
fileID, // 文件ID
fileURL, // 文件URL(如果是私有权限,则此URL是无法直接访问的)
};
// 数据库里可直接保存 fileURL 或 fileID
console.log("上传成功", uploadRes);
},
fail: (err) => {
console.log("上传失败", err);
}
});
// 监听上传进度
uploadTask.onProgressUpdate((res) => {
console.log("监听上传进度", res);
});
uni.hideLoading();
});
}
});
接口名:uploadFile
调用此接口可在云端上传文件到云存储
云端代码
将网络图片转为Buffer上传到扩展存储
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
let imageBuffer = await uniCloud.request({
url: "https://www.xxx.com/a.jpg",
method: "GET",
responseType: "buffer",
header: {
"cache-control": "no-cache",
}
});
let res = await extStorageManager.uploadFile({
cloudPath: `${Date.now()}.png`, // 云端文件名,不填则自动生成
fileContent: imageBuffer.data, // 要上传的文件内容
allowUpdate: false, // 是否允许覆盖
});
console.log('uploadFile: ', res);
将base64转为Buffer上传到扩展存储
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
// 文件的base64值
let base64 =
`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAAAXNSR0IArs4c6QAAAX1JREFUeF7tnLFxwkAQRf86dEwHDqEATAeO3YNdgmugBFGDiemAoQBSd+DUhD6Pxk6Y4XTB13o08AiFDt09vf3cCGZDkhbd6qHoey3Fk6T7/tgNv05S2YXu3o6v+4/4haODVGY3DOXC0uMzpGXMu+W7FM/AuUSgbGPePX5RVlU9Tj2ggj11AgBq2AEgAHkBgkEYhEEeAQzy+JFBGIRBHgEM8viRQRg0AYOOL/vqLBablTfDxujsa49SYtmTHGKUfW0A/UcGZd9FDDJSKvvmUGKUmKGnJAzCIAxK3aRSYpQYJUaJsZM2qoCdNI87DH0kYRAGYZBHAIM8fmQQBmGQRwCDPH5kEAZhkEcAgzx+ZBAGYZBH4NoNSqXT+PAx/puU/rMPgBpP9QAEoLYDQ3uR9ui8M8ggQtqzazIGecuY9uhRvuanvURvdgBq8AMQgCgxjwAGefzIIAzCII8ABnn8aLA0zK9vsESLrjqjsqXJW5XOX5O3/n3aBJ5ROmsT+AM4B3/QCW75sQAAAABJRU5ErkJggg==`;
let base64Str = "base64,";
let base64Index = base64.indexOf(base64Str);
if (base64Index > -1) base64 = base64.substring(base64Index + base64Str.length);
let fileContent = new Buffer(base64, 'base64');
let res = await extStorageManager.uploadFile({
cloudPath: `${Date.now()}.png`, // 云端文件名,不填则自动生成
fileContent, // 要上传的文件内容
allowUpdate: false, // 是否允许覆盖
});
console.log('uploadFile: ', res);
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
fileContent | Buffer | 是 | - | 文件内容 |
cloudPath | String | 否 | - | 云端文件路径(不填会自动生成) |
allowUpdate | Boolean | 否 | false | 是否允许覆盖更新 true:可覆盖 false:仅新增,不可覆盖 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
cloudPath | String | 文件云端路径 |
fileID | String | 文件ID |
fileURL | String | 文件URL(如果是私有权限的文件,则此URL是无法直接访问的) |
接口名:getTempFileURL
调用此接口可批量获取私有文件的临时下载链接
你可以在调用 extStorageManager.getTempFileURL 前执行一些自己的业务逻辑,判断用户是否有获取临时下载链接权限。
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
let res = extStorageManager.getTempFileURL({
fileList: ["qiniu://test.jpg"], // 文件地址列表
});
console.log('getTempFileURL: ', res);
return res;
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
fileList | Array | 是 | - | 文件地址列表,数组内元素值类型支持(fileID、cloudPath、fileURL) 如:"qiniu://test.jpg" "test.jpg" "https://example.com/test.jpg" 均表示同一个文件 |
expiresIn | Number | 否 | 3600 | 有效期,单位秒,默认1小时 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
fileList | Array | 存储下载链接的数组 |
响应参数中的fileList
字段 | 类型 | 说明 |
---|---|---|
tempFileURL | String | 临时文件URL地址 |
fileID | String | 文件ID |
cloudPath | String | 文件云端路径 |
接口名:downloadFile
调用此接口获得文件Buffer
你可以在调用 extStorageManager.downloadFile 前执行一些自己的业务逻辑,判断用户是否有下载该文件权限
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
let res = extStorageManager.downloadFile({
fileID: "qiniu://test.jpg", // 待下载的文件
});
console.log('getTempFileURL: ', res);
return res;
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
fileID | String | 是 | - | 待下载的文件,该字段支持的值类型:fileID、cloudPath、fileURL 如:"qiniu://test.jpg" "test.jpg" "https://example.com/test.jpg" 均表示同一个文件 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
fileContent | Buffer | 下载的文件的内容 |
接口名:deleteFile
调用此接口可批量删除云端文件
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
let res = await extStorageManager.deleteFile({
fileList: ["qiniu://test.jpg"], // 待删除的文件地址列表
});
console.log('deleteFile: ', res);
return res;
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
fileList | Array | 是 | - | 文件地址列表,数组内元素值类型支持(fileID、cloudPath、fileURL) 如:"qiniu://test.jpg" "test.jpg" "https://example.com/test.jpg" 均表示同一个文件 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
fileList | Array | 删除结果组成的数组。 |
接口名:updateFileStatus
可以将指定文件设置为私有权限或公共权限
默认上传的文件都是公共权限,如果需要将文件设置为私有权限,则可调用此接口
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
let res = await extStorageManager.updateFileStatus({
fileID: "qiniu://test.jpg", // 待修改的文件
isPrivate: true, // true 私有 false 公共
});
console.log('updateFileStatus: ', res);
return res;
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
fileID | String | 是 | - | 待修改的文件,该字段支持的值类型:fileID、cloudPath、fileURL 如:"qiniu://test.jpg" "test.jpg" "https://example.com/test.jpg" 均表示同一个文件 |
isPrivate | Boolean | 是 | - | true 设为私有权限 false 设为公共读权限 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
errCode | Number | 0 成功 其他均为失败 |
errMsg | String | 失败描述 |
接口名:refreshCdnCache
调用此接口可批量缓存云端文件CDN缓存
注意:
https://cdn.example.com/test.jpg?v=1
(此方式即使不执行refreshCdnCache接口也能获取最新的文件)云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
let res = await extStorageManager.refreshCdnCache({
fileList: ["qiniu://test.jpg"], // 待刷新的文件地址列表
});
console.log('refreshCdnCache: ', res);
return res;
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
fileList | Array | 是 | - | 文件地址列表,数组内元素值类型支持(fileID、cloudPath、fileURL) 如:"qiniu://test.jpg" "test.jpg" "https://example.com/test.jpg" 均表示同一个文件 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
errCode | Number | 0 代表成功,其他均为失败 |
errMsg | String | 失败原因 |
接口名:getDomains
注意:获取的域名列表是账号绑定的所有域名
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 调用getDomains接口时,这里的域名可以随便填,不会生效,但不能为空
});
let { domains = [] } = await extStorageManager.getDomains();
console.log('域名列表: ', domains);
响应参数
字段 | 类型 | 说明 |
---|---|---|
domains | Array | 域名列表 |
接口名:getCdnTop
注意:获取的域名列表是账号绑定的所有域名
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 调用getDomains接口时,这里的域名可以随便填,不会生效,但不能为空
});
// 获取域名
let { domains = [] } = await extStorageManager.getDomains();
// 查询 2024-05-12 日的TOP100统计数据
let startDate = "2024-05-12";
let endDate = "2024-05-12";
let getCdnTopRes = await extStorageManager.getCdnTop({
type: 2, // 1 topURL 2 topIP
domains,
startDate,
endDate
});
console.log("TOP100统计数据: ", getCdnTopRes.data);
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
type | Number | 是 | - | 必填,查询类型,值为1代表查询topURL 值为2代表查询topIP |
domains | Array | 是 | - | 必填,域名列表,总数不超过100条 |
startDate | String | 是 | - | 必填,开始时间,格式为:2006-01-02。起止最大间隔为31天 |
endDate | String | 是 | - | 必填,结束时间,格式为:2006-01-02。起止最大间隔为31天 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
data | Array | TOP100统计数据 |
以下API暂不支持本地运行,只能云端运行才能调用
接口名:getCdnFlow
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 调用getCdnFlow接口时,这里的域名可以随便填,不会生效,但不能为空
});
let getCdnFlowRes = await extStorageManager.getCdnFlow({
domains: ["cdn.example.com"], // 这里填写你绑定的域名,支持传多个域名
granularity: 'day',
startDate: "2024-04-01",
endDate: "2024-04-30"
});
console.log('getCdnFlowRes: ', getCdnFlowRes);
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
domains | Array | 是 | - | 必填,域名列表,总数不超过100条 |
granularity | String | 是 | - | 必填,粒度,取值:5min 、 hour 、day |
startDate | String | 是 | - | 必填,开始时间,格式为:2006-01-02。起止最大间隔为31天 |
endDate | String | 是 | - | 必填,结束时间,格式为:2006-01-02。起止最大间隔为31天 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
data | Object | 流量统计数据 |
完整响应参数示例
注意
/1024/1024/1024
{
"errCode": 0,
"errMsg": "ok",
"data": {
"time": [
"2024-04-01 00:00:00", "2024-04-02 00:00:00", "2024-04-03 00:00:00", "2024-04-04 00:00:00", "2024-04-05 00:00:00",
"2024-04-06 00:00:00", "2024-04-07 00:00:00", "2024-04-08 00:00:00", "2024-04-09 00:00:00", "2024-04-10 00:00:00",
"2024-04-11 00:00:00", "2024-04-12 00:00:00", "2024-04-13 00:00:00", "2024-04-14 00:00:00", "2024-04-15 00:00:00",
"2024-04-16 00:00:00", "2024-04-17 00:00:00", "2024-04-18 00:00:00", "2024-04-19 00:00:00", "2024-04-20 00:00:00",
"2024-04-21 00:00:00", "2024-04-22 00:00:00", "2024-04-23 00:00:00", "2024-04-24 00:00:00", "2024-04-25 00:00:00",
"2024-04-26 00:00:00", "2024-04-27 00:00:00", "2024-04-28 00:00:00", "2024-04-29 00:00:00", "2024-04-30 00:00:00"
],
"data": {
"qiniu.dcloud.net.cn": {
"china": [1350, 1312, 1350, 1350, 1350, 0, 3937, 1987, 1350, 1350, 1350, 1350, 1350, 1350, 1350, 1987, 1350, 1350, 675, 1350, 1350, 675,1350, 1350, 1987, 1350, 1350, 1350, 1987, 1312],
"oversea": [0, 0, 0, 0, 0, 0, 0, 4050, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
},
"total": { "china": 42859, "oversea": 4050, "all": 46909 }
}
}
接口名:updateCdnCert
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 调用updateCdnCert接口时,这里的域名可以随便填,不会生效,但不能为空
});
let updateCdnCertRes = await extStorageManager.updateCdnCert({
domain: "cdn.example.com", // 这里填写你绑定的域名
pri: "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQxxxxxxikbALs=\n-----END RSA PRIVATE KEY-----\n", // 证书私钥
ca: "-----BEGIN CERTIFICATE-----\nMIIGaxxxxxxxVKQPNriiTsBhYscw==\n-----END CERTIFICATE-----", // 证书内容
});
console.log('updateCdnCertRes: ', updateCdnCertRes);
请求参数
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
domain | String | 是 | - | 域名 |
pri | String | 是 | - | 必填,证书私钥 |
ca | String | 是 | - | 必填,证书内容 |
响应参数
字段 | 类型 | 说明 |
---|---|---|
errCode | String | 0代表正确,其他均为错误 |
errMsg | String | 错误提示 |
小程序需要添加域名白名单,否则无法正常使用
将下方域名添加到小程序的uploadFile合法域名列表中
https://upload.qiniup.com
下载域名就是你开通扩展存储时绑定的自定义域名,将你的自定义域名添加到download合法域名列表中
简介
图片瘦身服务(imageslim)在尽可能不影响画质的情况下,将JPEG、PNG格式的图片实时压缩,瘦身后画质基本没有变化,分辨率不变,格式不变,大幅缩小文件体积:
使用限制
接口规格
imageslim/zlevel/<zlevel>
参数名称 | 必填 | 说明 |
---|---|---|
/zlevel/<zlevel> | 否 | 图片质量损失控制,值越小,质量越好,压缩率越低。默认值为3,取值范围:[0-10]。 |
使用示例
原图
http://7xkv1q.com1.z0.glb.clouddn.com/grape.jpg
瘦身后
http://7xkv1q.com1.z0.glb.clouddn.com/grape.jpg?imageslim
瘦身后并控制质量损失
http://7xkv1q.com1.z0.glb.clouddn.com/grape.jpg?imageslim/zlevel/2
瘦身前后图片对比
功能 | 说明 |
---|---|
缩略 | 等比缩放、设定目标宽高缩放等多种方式 |
格式转换 | 格式转换、GIF 颜色控制 |
渐进显示 | 图片渐进显示 |
质量变换 | 对图片质量进行调节 |
限制说明
使用服务时有如下限制:
gif
,一般处理时间比较长,建议优先使用持久化处理webp
时,宽或高不能超过16383gif
最大帧数为 200w
和 h
参数不能超过3万像素,总像素不能超过1.5亿像素w
和 h
参数不能超过9999像素,总像素不得超过24999999(2500w-1)像素gif
、webp动图
,最大帧数为 500w
和 h
不能超过3万像素,总像素不能超过1.5亿像素w
和 h
不能超过14999像素,总像素不得超过59999999(6000w-1)像素接口规格
注意:接口规格不含任何空格与换行符
imageView2/<mode>/w/<LongEdge>
/h/<ShortEdge>
/format/<Format>
/interlace/<Interlace>
/q/<Quality>
/colors/<colors>
/ignore-error/<ignoreError>
参数说明
参数名称 | 必填 | 说明 |
---|---|---|
/mode | 是 | 定义等比缩放、设定目标宽高缩放的多种不同方式,取值范围:[0,5]的整数,必填项。分为如下几种情况: |
mode=0,使用姿势:/0/w/<LongEdge>/h/<ShortEdge> 1、限定缩略图的长边最多为 <LongEdge> ,短边最多为 <ShortEdge> ,进行等比缩放,不裁剪。2、如果只指定 w 参数则表示限定长边(短边自适应),只指定 h 参数则表示限定短边(长边自适应)。 | ||
mode=1,使用姿势:/1/w/<LongEdge>/h/<ShortEdge> 1、限定缩略图的宽最少为 <LongEdge> ,高最少为<ShortEdge> ,进行等比缩放,居中裁剪。2、转后的缩略图通常恰好是 <LongEdge>x<ShortEdge> 的大小(有一个边缩放的时候会因为超出矩形框而被裁剪掉多余部分)。3、如果只指定 w 参数或只指定 h 参数,代表限定为长宽相等的正方图。 | ||
mode=2,使用姿势:/2/w/<LongEdge>/h/<ShortEdge> 1、限定缩略图的长边最少为 <LongEdge> ,短边最少为<ShortEdge> ,进行等比缩放,不裁剪。2、如果只指定 w 参数则表示限定宽(高自适应),只指定 h 参数则表示限定高(宽自适应)。 3、它和模式0类似,区别只是限定宽和高,不是限定长边和短边。从应用场景来说,模式0适合移动设备上做缩略图,模式2适合PC上做缩略图。 | ||
mode=3,使用姿势:/3/w/<LongEdge>/h/<ShortEdge> 1、限定缩略图的长边最少为 <LongEdge> ,短边最少为<ShortEdge> ,进行等比缩放,不裁剪。2、如果只指定 w 参数或只指定 h 参数,代表长宽限定为同样的值。你可以理解为模式1是模式3的结果再做居中裁剪得到的。 | ||
mode=4,使用姿势:/4/w/<LongEdge>/h/<ShortEdge> 1、限定缩略图的长边最少为 <LongEdge> ,短边最少为<ShortEdge> ,进行等比缩放,不裁剪。2、如果只指定 w 参数或只指定 h 参数,表示长边短边限定为同样的值。这个模式很适合在手持设备做图片的全屏查看(把这里的长边短边分别设为手机屏幕的分辨率即可),生成的图片尺寸刚好充满整个屏幕(某一个边可能会超出屏幕)。 | ||
mode=5,使用姿势:/5/w/<LongEdge>/h/<ShortEdge> 1、限定缩略图的长边最少为 <LongEdge> ,短边最少为<ShortEdge> ,进行等比缩放,居中裁剪。2、如果只指定 w 参数或只指定 h 参数,表示长边短边限定为同样的值。同上模式4,但超出限定的矩形部分会被裁剪。 | ||
/format/<Format> | 否 | 图片输出格式 1、取值:copy,保持原图格式输出。 2、取值:jpg,gif,png,webp等,参考支持转换的图片格式。 3、取值:heic 或 avif ,属于 图片高级压缩 格式,按照图片高级压缩收费,当前仅支持 持久化处理 来使用。 注意: ● 必须指定mode参数,否则不生效。 ● 当原图为heic 或 avif 时,设置format/copy 会保持原图格式不变,且不按照图片高级压缩收费;如果不指定 format 参数,将会输出jpeg 。 |
/colors/<colors> | 否 | 控制图片颜色数量 ● 源图片为 GIF 时,控制输出 GIF 中不同颜色的数量,取值可为2、4、8、16、32、64、128、256,不使用该参数时的默认值为128。 ● 输出图片为 PNG 时,控制输出 PNG 中不同颜色的数量,取值可为2、4、8、16、32、64、128、256,不使用该参数时默认关闭。 注意: ● 必须指定mode参数,否则不生效。 ● 如果只设置图片颜色数量,不做其他缩放处理,建议使用 图片高级处理-格式转换 |
/interlace/<Interlace> | 否 | 是否支持渐进显示 取值范围:1 支持渐进显示,0不支持渐进显示(默认为0)。 适用目标格式:jpg 效果:网速慢时,图片显示由模糊到清晰。新图的输出格式,必须指定mode参数,否则不生效。 注意: ● 必须指定mode参数,否则不生效。 ● 如果只设置渐渐显示,不做其他缩放处理,建议使用 图片高级处理-渐进显示 |
/q/<Quality> | 否 | 新图的图片质量 ● 取值范围是[1, 100] ● 输入是 jpeg,且不强制指定质量条件下,七牛会根据原图质量算出一个修正值,取修正值和指定值中的小值。 注意: ● 必须指定mode参数,否则不生效。 ● 指定值后面可以增加 !,表示强制使用指定值,如100!。 ● heic:默认值35 ● avif:默认值50 ● 其他格式:默认值75 ● 如果只设置图片质量,不做其他缩放处理,建议使用 图片高级处理-质量变换 |
/ignore-error/<ignoreError> | 否 | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息。 注意: ● 必须指定mode参数,否则不生效。 |
注意:
<Quality>
修正值算法:
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
裁剪正中部分,等比缩小生成200x200缩略图:
https://dora-doc.qiniu.com/gogopher.jpg?imageView2/1/w/200/h/200
宽度固定为200px,高度等比缩小,生成200x133缩略图:
https://dora-doc.qiniu.com/gogopher.jpg?imageView2/2/w/200
高度固定为200px,宽度等比缩小,生成300x200缩略图:
https://dora-doc.qiniu.com/gogopher.jpg?imageView2/2/h/200
设置图片质量为55:
https://dora-doc.qiniu.com/gogopher.jpg?imageView2/1/w/200/h/200/q/55
接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/thumbnail/!<Scale>p
/thumbnail/!<Scale>px
/thumbnail/!x<Scale>p
/thumbnail/<Width>x
/thumbnail/x<Height>
/thumbnail/<Width>x<Height>
/thumbnail/!<Width>x<Height>r
/thumbnail/<Width>x<Height>!
/thumbnail/<Width>x<Height>>
/thumbnail/<Width>x<Height><
/thumbnail/<Area>@
/ignore-error/<ignoreError>
参数名称 | 必填 | 说明 |
---|---|---|
/thumbnail/!<Scale>p | 基于原图的长宽,按指定百分比缩放。Scale取值范围1-999。 | |
/thumbnail/!<Scale>px | 以百分比形式指定目标图片宽度,高度不变。Scale取值范围1-999。 | |
/thumbnail/!x<Scale>p | 以百分比形式指定目标图片高度,宽度不变。Scale取值范围1-999。 | |
/thumbnail/<Width>x | 指定目标图片宽度,高度等比缩放,Width取值范围1-9999。 | |
/thumbnail/x<Height> | 指定目标图片高度,宽度等比缩放,Height取值范围1-9999。 | |
/thumbnail/<Width>x<Height> | 等比缩放,比例值为宽缩放比和高缩放比的较小值,Width 和 Height 取值范围1-9999。 注意: 宽缩放比:目标宽/原图宽 高缩放比:目标高/原图高 | |
/thumbnail/!<Width>x<Height>r | 等比缩放,比例值为宽缩放比和高缩放比的较大值,Width 和 Height 取值范围1-9999。 注意: 宽缩放比:目标宽/原图宽 高缩放比:目标高/原图高 | |
/thumbnail/<Width>x<Height>! | 按指定宽高值强行缩略,可能导致目标图片变形,width和height取值范围1-9999。 | |
/thumbnail/<Width>x<Height>> | 等比缩小,比例值为宽缩放比和高缩放比的较小值。如果目标宽和高都大于原图宽和高,则不变,Width 和 Height 取值范围1-9999。 注意: 宽缩放比:目标宽/原图宽 高缩放比:目标高/原图高; | |
/thumbnail/<Width>x<Height>< | 等比放大,比例值为宽缩放比和高缩放比的较小值。如果目标宽(高)小于原图宽(高),则不变,Width 和 Height 取值范围1-9999。 注意: 宽缩放比:目标宽/原图宽 高缩放比:目标高/原图高; | |
/thumbnail/<Area>@ | 按原图高宽比例等比缩放,缩放后的像素数量不超过指定值,Area取值范围1-24999999。 | |
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息 |
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
等比缩小至 20%
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/!20p
等比缩小到宽为200px
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/200x
等比缩小到高为200px
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/x200
限定长边,生成不超过 200x200 的缩略图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/200x200
限定短边,生成不小于 200x200 的缩略图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/!200x200r
强制生成 200x200 的缩略图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/200x200!
原图大于指定长宽矩形,按长边自动缩小为 200x200 缩略图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/200x200>
原图小于指定长宽矩形,按长边自动拉伸为 700x600 放大图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/700x600<
生成图的像素总数小于指定值
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/350000@
接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/crop
裁剪操作参数表 (cropsize)
参数名称 | 必填 | 说明 |
---|---|---|
/crop/<Width>x | 指定目标图片宽度,高度不变。取值范围为0-10000。 | |
/crop/x<Height> | 指定目标图片高度,宽度不变。取值范围为0-10000。 | |
/crop/<Width>x<Height> | 同时指定目标图片宽高。取值范围为0-10000。 | |
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息。 |
裁剪偏移参数表 (cropoffset)
参数名称 | 必填 | 说明 |
---|---|---|
/crop/!{cropsize}a<dx>a<dy> | 相对于偏移锚点,向右偏移dx个像素,同时向下偏移dy个像素。取值范围不限,小于原图宽高即可。 | |
/crop/!{cropsize}-<dx>a<dy> | 相对于偏移锚点,从指定宽度中减去dx个像素,同时向下偏移dy个像素。取值范围不限,小于原图宽高即可。 | |
/crop/!{cropsize}a<dx>-<dy> | 相对于偏移锚点,向右偏移dx个像素,同时从指定高度中减去dy个像素。取值范围不限,小于原图宽高即可。 | |
/crop/!{cropsize}-<dx>-<dy> | 相对于偏移锚点,从指定宽度中减去dx个像素,同时从指定高度中减去dy个像素。取值范围不限,小于原图宽高即可。 |
示例
注意
重心参数表
在图片高级处理现有的功能中只影响其后的裁剪操作参数表,即裁剪操作以 gravity 为原点开始偏移后,进行裁剪操作。
NorthWest | North | NorthEast
| |
| |
--------------+----------------+--------------
| |
West | Center | East
| |
--------------+----------------+--------------
| |
| |
SouthWest | South | SouthEast
转义说明
部分参数以 ! 开头,表示参数将被转义。为便于阅读,我们采用特殊转义方法,如下所示:
p => % (percent)
r => ^ (reverse)
a => + (add)
即!50x50r 实际代表 50x50^ 这样一个字符串。而!50x50实际代表 50x50 这样一个字符串(该字符串并不需要转义)。 中的 OffsetGeometry 部分可以省略,默认为 +0+0。即 /crop/50x50 等价于 /crop/!50x50a0a0,执行 -crop 50x50+0+0 语义。
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
限定宽最大300,生成裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/crop/300x
限定高最大300,生成裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/200x
等比缩小到高为200px
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/x200
生成固定 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/crop/300x300
生成 300x300 裁剪图,偏移距离 30x100
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/crop/!300x300a30a100
生成 300x200 裁剪图,偏移距离 30x0
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/crop/!300x300a30-100
生成 270x300 裁剪图,偏移距离 0x100
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/crop/!300x300-30a100
生成 270x200 裁剪图,偏移距离 0x0
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/crop/!300x300-30-100
锚点在左上角 (NorthWest),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/NorthWest/crop/300x300
锚点在正上方 (North),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/North/crop/300x300
锚点在右上角 (NorthEast),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/NorthEast/crop/300x300
锚点在正左方 (West),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/West/crop/300x300
锚点在正中 (Center),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/Center/crop/300x300
锚点在正右方 (East),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/East/crop/300x300
锚点在左下角 (SouthWest),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/SouthWest/crop/300x300
锚点在正下方 (South),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/South/crop/300x300
锚点在右下角 (SouthEast),生成 300x300 裁剪图
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/gravity/SouthEast/crop/300x300
接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/format/<Format>
/colors/<colors>
/ignore-error/<ignoreError>
参数说明
参数名称 | 必填 | 说明 |
---|---|---|
/format/<Format> | 新图的输出格式 1、取值:jpg,gif,png,webp等,参考 支持转换的图片格式。 2、取值:heic 或 avif ,属于 图片高级压缩 格式,按照图片高级压缩收费,当前仅支持 持久化处理 来使用。 3、取值:缺省,默认保持原图格式输出。 注意: ● 必须指定mode参数,否则不生效。 ● 当原图为heic 或 avif 时,如果 format 值缺省,会输出jpeg 。 | |
/colors/<colors> | 1、源图片为 GIF 时,控制输出 GIF 中不同颜色的数量,取值可为2、4、8、16、32、64、128或256,不使用该参数时的默认值为128。 2、输出图片为 PNG 时,控制输出 PNG 中不同颜色的数量,取值可为2、4、8、16、32、64、128或256,不使用该参数时默认关闭。 | |
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息。 |
注意
使用示例
转为png格式
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/format/png
接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/interlace/<Interlace>
/ignore-error/<ignoreError>
参数说明
参数名称 | 必填 | 说明 |
---|---|---|
/interlace/<Interlace> | 是否支持渐进显示 取值范围:1 支持渐进显示,0不支持渐进显示(默认为0)。 适用目标格式:jpg 效果:网速慢时,图片显示由模糊到清晰。 | |
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息。 |
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
原图转为png后,并渐进显示图片:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/format/png/interlace/1
原图缩放后,并渐进显示图片:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/thumbnail/300x300/interlace/1
接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/background/<background>
/ignore-error/<ignoreError>
参数说明
参数名称 | 必填 | 说明 | ||||||
---|---|---|---|---|---|---|---|---|
/background/<background> | 填充背景颜色,可以是颜色名称(比如red)或十六进制颜色(比如#FF0000)的URL安全的Base64编码。我们支持的颜色名称有transparent(#00000000)、none(#00000000)、white(#FFFFFF)、black(#000000)、red(#FF0000)、orange(#FFA500)、yellow(#FFFF00)、green(#008000)、blue(#0000FF)、purple(#800080)、gray(#7E7E7E)、pink(#FFC0CB),其中none与transparent均为透明背景色,另外十六进制颜色不区分大小写,具体颜色请参考颜色编码表。缺省背景色为white(#FFFFFF)。 | |||||||
/extent/<extent> | 背景颜色填充的大小和偏移,即 {size}{offset}。 注意:dx、dy 取值范围不限,小于原图宽高即可。示例如下: | 1、/extent/!642x492a10a10 ,表示相对原图左上角,向右偏移10,向下偏移10,填充 642x492 大小的背景颜色。 | 2、/extent/!642x492-10-10 ,表示相对原图左上角,向左偏移10,向上偏移10,填充 642x492大小的背景颜色。 | 注意:dx、dy 取值范围不限,小于原图宽高即可。示例如下: | 1、/extent/!642x492a10a10 ,表示相对原图左上角,向右偏移10,向下偏移10,填充 642x492 大小的背景颜色。 | 2、/extent/!642x492-10-10 ,表示相对原图左上角,向左偏移10,向上偏移10,填充 642x492大小的背景颜色。 | 具体参数如下: | |
/extent/<Width>x<height> | 相对于原图中心位置,填充指定大小的背景颜色。 | |||||||
/extent/<Width>x<height>a<dx>a<dy> | 相对于原图左上角,向右偏移dx个像素,同时向下偏移dy个像素,填充指定大小的背景颜色。 | |||||||
/extent/!<Width>x<height>-<dx>-<dy> | 相对于原图左上角,向左偏移dx个像素,同时向上偏移dy个像素,填充指定大小的背景颜色。 | |||||||
/extent/!<Width>x<height>-<dx>a<dy> | 相对于原图左上角,向左偏移dx个像素,同时向下偏移dy个像素,填充指定大小的背景颜色。 | |||||||
/extent/!<Width>x<height>a<dx>-<dy> | 相对于原图左上角,向右偏移dx个像素,同时向上偏移dy个像素,填充指定大小的背景颜色。 | |||||||
/extent/!-<dx>a<dy> | 相对于原图左上角,向左偏移dx个像素,同时向下偏移dy个像素,填充指定大小的背景颜色。 | |||||||
/extent/!a<dx>-<dy> | 相对于原图左上角,向右偏移dx个像素,同时向上偏移dy个像素,填充指定大小的背景颜色。 | |||||||
/bordercolor/<bordercolor> | 填充背景颜色,可以是颜色名称(比如red)或十六进制颜色(比如#FF0000)的URL安全的Base64编码。我们支持的颜色名称有transparent(#00000000)、none(#00000000)、white(#FFFFFF)、black(#000000)、red(#FF0000)、orange(#FFA500)、yellow(#FFFF00)、green(#008000)、blue(#0000FF)、purple(#800080)、gray(#7E7E7E)、pink(#FFC0CB),其中none与transparent均为透明背景色,另外十六进制颜色不区分大小写,具体颜色请参考颜色编码表。缺省背景色为white(#FFFFFF)。 | |||||||
/border/<width>x<height> | 相对于原图中心位置,填充指定大小的背景颜色。输出图像宽高为(OriWidth+2width) x(OriHeight + 2 height) 使用示例:http://rnjirg2hf.sabkt.gdipper.com/gogopher111.jpeg?imageMogr2/border/10x10/bordercolor/cmVk | |||||||
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息。 |
转义说明
部分参数以 ! 开头,表示参数将被转义。为便于阅读,我们采用特殊转义方法,如下所示:
p => % (percent)
r => ^ (reverse)
a => + (add)
即 !50x50r
实际代表 50x50 这样一个字符串。而 !50x50
实际代表 50x50 这样一个字符串(该字符串并不需要转义)。 <ImageSizeAndOffsetGeometry>
中的 OffsetGeometry 部分可以省略,默认为 +0+0。即 /extent/50x50 等价于 /extent/!50x50a0a0。
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
旋转并添加背景色:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/auto-orient/thumbnail/!256x256r/gravity/center/crop/!256x256/blur/3x9/quality/80/rotate/45/background/b3Jhbmdl
添加1像素的红色边框:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/extent/!642x429/background/cmVk
左边填充3像素的红色边框:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/extent/!-3a0/background/cmVk
自适应填充背景色:
http://rnjirg2hf.sabkt.gdipper.com/gogopher111.jpeg?imageMogr2/border/10x10/bordercolor/cmVk
图片右边填充3像素的红色边框:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/extent/!a3-0/background/cmVk
图片上方填充3像素的红色边框:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/extent/!a0-3/background/cmVk
图片下方填充3像素的红色边框:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/extent/!-0a3/background/cmVk
限制说明
gif
最大帧数为 200w
和 h
参数不能超过3万像素,总像素不能超过1.5亿像素w
和 h
参数不能超过9999像素,总像素不得超过24999999(2500w-1)像素gif
、webp动图
,最大帧数为 500w
和 h
不能超过3万像素,总像素不能超过1.5亿像素w
和 h
不能超过14999像素,总像素不得超过59999999(6000w-1)像素接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/auto-orient
/rotate/<rotate>
/ignore-error/<ignoreError>
参数说明
参数名称 | 必填 | 说明 |
---|---|---|
/auto-orient | 自适应旋转:与图像处理顺序相关,建议放在首位,根据原图EXIF信息自动旋正,便于后续处理。 | |
/rotate/<rotate> | 普通旋转:图片顺时针旋转角度,取值范围为1-360,默认为不旋转。 | |
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息 |
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
顺时针旋转 45 度:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/rotate/45
限制说明
gif
最大帧数为 200w
和 h
参数不能超过3万像素,总像素不能超过1.5亿像素w
和 h
参数不能超过9999像素,总像素不得超过24999999(2500w-1)像素gif
、webp动图
,最大帧数为 500w
和 h
不能超过3万像素,总像素不能超过1.5亿像素w
和 h
不能超过14999像素,总像素不得超过59999999(6000w-1)像素接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/blur/<blur>
/ignore-error/<ignoreError>
参数说明
参数名称 | 必填 | 说明 |
---|---|---|
/blur/<blur> | 高斯模糊参数。radius是模糊半径,取值范围为1-50。sigma是正态分布的标准差,必须大于0。图片格式为gif时,不支持该参数。 | |
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息 |
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
半径为 3,Sigma 值为 5:
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/blur/3x5
限制说明
gif
最大帧数为 200w
和 h
参数不能超过3万像素,总像素不能超过1.5亿像素w
和 h
参数不能超过9999像素,总像素不得超过24999999(2500w-1)像素gif
、webp动图
,最大帧数为 500w
和 h
不能超过3万像素,总像素不能超过1.5亿像素w
和 h
不能超过14999像素,总像素不得超过59999999(6000w-1)像素接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/sharpen/<sharpen>
/ignore-error/<ignoreError>
参数说明
参数名称 | 必填 | 说明 |
---|---|---|
/sharpen/<sharpen> | 图片是否锐化,当设置值为1时打开锐化效果。 | |
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息 |
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
设置锐化参数为1,进行锐化处理
https://dn-odum9helk.qbox.me/resource/gogopher.jpg?imageMogr2/sharpen/1
限制说明
gif
最大帧数为 200w
和 h
参数不能超过3万像素,总像素不能超过1.5亿像素w
和 h
参数不能超过9999像素,总像素不得超过24999999(2500w-1)像素gif
、webp动图
,最大帧数为 500w
和 h
不能超过3万像素,总像素不能超过1.5亿像素w
和 h
不能超过14999像素,总像素不得超过59999999(6000w-1)像素接口规格
注意:接口规格不含任何空格与换行符。
imageMogr2/density/<density>
/ignore-error/<ignoreError>
参数说明
参数名称 | 必填 | 说明 |
---|---|---|
density/<density> | 图像DPI值的修改值范围:1-1200,默认为原图DPI值。 | |
/ignore-error/<ignoreError> | 主要针对图片兼容性的问题导致无法处理,取值为1时,则处理失败时返回原图; 不设置此参数,默认处理失败时返回错误信息 |
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
设置输出分辨率为300DPI,进行处理。
https://dora-doc.qiniu.com/gogopher.jpg?imageMogr2/density/300
简介
图片基本信息包括图片格式、图片大小、色彩模型。
限制说明
接口规格
imageInfo
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
获取图片基本信息
https://dora-doc.qiniu.com/gogopher.jpg?imageInfo
返回结果(内容经过格式化以便阅读):
{
size: 214513,
format: "jpeg",
width: 640,
height: 427,
colorModel: "ycbcr",
orientation: "Top-left"
}
字段名称 | 必填 | 说明 |
---|---|---|
size | 是 | 文件大小,单位:Bytes |
format | 是 | 图片类型,如png、jpeg、gif、bmp等。 |
width | 是 | 图片宽度,单位:像素(px)。 |
height | 是 | 图片高度,单位:像素(px)。 |
colorModel | 是 | 色彩模型,如palette16、ycbcr等。 |
frameNumber | 帧数,gif 图片会返回此项 |
简介
EXIF(EXchangeable Image File Format) 是专门为数码相机的照片设定的可交换图像文件格式。
限制说明
接口规格
exif
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
获取图片 EXIF 信息
https://dora-doc.qiniu.com/gogopher.jpg?exif
返回结果(内容经过格式化以便阅读):
{
"DateTime" : {
"type" : 2,
"val" : "2011:11:19 17:09:23"
},
"ExposureBiasValue" : {
"type" : 10,
"val" : "0.33 EV"
},
"ExposureTime" : {
"type" : 5,
"val" : "1/50 sec."
},
"Model" : {
"type" : 2,
"val" : "Canon EOS 600D"
},
"ISOSpeedRatings" : {
"type" : 3,
"val" : "3200"
},
"ResolutionUnit" : {
"type" : 3,
"val" : " 英寸"
},
...后续内容已省略...
}
简介
将图片生成圆角图片,并且可以指定图片的圆角大小。
限制说明
gif
最大帧数为 200w
和 h
参数不能超过3万像素,总像素不能超过1.5亿像素w
和 h
参数不能超过9999像素,总像素不得超过24999999(2500w-1)像素gif
、webp动图
,最大帧数为 500w
和 h
不能超过3万像素,总像素不能超过1.5亿像素w
和 h
不能超过14999像素,总像素不得超过59999999(6000w-1)像素
接口规格处理后图片文件大小没有限制
roundPic/radius/<radius>
/radiusx/<radiusx>
/radiusy/<radiusy>
参数名称 | 必填 | 说明 |
---|---|---|
/radius/<radius> | 否 | 圆角大小的参数,水平和垂直的值相同,可以使用像素数(如200)或百分比(如!25p)。不能与radiusx和radiusy同时使用。 |
/radiusx/<radiusx> | 否 | 圆角水平大小的参数,可以使用像素数(如200)或百分比(如!25p)。需要与radiusy同时使用。 |
/radiusy/<radiusy> | 否 | 圆角垂直大小的参数,可以使用像素数(如200)或百分比(如!25p)。需要与radiusx同时使用 |
注意:
使用示例
原图
https://dora-doc.qiniu.com/gogopher.jpg
把一张图片变成50度圆角:
https://dora-doc.qiniu.com/gogopher.jpg?roundPic/radius/50
把一张图片变成圆形:
https://dora-doc.qiniu.com/gogopher.jpg?roundPic/radius/99999999999
简介
给图片添加水印
限制说明
gif
最大帧数为 200w
和 h
参数不能超过3万像素,总像素不能超过1.5亿像素w
和 h
参数不能超过9999像素,总像素不得超过24999999(2500w-1)像素gif
、webp动图
,最大帧数为 500w
和 h
不能超过3万像素,总像素不能超过1.5亿像素w
和 h
不能超过14999像素,总像素不得超过59999999(6000w-1)像素
接口规格处理后图片文件大小没有限制
watermark/1
/format/<Format>
/image/<encodedimageurl>
/dissolve/<dissolve>
/gravity/<gravity>
/dx/<distanceX>
/dy/<distanceY>
/ws/<watermarkScale>
/wst/<watermarkScaleType>
参数名称 | 必填 | 说明 |
---|---|---|
/format/<Format> | 图片输出格式 1、取值:heic 或 avif ,属于 图片高级压缩 格式,按照图片高级压缩收费,当前仅支持 持久化处理 来使用。 2、取值:copy,保持原图格式输出。 注意: ● 当原图为heic 或 avif 时,设置format/copy 会保持原图格式不变,且不按照图片高级压缩收费;如果不指定 format 参数,将会输出jpeg 。 | |
/image/<encodedimageurl> | 是 | 水印图片地址,均需要经过URL安全的Base64编码。 注意:更换图片水印时,建议更换图片的文件名。 |
/dissolve/<dissolve> | 透明度,取值范围1-100,默认值为100(完全不透明)。 | |
/gravity/<gravity> | 水印位置,参考水印锚点参数表,默认值为SouthEast(右下角)。 | |
/dx/<distanceX> | 横轴边距,单位:像素(px),默认值为10。 | |
/dy/<distanceY> | 纵轴边距,单位:像素(px),默认值为10。 | |
/ws/<watermarkScale> | 水印图片自适应原图的比例,ws的取值范围为0-1。具体是指水印图片保持原比例,按照自适应原图的类型wst,比如默认是按照短边,则水印图片短边=原图短边*ws。 | |
/wst/<watermarkScaleType> | 水印图片自适应原图的类型,取值0、1、2、3分别表示为自适应原图的短边、长边、宽、高,默认值为0。 |
例如:
水印锚点参数表
NorthWest | North | NorthEast
| |
| |
--------------+----------------+--------------
| |
West | Center | East
| |
--------------+----------------+--------------
| |
| |
SouthWest | South | SouthEast
使用示例
原图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher.png
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/uniCloud.jpg
aHR0cHM6Ly93ZWItZXh0LXN0b3JhZ2UuZGNsb3VkLm5ldC5jbi91bmljbG91ZC9leHQtc3RvcmFnZS91bmlDbG91ZC5qcGc=
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher.png?watermark/1/image/aHR0cHM6Ly93ZWItZXh0LXN0b3JhZ2UuZGNsb3VkLm5ldC5jbi91bmljbG91ZC9leHQtc3RvcmFnZS91bmlDbG91ZC5qcGc=/dissolve/50/gravity/SouthEast/dx/20/dy/20/ws/0.2
效果展示
简介
肉眼可见的水印方式,一般用于标志图片的出处或者用于声明版权,会破坏原图,且影响美观。扩展存储提供盲水印功能,隐蔽性强,在不破坏原始作品的情况下,实现版权的防护与追踪。当图片被盗用后,您可对图片进行盲水印解码,验证版权归属。
限制说明
原图限制:
编码、解码的 version 要相同
盲水印主要为CPU密集型任务,处理时间会随着图片文件的增大而增大
建议使用 version/3,相较于之前的版本有以下优化:
隐形美观:盲水印是一种肉眼不可见的水印方式,可以保持图片美观的同时,保护您的资源版权。
版权保护:对图片资源使用图片盲水印或者文字水印,借此避免数字媒体未经授权的复制和拷贝,可通过对原图进行解码操作,得到水印图来证明版权归属。
防泄漏:对于内部分享的图片资源,您可使用盲水印功能,加上不同标识,如果资料被复制、传播可根据解码出的唯一标识得出泄露方信息。
接口规格
watermark/5
/version/<version>
/method/<method>
/imageKey/<encodedImageKey>
/orignal/<encodedImage>
添加水印
version 为1或2时,请求参数说明如下:
名称 | 必填 | 说明 |
---|---|---|
/version/<version> | N | 接口版本值为1或2 , 默认为 1。 |
/method/<method> | Y | 盲水印标志位,编码(添加水印)为encode。 |
/imageKey/<encodedImageKey> | N | 水印图片(经过URL安全的Base64编码) 例如:imageKey=upload.png,encodedImageKey=urlsafe_base64_encode(imageKey)。 注意:水印图片宽高分别不超过原图宽高的二分之一,图片为黑底白字效果更佳。 |
version 为3时,请求参数说明如下:
名称 | 必填 | 说明 |
---|---|---|
/version/<version> | N | 接口版本值为3,注意: 1)原图的宽高都需大于512。 2)水印图片的限制,必须是二值图像,且水印的大小为64x64,如果不符合条件, 会缩放和处理图片到服务要求。 |
/method/<method> | Y | 盲水印标志位,编码(即添加水印)为encode。 |
/imageKey/<encodedImageKey> | N | 水印图片(经过URL安全的Base64编码) 例如:imageKey=upload.png,encodedImageKey=urlsafe_base64_encode(imageKey) |
提取水印
version 为1或2时,请求参数说明如下:
名称 | 必填 | 说明 |
---|---|---|
/version/<version> | N | 接口版本值为1或2 , 默认为 1。 |
/method/<method> | Y | 盲水印标志位,解码(即提取水印)为decode。 |
/orignal/<encodedImage> | N | 解码对比原图(经过URL安全的Base64编码) |
version 为3时,请求参数说明如下:
名称 | 必填 | 说明 |
---|---|---|
/version/<version> | N | 接口版本值为3,注意:原图的宽高都需大于512。 |
/method/<method> | Y | 盲水印标志位,解码(即提取水印)为decode |
使用示例
原图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png
编码/ 添加盲水印(version/3)
unicloud/ext-storage/uniCloud.png
dW5pY2xvdWQvZXh0LXN0b3JhZ2UvdW5pQ2xvdWQucG5n
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png?watermark/5/version/3/method/encode/imageKey/dW5pY2xvdWQvZXh0LXN0b3JhZ2UvdW5pQ2xvdWQucG5n
效果展示
解码/ 提取盲水印(version/3)
unicloud/ext-storage/uniCloud.png
dW5pY2xvdWQvZXh0LXN0b3JhZ2UvdW5pQ2xvdWQucG5n
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png?watermark/5/version/3/method/encode/imageKey/dW5pY2xvdWQvZXh0LXN0b3JhZ2UvdW5pQ2xvdWQucG5n|watermark/5/version/3/method/decode
效果展示
编码/ 添加盲水印(version/2)
unicloud/ext-storage/u.png
dW5pY2xvdWQvZXh0LXN0b3JhZ2UvdW5pQ2xvdWQucG5n
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png?watermark/5/version/2/method/encode/imageKey/dW5pY2xvdWQvZXh0LXN0b3JhZ2UvdS5wbmc=
效果展示
解码/ 提取盲水印(version/2)
unicloud/ext-storage/u.png
dW5pY2xvdWQvZXh0LXN0b3JhZ2UvdW5pQ2xvdWQucG5n
unicloud/ext-storage/gogopher1.png
dW5pY2xvdWQvZXh0LXN0b3JhZ2UvZ29nb3BoZXIxLnBuZw==
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png?watermark/5/version/2/method/encode/imageKey/dW5pY2xvdWQvZXh0LXN0b3JhZ2UvdS5wbmc=|watermark/5/version/2/method/decode/orignal/dW5pY2xvdWQvZXh0LXN0b3JhZ2UvZ29nb3BoZXIxLnBuZw==
效果展示
接口规格
watermark/6
/version/<version>
/method/<method>
/text/<encodedText>
/orignal/<encodedImage>
添加水印
version 为1或2时,请求参数说明如下:
名称 | 必填 | 说明 |
---|---|---|
/version/<version> | N | 接口版本值为1或2 , 默认为 1。 |
/method/<method> | Y | 盲水印标志位,编码(添加水印)为encode。 |
/text/<encodedText> | N | 水印文字(经过URL安全的Base64编码)。只支持英文数字字符,不支持中文字符,数量上限分别为 10。 |
version 为3时,请求参数说明如下:
名称 | 必填 | 说明 |
---|---|---|
/version/<version> | N | 接口版本值为3,注意: 1)原图的宽高都需大于512。 2)水印图片的限制,必须是二值图像,且水印的大小为64x64,如果不符合条件, 会缩放和处理图片到服务要求。 |
/method/<method> | Y | 盲水印标志位,编码(即添加水印)为encode。 |
/text/<encodedText> | N | 水印文字(经过URL安全的Base64编码)。只支持英文数字字符,不支持中文字符,数量上限分别为 15。 |
提取水印
version 为1或2时,请求参数说明如下:
名称 | 必填 | 说明 |
---|---|---|
/version/<version> | N | 接口版本值为1或2 , 默认为 1。 |
/method/<method> | Y | 盲水印标志位,解码(即提取水印)为decode。 |
/orignal/<encodedImage> | N | 解码对比原图(经过URL安全的Base64编码) |
version 为3时,请求参数说明如下:
名称 | 必填 | 说明 |
---|---|---|
/version/<version> | N | 接口版本值为3,注意:原图的宽高都需大于512。 |
/method/<method> | Y | 盲水印标志位,解码(即提取水印)为decode |
使用示例
原图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png
编码/ 添加盲水印(version/3)
123456
MTIzNDU2
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png?watermark/6/version/3/method/encode/text/MTIzNDU2
效果展示
解码/ 提取盲水印(version/3)
123456
MTIzNDU2
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png?watermark/6/version/3/method/encode/text/MTIzNDU2|watermark/6/version/3/method/decode
效果展示
编码/ 添加盲水印(version/2)
123456
MTIzNDU2
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png?watermark/6/version/2/method/encode/text/MTIzNDU2
效果展示
解码/ 提取盲水印(version/2)
123456
MTIzNDU2
unicloud/ext-storage/gogopher1.png
dW5pY2xvdWQvZXh0LXN0b3JhZ2UvZ29nb3BoZXIxLnBuZw==
最终拼接得到图片URL如下
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher1.png?watermark/6/version/2/method/encode/text/MTIzNDU2|watermark/6/version/2/method/decode/orignal/dW5pY2xvdWQvZXh0LXN0b3JhZ2UvZ29nb3BoZXIxLnBuZw==
效果展示
简介
动图合成接口(animate)用于将多张图片合成 GIF 动图。
限制说明
接口规格
animate/duration/<duration>
/merge/key/<encodedImageKey>
/key/<encodedImageKey>
...
/effect/<effectType>
参数说明
名称 | 必填 | 说明 |
---|---|---|
<duration> | Y | GIF动图的每帧间隔时间(单位: 0.01s),取值要求为大于0的整数。 |
<encodedImageKey> | N | 合成GIF动图的源图片key需要经过 URL安全的Base64编码 ,且保证所有的源图都来源于同一个bucket。 |
<effectType> | N | 定义播放顺序,取值:0,1。(0:正序循环播放;1:倒序循环播放;)默认为0 |
第一张图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/1.png
第二张图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/2.png
第三张图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/3.png
三张图合成一张动图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/1.png?animate/duration/10/merge/key/dW5pY2xvdWQvZXh0LXN0b3JhZ2UvMi5wbmc=/key/dW5pY2xvdWQvZXh0LXN0b3JhZ2UvMy5wbmc=
图片处理部分API会涉及到 经过URL安全的Base64编码
,具体算法如下
云端代码示例
function urlsafeBase64Encode(text) {
let encoded = Buffer.from(text).toString('base64');
return encoded.replace(/\//g, '_').replace(/\+/g, '-');
};
let base64 = urlsafeBase64Encode("test/a.jpg");
console.log('base64: ', base64);
前端代码示例(仅H5端可用)
function urlsafeBase64Encode(text) {
// 使用window.btoa进行Base64编码,得到的是URL安全的Base64字符串
let encoded = window.btoa(text);
// 由于window.btoa已经使用_和-代替了+和/,所以不需要额外替换
return encoded;
};
let base64 = urlsafeBase64Encode("test/a.jpg");
console.log('base64: ', base64);
在调用 getTempFileURL 的时候,fileList 内带上图片处理参数即可
云端代码
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu",
domain: "example.com", // 域名地址
});
let res = extStorageManager.getTempFileURL({
fileList: ["qiniu://test.jpg?imageMogr2/thumbnail/!20p"], // 文件地址列表
});
console.log('getTempFileURL: ', res);
return res;
扩展存储支持上传的音视频自动进行转码(无需调用API),查看音视频转码费用
开启音视频转码功能需要进 扩展存储技术交流群 申请发送文字:我想申请开通扩展存储音视频转码功能,我的转码类型是“普通转码(H.264)”
简介
视频单帧缩略图接口(vframe)用于从视频流中截取指定时刻的单帧画面并按指定大小缩放成图片。
接口规格
vframe/<Format>
/offset/<Second>
/w/<Width>
/h/<Height>
/rotate/<Degree>
注意:
w
而不指定 h
时,缩略图的高度将等比缩放;当指定 h
而不指定 w
时,缩略图的宽度将等比缩放。w
与 h
中,长边取值范围 [20,3840],短边取值范围 [20,2160]。参数说明
参数名称 | 必填 | 说明 |
---|---|---|
<Format> | 是 | 输出的目标截图格式,支持jpg、png等。 |
/offset/<Second> | 是 | 指定截取视频的时刻,单位:秒,精确到毫秒。 |
/w/<Width> | 否 | 缩略图宽度,单位:像素(px) |
/h/<Height> | 否 | 缩略图高度,单位:像素(px) |
/rotate/<Degree> | 否 | 指定顺时针旋转的度数,可取值为90、180、270、auto,默认为不旋转 |
注意:建议视频文件不能太大,举例用户设置该接口的超时时间为10s,那么同步处理的视频文件最好不超过450MB ,否则可能会超时导致处理失败。
原视频
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/test.mp4
取视频第2秒的图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/test.mp4?vframe/jpg/offset/2/
取视频第2秒的图,宽度为480px,高度为360px:
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/test.mp4?vframe/jpg/offset/2/w/480/h/360
取视频第60秒的图
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/test.mp4?vframe/jpg/offset/60
简介
音视频元信息接口(avinfo)用于获取指定音频、视频资源的元信息
接口规格
avinfo
原视频
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/test.mp4
获取音视频元信息
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/test.mp4?avinfo
返回结果
{
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
"profile": "Baseline",
"codec_type": "video",
"codec_time_base": "1/60",
"codec_tag_string": "avc1",
"codec_tag": "0x31637661",
"width": 1280,
"height": 720,
"coded_width": 1280,
"coded_height": 720,
"has_b_frames": 0,
"sample_aspect_ratio": "1:1",
"display_aspect_ratio": "16:9",
"pix_fmt": "yuv420p",
"level": 41,
"color_range": "tv",
"chroma_location": "left",
"refs": 1,
"is_avc": "true",
"nal_length_size": "4",
"r_frame_rate": "30/1",
"avg_frame_rate": "30/1",
"time_base": "1/30000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 22526000,
"duration": "750.866667",
"bit_rate": "89992",
"bits_per_raw_sample": "8",
"nb_frames": "22526",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
},
"tags": {
"creation_time": "2020-03-15T13:54:39.000000Z",
"language": "eng",
"handler_name": "Mainconcept MP4 Video Media Handler",
"encoder": "AVC Coding"
}
},
{
"index": 1,
"codec_name": "aac",
"codec_long_name": "AAC (Advanced Audio Coding)",
"profile": "LC",
"codec_type": "audio",
"codec_time_base": "1/44100",
"codec_tag_string": "mp4a",
"codec_tag": "0x6134706d",
"sample_fmt": "s16p",
"sample_rate": "44100",
"channels": 2,
"channel_layout": "stereo",
"bits_per_sample": 0,
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/44100",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 33115136,
"duration": "750.910113",
"bit_rate": "125663",
"max_bit_rate": "236003",
"nb_frames": "32339",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
},
"tags": {
"creation_time": "2020-03-15T13:54:39.000000Z",
"language": "eng",
"handler_name": "Mainconcept MP4 Sound Media Handler"
}
}
],
"format": {
"nb_streams": 2,
"nb_programs": 0,
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"format_long_name": "QuickTime / MOV",
"start_time": "0.000000",
"duration": "750.910111",
"size": "20503129",
"bit_rate": "218434",
"probe_score": 100,
"tags": {
"major_brand": "mp42",
"minor_version": "0",
"compatible_brands": "isommp42",
"creation_time": "2020-03-15T13:54:38.000000Z"
}
}
}
简介
资源下载二维码生成功能(qrcode),用于为存放在七牛云存储上的资源的下载URL或资源内容生成二维码图片,方便用户在各种客户端之间传播资源。
所生成的二维码图片格式为png。
接口规格
qrcode/level/<Level>
参数名称 | 说明 | 必填 |
---|---|---|
/level/<Level> | 冗余度,可选值L(7%)、M(15%)、Q(25%),H(30%),默认为L。 |
其中 <DownloadURL>
代表资源的原始下载 URL
注意:L是最低级别的冗余度,H最高。提高冗余度,较大可能会使生成图片总像素变多。
原视频文件
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/test.mp4
生成文件对应的二维码,扫码二维码即可直接观看视频
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/test.mp4?qrcode
原图片文件
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher.png
扫码二维码查看图片内容
https://web-ext-storage.dcloud.net.cn/unicloud/ext-storage/gogopher.png?qrcode
申请方式:在 扩展存储技术交流群 里发送内容,我想申请七牛云CDN额外防御:同一个IP在1分钟内,同一个域名下,CDN单节点下载文件次数超过200次,封禁该IP 1小时,1小时后自动解封
提示:通过 getDomains
和 getCdnTop
这两个API,结合云函数定时任务可以自己实现CDN流量监控告警功能,该告警功能的定时任务代码模板如下所示
安装教程
ext-storage-cron
注意
'use strict';
const config = {
frequency: 0, // 执行频率,单位:分钟,可选值:0、15、30、60(只能是这4个值的其中一个,设置为0也代表每15分钟执行一次)
traffic: 10, // 单个IP 每日流量阈值(单位:GB)如果设置为0代表不限制
count: 1000000, // 单个IP 每日访问次数阈值(单位:次)如果设置为0代表不限制
sms: {
appid: "__UNI__XXXXXXX", // 修改成你的manifest.json里的appid
phone: "15200000001", // 修改成你自己的手机号
templateId: "uni_cdn_warn", // 无需修改,当然也可以改成你自己的短信模板id(uni_cdn_warn为公共短信模板id,无需申请即可使用,发送频率限制为10分钟内最大1次)
},
db: {
sendLog: "opendb-open-data", // 无需修改,当然也可以改成你自己的数据表名称
}
};
/**
* 发送告警信息函数,目前用的是发送短信,你也可以自己更改为发送邮件、微信公众号模板消息等其他方式
* @param {Object} list 告警列表
* @param {Object} requestId 本次定时任务的请求id(方便在云函数那查询日志)
*/
async function sendWarn(list, requestId) {
console.log("准备发送告警信息");
for (let i = 0; i < list.length; i++) {
console.log(`正在发送告警信息,当前进度${i+1}/${list.length}`);
let {
type, // 告警类型 traffic 流量 count 访问次数
domain, // 域名
ip, // IP
traffic, // 流量(单位:GB)
count, // 访问次数(单位:次)
} = list[i];
let logInfo = await getSendLog({ ip });
// 同一个IP在24小时内只发送一次告警信息
if (logInfo && logInfo.value && logInfo.value.send_time && Date.now() < (logInfo.value.send_time + 24 * 3600 * 1000)) {
console.log("该IP 24小时内已经发送过告警信息,不再发送");
continue;
}
try {
let sendSmsRes = await uniCloud.sendSms({
...config.sms,
data: {}
});
console.log('sendSmsRes: ', sendSmsRes);
if (sendSmsRes.errCode === 0) {
// 发送成功,记录该IP的发送时间
await setSendLog({
type,
domain,
ip,
traffic,
count,
});
}
} catch (err) {
// 调用失败
console.log("发送短信失败:", err)
}
}
}
// 以上代码可以修改-----------------------------------------------------------
// 以下代码无需修改-----------------------------------------------------------
// 查询发送告警信息的时间
async function getSendLog(data) {
try {
let {
ip, // IP
} = data;
const db = uniCloud.database();
let nowTime = Date.now();
let dbRes = await db.collection(config.db.sendLog).doc(`ext-storage-warn:${ip}`).get();
return dbRes.data[0];
} catch (err) {
console.error('err: ', err)
return;
}
}
// 记录发送告警信息的时间
async function setSendLog(data) {
try {
let {
type, // 告警类型 traffic 流量 count 访问次数
domain, // 域名
ip, // IP
traffic, // 流量(单位:GB)
count, // 访问次数(单位:次)
} = data;
const db = uniCloud.database();
let nowTime = Date.now();
let logInfo = await getSendLog({ ip });
if (!logInfo) {
logInfo = {
value: {
type,
domain,
ip,
traffic,
count,
send_count: 0,
},
expired: 0
}
}
logInfo.value.send_time = nowTime;
if (!logInfo.value.send_count) logInfo.value.send_count = 0;
logInfo.value.send_count++; // 记录该IP历史累加发送次数
delete logInfo._id;
// 1个IP只会存在一条记录
return await db.collection(config.db.sendLog).doc(`ext-storage-warn:${ip}`).set(logInfo);
} catch (err) {
console.error('err: ', err)
return;
}
}
// 查询今日CDN流量top100
const extStorageManager = uniCloud.getExtStorageManager({
provider: "qiniu", // 扩展存储供应商
domain: "https://www.domain.com", // 此处的域名无需更改
});
exports.main = async (event, context) => {
let res = {};
let nowTime = Date.now();
// 获取当前分钟数
let minute = new Date(nowTime).getMinutes();
if (!config) {
throw new Error(`未找到相关配置信息`);
}
if (config.frequency > 0 && minute % config.frequency !== 0) {
console.log("未到执行时间");
return "未到执行时间";
}
// 时区8小时
let offset = 8 * 3600 * 1000;
let startDate = new Date(nowTime + offset).toISOString().split("T")[0];
let endDate = new Date(nowTime + offset).toISOString().split("T")[0];
// 获取我绑定的域名列表
let { domains = [] } = await extStorageManager.getDomains();
// 循环domains数组,查询每个域名的CDN流量top100
console.log(`当前查询日期:${startDate}`)
let warnList = [];
for (let i = 0; i < domains.length; i++) {
let domain = domains[i];
console.log(`正在查询域名:${domain},当前进度${i+1}/${domains.length}`);
let getCdnTopRes = await getCdnTop({
domain,
startDate,
endDate,
requestId: context.request_id
});
warnList = warnList.concat(getCdnTopRes);
}
let warnListStr = warnList.map(item => `${item.domain}(${item.ip} : ${item.traffic}GB : ${item.count}次)`).join("、");
let msg = `共查询了${domains.length}个域名,触发告警的IP有${warnList.length}个`;
if (warnList.length > 0) {
msg += `,分别是:${warnListStr}`;
}
console.log('执行完毕: ', msg);
res.msg = msg;
return res;
};
async function getCdnTop(data = {}) {
let { domain, startDate, endDate, requestId } = data;
let getCdnTopRes = await extStorageManager.getCdnTop({
type: 2, // 1 topURL 2 topIP
domains: [domain],
startDate,
endDate
});
let list = getCdnTopRes.data;
//console.log('list: ', list)
let warnList = [];
let totalTraffic = 0;
let totalCount = 0;
for (let i = 0; i < list.length; i++) {
let item = list[i];
let traffic = parseFloat((item.traffic / 1024 / 1024 / 1024).toFixed(2)); // 流量(单位:GB)
let count = item.count; // 访问次数(单位:次)
totalTraffic += item.traffic;
totalCount += count;
if (config.traffic > 0 && traffic >= config.traffic) {
// 流量告警
console.log(`域名${domain}触发流量告警,告警IP为:${item.ip}`)
warnList.push({
type: "traffic",
domain,
ip: item.ip,
traffic,
count
})
} else if (config.count > 0 && count >= config.count) {
// 访问次数告警
console.log(`域名${domain}触发访问次数告警,告警IP为:${item.ip}`)
warnList.push({
type: "count",
domain,
ip: item.ip,
traffic,
count
})
}
}
if (totalTraffic > 0 || totalCount > 0) {
console.log(`域名${domain} TOP100 总流量:${parseFloat((totalTraffic / 1024 / 1024 / 1024).toFixed(6))}GB,共请求${totalCount}次`);
}
if (warnList.length > 0) {
await sendWarn(warnList, requestId);
}
return warnList;
}
{
"name": "ext-storage-cron",
"extensions": {
"uni-cloud-sms": {},
"uni-cloud-ext-storage": {}
},
"cloudfunction-config": {
"concurrency": 1,
"memorySize": 512,
"path": "",
"timeout": 600,
"triggers": [
{
"name": "ext-storage-cron",
"type": "timer",
"config": "0 */15 * * * * *"
}
],
"runtime": "Nodejs16"
}
}
ext-storage-cron
,完成上传成功后,每15分钟定时任务会启动一次,可前往云函数 ext-storage-cron
查看运行日志
通常是因为空间没有开通扩展存储导致的,前往开通教程,如果确定已经开通,则可尝试重启项目并重新上传相关云函数。
出现这个错误时,请依次执行以下操作
可以通过云函数或云对象开启URL化的方式来获取前端上传参数,拿到上传参数后即可通过uni.uploadFile(或其他三方请求库)上传文件
具体步骤
ext-storage-co
,其中 index.obj.js
代码如下(如果已经有了,则忽略此步骤)const provider = "qiniu";
module.exports = {
_before: function() {
},
getUploadFileOptions(data = {}) {
let {
cloudPath,
domain,
} = data;
// 可以在此先判断下此路径是否允许上传等逻辑
// ...
let prefix = "public/"; // 上传文件的目录前缀,比如上传到 public 目录下,这里填写 public/
// 然后获取 extStorageManager 对象实例
const extStorageManager = uniCloud.getExtStorageManager({
provider, // 扩展存储供应商
domain, // 自定义域名
});
// 最后调用 extStorageManager.getUploadFileOptions
let uploadFileOptionsRes = extStorageManager.getUploadFileOptions({
cloudPath: prefix + cloudPath, // 上传文件的云端路径
allowUpdate: false, // 是否允许覆盖更新,如果会返回给前端,建议设置false,代表仅新增,不可覆盖(防止文件被意外替换),如果仅服务端直接交互,可以根据业务需要设置为true
});
return uploadFileOptionsRes;
}
}
package.json
文件,内容如下{
"name": "ext-storage-co",
"dependencies": {},
"extensions": {
"uni-cloud-ext-storage": {}
},
"cloudfunction-config": {
"concurrency": 1,
"memorySize": 512,
"keepRunningAfterReturn": false,
"path": "/http/ext-storage-co",
"timeout": 60,
"triggers": [],
"runtime": "Nodejs16"
}
}
ext-storage-co
请求地址为:刚复制URL化地址 + /getUploadFileOptions
如 https://fc-mp-xxxx.next.bspapp.com/http/ext-storage-co/getUploadFileOptions
下方的代码用了 axios 作为请求库,这里需要替换成你自己用的请求库
// TODO 这里的 tokenUrl 需要改成你的自己的云对象 ext-storage-co 的 getUploadFileOptions 的URL化地址
let tokenUrl = 'https://fc-mp-xxxx.next.bspapp.com/http/ext-storage-co/getUploadFileOptions';
axios({
method: "GET",
url: tokenUrl,
params: {
domain: "cdn.xxx.com", // TODO 改成你七牛绑定的域名
cloudPath: `test/${Date.now()}.jpg`, // 文件路径(可自己定义文件名规则)
}
})
.then((res) => {
let uploadFileOptionsRes = res.data;
console.log('then-uploadFileOptionsRes: ', uploadFileOptionsRes)
let url = uploadFileOptionsRes.uploadFileOptions.url; // 上传地址
let name = uploadFileOptionsRes.uploadFileOptions.name; // 上传文件名
let token = uploadFileOptionsRes.uploadFileOptions.formData.token; // 上传token
let key = uploadFileOptionsRes.uploadFileOptions.formData.key; // 上传key
// 创建一个 FormData 对象
const formData = new FormData();
formData.append('file', file);
formData.append('token', token);
formData.append('key', key);
// 发送请求
axios.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
let percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`上传进度:${percentCompleted}%` );
}
})
.then((uploadRes) => {
// 处理成功情况
const res = {
cloudPath: uploadFileOptionsRes.cloudPath, // 文件云端路径
fileID: uploadFileOptionsRes.fileID, // 文件ID
fileURL: uploadFileOptionsRes.fileURL, // 文件URL(如果是私有权限,则此URL是无法直接访问的)
fileInfo: typeof uploadRes.data === "string" ? JSON.parse(uploadRes.data) : uploadRes.data,
};
// 数据库里可直接保存 fileURL 或 fileID
console.log("上传成功", res);
console.log("fileURL", res.fileURL);
console.log("fileID", res.fileID);
})
.catch((err) => {
// 处理错误情况
console.log("上传失败", err);
});
})
.catch((err) => {
console.log("catch", err);
});
由于扩展存储不支持前端直传,而是需要先通过云函数或云对象来获取上传凭证,具有较高的安全性,但也因此导致上传代码与内置存储不同,老项目想使用扩展存储就要改动原有上传代码,那么有没有方案可以做到在不改原有上传代码的基础上,老项目也能很方便的使用扩展存储呢?
答案是可以的,按如下步骤操作。
开通扩展存储 开通教程
在你的项目根目录的 /js_sdk/ext-storage/
目录新建文件 uploadFileForExtStorage.js
(没有 /js_sdk/ext-storage/
目录就新建目录)
uploadFileForExtStorage.js 文件复制下方的代码
/**
* 设置 uniCloud.uploadFile 默认上传到扩展存储
* @param {String} provider 云储存供应商
* @value unicloud 内置存储
* @value extStorage 扩展存储
* @param {String} domain 自定义域名,仅扩展存储有效
* @param {Boolean} fileID2fileURL 是否将fileID转为fileURL
* @param {Function} uploadFileOptions 获取上传参数的函数,仅扩展存储有效
*/
function init(options = {}) {
let {
provider: defaultProvider,
} = options;
let originalDefaultProvider = defaultProvider;
let extStorage = new ExtStorage(options);
const uploadFile = uniCloud.uploadFile;
uniCloud.uploadFile = (...e) => {
let options = e[0] || {};
let {
provider = defaultProvider
} = options;
if (provider === "extStorage") {
return extStorage.uploadFile(...e);
} else {
return uploadFile(...e);
}
}
const getTempFileURL = uniCloud.getTempFileURL;
uniCloud.getTempFileURL = (...e) => {
let options = e[0] || {};
let {
provider = defaultProvider
} = options;
if (provider === "extStorage") {
return extStorage.getTempFileURL(...e);
} else {
return getTempFileURL(...e);
}
}
const deleteFile = uniCloud.deleteFile;
uniCloud.deleteFile = (...e) => {
let options = e[0] || {};
let {
provider = defaultProvider
} = options;
if (provider === "extStorage") {
return extStorage.deleteFile(...e);
} else {
return deleteFile(...e);
}
}
uniCloud.setCloudStorage = (data={}) => {
let {
provider,
domain,
fileID2fileURL,
} = data;
if (provider === null) {
defaultProvider = originalDefaultProvider;
} else if (provider) {
defaultProvider = provider;
}
if (domain) extStorage.domain = domain;
if (fileID2fileURL) extStorage.fileID2fileURL = fileID2fileURL;
}
}
export default {
init
}
class ExtStorage {
constructor(data = {}) {
let {
uploadFileOptions,
domain,
fileID2fileURL
} = data;
this.uploadFileOptions = uploadFileOptions;
this.domain = domain;
this.fileID2fileURL = fileID2fileURL;
}
// 上传文件
uploadFile(options) {
let {
filePath,
cloudPath,
} = options;
const promiseRes = new Promise(async (resolve, reject) => {
try {
const uploadFileOptionsRes = await this.uploadFileOptions({
cloudPath,
domain: this.domain
});
const uploadTask = uni.uploadFile({
...uploadFileOptionsRes.uploadFileOptions, // 上传文件所需参数
filePath, // 本地文件路径
success: (uploadFileRes) => {
if (uploadFileRes.statusCode !== 200) {
const err = uploadFileRes;
if (typeof options.fail === "function") options.fail(err);
reject(err);
} else {
const res = {
cloudPath: uploadFileOptionsRes.cloudPath, // 文件云端路径
fileID: uploadFileOptionsRes.fileID, // 文件ID
fileURL: uploadFileOptionsRes.fileURL, // 文件URL(如果是私有权限,则此URL是无法直接访问的)
};
if (this.fileID2fileURL) {
res.fileID = `https://${this.domain}/${res.cloudPath}`;
}
if (typeof options.success === "function") options.success(res);
resolve(res);
}
},
fail: (err) => {
if (typeof options.fail === "function") options.fail(err);
reject(err);
},
complete: () => {
if (typeof options.complete === "function") options.complete();
}
});
// 监听上传进度
uploadTask.onProgressUpdate((progressEvent) => {
if (typeof options.onUploadProgress === "function") {
const total = progressEvent.totalBytesExpectedToSend;
const loaded = progressEvent.totalBytesSent;
const progress = Math.round(loaded * 100 / total);
options.onUploadProgress({
total,
loaded,
progress
});
}
});
} catch (err) {
if (typeof options.fail === "function") options.fail(err);
reject(err);
if (typeof options.complete === "function") options.complete();
}
});
promiseRes.catch(() => {
});
return promiseRes;
}
// 获取临时文件下载地址
getTempFileURL(options = {}) {
let {
fileList
} = options;
return new Promise((resolve, reject) => {
let res = {
fileList: fileList.map((item, index) => {
let cloudPath = getCloudPath(item);
return {
fileID: item,
tempFileURL: `https://${this.domain}/${cloudPath}`
}
})
}
if (typeof options.success === "function") options.success(res);
resolve(res);
if (typeof options.complete === "function") options.complete();
});
}
// 删除文件
deleteFile(options = {}) {
// 扩展存储不允许前端删除文件(故此处直接返回)
return new Promise((resolve, reject) => {
let res = {
fileList: []
};
if (typeof options.success === "function") options.success(res);
resolve(res);
if (typeof options.complete === "function") options.complete();
});
}
}
function getCloudPath(cloudPath) {
const qiniuPrefix = 'qiniu://';
if (cloudPath.indexOf(qiniuPrefix) === 0) {
cloudPath = cloudPath.substring(qiniuPrefix.length);
} else if (cloudPath.indexOf('http://') === 0 || cloudPath.indexOf('https://') === 0) {
let startIndex = cloudPath.indexOf('://') + 3;
startIndex = cloudPath.indexOf('/', startIndex);
let endIndex = cloudPath.indexOf('?') === -1 ? cloudPath.length : cloudPath.indexOf('?');
endIndex = cloudPath.indexOf('#') !== -1 && cloudPath.indexOf('#') < endIndex ? cloudPath.indexOf('#') : endIndex;
cloudPath = cloudPath.substring(startIndex + 1, endIndex);
}
return cloudPath
}
App.vue
的 <script>
下面且是 export default {
的上面,新增以下代码import uploadFileForExtStorage from "@/js_sdk/ext-storage/uploadFileForExtStorage.js"
App.vue
的 onLaunch
函数中新增以下代码// 设置 uniCloud.uploadFile 默认上传的云存储供应商
uploadFileForExtStorage.init({
provider: "extStorage", // provider代表默认上传到哪,可选项 "unicloud" 内置存储; "extStorage" 扩展存储;
domain: "cdn.example.com", //【重要】这里需要改成你开通扩展存储时绑定的自定义域名)
fileID2fileURL: true, // 将fileID转成fileURL,方便兼容老项目
// 获取上传参数的函数
uploadFileOptions: async (event) => {
// ext-storage-co 是你自己写的云对象,参考代码:https://doc.dcloud.net.cn/uniCloud/ext-storage/dev.html#getuploadfileoptions
const uniCloudStorageExtCo = uniCloud.importObject("ext-storage-co");
return await uniCloudStorageExtCo.getUploadFileOptions(event);
}
});
App.vue
完整示例
<script>
import uploadFileForExtStorage from "@/js_sdk/ext-storage/uploadFileForExtStorage.js"
export default {
onLaunch: function() {
console.log('App Launch');
// 设置 uniCloud.uploadFile 默认上传到扩展存储
uploadFileForExtStorage.init({
provider: "extStorage", // provider代表默认上传到哪,可选项 "unicloud" 内置存储; "extStorage" 扩展存储;
domain: "cdn.example.com", //【重要】这里需要改成你开通扩展存储时绑定的自定义域名)
fileID2fileURL: true, // 将fileID转成fileURL,方便兼容老项目
// 获取上传参数的函数
uploadFileOptions: async (event) => {
// ext-storage-co 是你自己写的云对象,参考代码:https://doc.dcloud.net.cn/uniCloud/ext-storage/dev.html#getuploadfileoptions
const uniCloudStorageExtCo = uniCloud.importObject("ext-storage-co");
return await uniCloudStorageExtCo.getUploadFileOptions(event);
}
});
},
onShow: function() {
console.log('App Show');
},
onHide: function() {
console.log('App Hide');
}
}
</script>
<style>
</style>
ext-storage-co
,其中 index.obj.js
代码如下const provider = "qiniu";
module.exports = {
_before: function() {
},
getUploadFileOptions(data = {}) {
let {
cloudPath,
domain,
} = data;
// 可以在此先判断下此路径是否允许上传等逻辑
// ...
// 然后获取 extStorageManager 对象实例
const extStorageManager = uniCloud.getExtStorageManager({
provider, // 扩展存储供应商
domain, // 自定义域名
});
// 最后调用 extStorageManager.getUploadFileOptions
let uploadFileOptionsRes = extStorageManager.getUploadFileOptions({
cloudPath: `public/${cloudPath}`, // 强制在public目录下
allowUpdate: false, // 是否允许覆盖更新,如果返回前端,建议设置false,代表仅新增,不可覆盖
});
return uploadFileOptionsRes;
}
}
ext-storage-co
,点击 管理公共模块或扩展库依赖
选择扩展库 uni-cloud-ext-storage
点确定ext-storage-co
如依然有问题,可进群反馈 扩展存储技术支持群
根据不同的存储服务商,下面列出了不同的迁移方案
实际场景
我的存储空间是在七牛云的官方账号上,我现在想迁移到uniCloud扩展存储,应该如何迁移?
迁移方案
因为扩展存储的服务商就是七牛云,因此可以直接使用七牛云自带的迁移工具直接迁移,但需要得到uniCloud扩展存储的空间授权,授权可前往扩展存储技术群申请
申请方式:在 扩展存储技术交流群 里发送内容,我想申请七牛云官方账号存储空间迁移到扩展存储
相关文档:七牛云跨区域迁移教程
实际场景
我用的是阿里云OSS(腾讯云COS或其他存储都可以),我想将Ta迁移到扩展存储,应该如何迁移?
迁移方案一
将需要迁移的存储内的文件全部下载到你的电脑上,然后通过 本地电脑文件快速批量上传到扩展存储 的方式上传到扩展存储
迁移方案二
可以申请将扩展存储的回源地址修改为你原先存储的源站地址,这样当用户访问文件时,扩展存储如果未找到文件会尝试从你原先的源站进行下载并保存到扩展存储,以此实现动态迁移,此方案需要一段时间内保留原先存储内的文件,直到扩展存储几乎全部回源后(即原先的存储几乎已无回源流量产生时)就可以释放原先存储(注意:如果某些文件一直没访问,那么扩展存储里不会有这些文件)
申请方式:在 扩展存储技术交流群 里发送内容,我想申请阿里云OSS迁移到扩展存储,我选择迁移方案二-动态迁移
迁移方案三
只将uni-cdn的回源地址修改为你原先存储的源站地址,这样只使用了uni-cdn,扩展存储内是不会存储文件的,文件还是全在你原先的存储中
申请方式:在 扩展存储技术交流群 里发送内容,我想申请uni-cdn回源到阿里云OSS,我选择迁移方案三-uni-cdn回源自有存储,我每月消耗的CDN是 xxx TB
特别注意:方案三只有CDN消耗大户才能申请
由于内置存储使用的是云厂商的默认域名,故即使迁移成功后,数据库里的图片存储的url地址还是你之前内置存储的默认域名,因此还需要进行如下操作。
方案一
用本地调试的运行方式(设置本地运行超时时间一个很大的数字),循环数据库所有表的记录,进行url修改
方案二
如果你使用的是第三方组件库的image组件,那么可以考虑直接修改image组件内的源码,将图片的src进行本地动态替换
方案三
如果你认为内置存储里存储的文件不重要,也可以考虑放弃内置存储里的文件,不迁移,直接使用扩展存储
七牛云提供了 qshell
工具用来快速将本地电脑文件快速批量上传到扩展存储