问题描述:我们通过 URL 图片连接下载图片至本地,产生图片请求异常:No ‘Access-Control-Allow-Origin’ header is present on the requested resource。
我们通过 XMLHttpRequest 加载外部图片文件
// 图片下载
download (row) {
const $that = this
const imgUrl = row.fileUrl
let xhr = new XMLHttpRequest()
xhr.open('get', imgUrl, true)
// 至关重要
xhr.responseType = 'blob'
xhr.onload = function () {
if (this.status === 200) {
// 得到一个blob对象
let blob = this.response
// 至关重要
$that.downloadFile(row.fileName, blob)
}
}
xhr.send()
},
将结果返回结果放入 <a> 标签中,进行下载。
// 下载
downloadFile (fileName, blob) {
var dowloadElement = document.createElement('a')
var href = window.URL.createObjectURL(blob)
let evt = document.createEvent('HTMLEvents')
dowloadElement.href = href
dowloadElement.download = fileName
document.body.appendChild(dowloadElement)
evt.initEvent('click', true, true)
dowloadElement.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }))
document.body.removeChild(dowloadElement)
window.URL.revokeObjectURL(href)
},
此时出现图片链接出现 403 禁止访问的问题,但是通过链接,可以在浏览器中查看访问都没有问题。
首先想到的是不再是通过 XMLHttpRequest 进行加载图片,而是通过 <a>、<image> 标签在浏览器中可以无视同源策略。
在 <a> 标签中,配置 download 的属性时,图片链接直接在浏览器中打开了,因此仅适用于同源的 URL。
解决方案:先创建一个图片,然后用 canvas 绘制该图片,然后使用 canvas.toDataURL 获得,代码如下:
async download (row) {
let image = new Image()
image.setAttribute('crossOrigin', 'anonymous')
image.src = row.fileUrl
image.onload = () => {
let canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
let ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, 0, image.width, image.height)
let ext = image.src.substring(image.src.lastIndexOf('.')+1).toLowerCase()
let dataURL = canvas.toDataURL(ext)
this.downloadFile(dataURL, row.fileName)
}
},
代码中有设置图片的 crossOrigin 属性 image.setAttribute('crossOrigin', 'anonymous')。
因为图片跨域,canvas的toDataURL会报错拿不到结果。
将通过 canvas 制作的图片链接,放入到 <a> 标签中进行下载。
download (href, name = 'pic') {
let eleLink = document.createElement('a')
eleLink.download = name
eleLink.href = href
eleLink.click()
eleLink.remove()
}
此时产生了新的问题:就是有些图片可以下载下来,有些图片同样会产生 403 禁止访问的问题。找了很久这个问题的产生,初步估计是上传图片的时间在当前上传的就可以下载,而非当天上传的图片,下载就产生了403 禁止访问的问题。通过查看 阿里云 OSS 下载文件,通过文件 URL 下载文件,浏览器中使用 signatureUrl 方法生成用于下载的文件 URL,URL的有效期默认为半小时,即 1800s。
调用接口,获取下载文件配置信息,代码如下:
import Aliyun from '../../config/aliyun/aliyunConfig'
async getData (row) {
const { data } = await common.getSecurityToken()
const { accessKeyId, accessKeySecret, securityToken } = data
return new Aliyun().configDownload({
AWconfig: {
aliyunConfig: { // AWconfig为阿里云图片地址配置
url: 'xxx',
endpoint: 'xxx',
bucket: 'xxx',
},
},
token: { accessKeyId, accessKeySecret, securityToken },
name: row.fileName,
url: row.fileId
})
},
将处理过后的 URL 链接放入 image 中,代码如下:
// 图片下载
async download (row) {
const result = await this.getData(row)
image.src = result
...
},
那么我们如何配置 Aliyun 类中的 configDownload 方法呢?
export default class Aliyun {
// 配置下载功能
configDownload (obj) {
this.validate(obj) // 检验参数
let { accessKeyId, accessKeySecret, securityToken } = this.getParams(obj) // 处理参数
var aliyunConfig = obj.AWconfig.aliyunConfig
var client = new OSS.Wrapper({
accessKeyId,
accessKeySecret,
stsToken: securityToken,
endpoint: aliyunConfig.endpoint,
bucket: aliyunConfig.bucket
})
const filename = obj.name // filename为自定义下载后的文件名。
const response = {
'content-disposition': `attachment; filename=${encodeURIComponent(filename)}`
}
// object-key表示从OSS下载文件时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
return client.signatureUrl(obj.url, { response });
}
}
这里需要注意的是,传入的 URL 地址并非完整路径;传入的是包含文件后缀在内的完整路径。