JS实现下载远端图片到本地

背景

公司静态资源存储采用的是阿里云的oss服务,会返回一个如下的静态资源链接:http://static.kanfangjilu.com/5b7fe45205691500062a1c59/3315E3869EFD673014AAB086D9C2CD9B?x-oss-process=image/format,jpg

踩坑过程

1、通过window.open(url)下载的方式
因为看到下载excel是采用的这种方法,起初尝试这种方式,直接赋值图片的url,结果是不会触发下载,只会在当前页面打开图片,这种方式是无效的,对于文件像是excel、pdf是可以下载的,不支持图片的下载。

2、通过 <a> 的 download 属性

function download(url, name) {
    const aLink = document.createElement('a')
    aLink.download = name 
    aLink.href = url 
    aLink.dispatchEvent(new MouseEvent('click', {}))
}

这种方法是最常想到的,也是网上最常见的方法,当图片地址是同源的,可以采用这种方式下载图片,但是如果图片的地址是远端跨域的,点击下载效果也是在当前页面打开图片,这种方式对于需要将远端图片下载到本地是无效的。

3、通过url 转 base64,利用canvas.toDataURL的API转化成base64

urlToBase64(url) {
  return new Promise ((resolve,reject) => {
      let image = new Image();
      image.onload = function() {
        let canvas = document.createElement('canvas');
        canvas.width = this.naturalWidth;
        canvas.height = this.naturalHeight;
        // 将图片插入画布并开始绘制
        canvas.getContext('2d').drawImage(image, 0, 0);
        // result
        let result = canvas.toDataURL('image/png')
        resolve(result);
      };
      // CORS 策略,会存在跨域问题https://stackoverflow.com/questions/20424279/canvas-todataurl-securityerror
      image.setAttribute("crossOrigin",'Anonymous');
      image.src = url;
      // 图片加载失败的错误处理
      image.onerror = () => {
        reject(new Error('图片流异常'));
    };
}

这种方式经过测试,同样会存在跨域问题,虽然已经设置了允许跨域,但是浏览器对此却是拒绝的。


现在的主要问题是不能解决跨域问题,如果图片链接是跨域的,浏览器会禁用download,只允许打开图片而不允许下载,需要想办法生成图片本地的下载地址,我想到的是把远端图片的二进制信息或者是base64信息返回给前端,在前端生成本地的地址,我开始考虑在中间层做处理,在中间层根据图片的链接地址获取到图片的流信息。

4、中间层获取远端图片流信息

req.requestImage = (url) => new Promise((resolve, reject) => {
    request.get({ url, encoding: null }, (error, response) => {
      if (error) {
        return reject(error)
      }
      return resolve(response)
    })
  })

注意这里encoding: null需要设置,默认采用的utf-8, 而我们需要的是二进制流。

把图片的url地址传给中间层

function downloadImage(req) {
return req.requestImage(req.body.imgUrl)
}

获取到图片的流信息后返回给前端,response.body就是图片的二进制流

router.post('/v1/house/download', (req, res, next) => {
 House.downloadImage(req).then((response) => {
   res.writeHead(200, { 'Content-Type': response.headers['content-type'] })
   res.end(response.body)
 }).catch(next)
})

这里也可以处理成base64

request.get({url: 'http://static.kanfangjilu.com/5a34feb8698503900bbb24f8/A7C9AAE5322FCE531113E08591767E62?x-oss-process=image/format,jpg', encoding: null }, function (error, response, body) {
    const type = response.headers["content-type"]
    const prefix = "data:" + type + ";base64,"
    const data = response.body.toString('base64');
    const base64 = prefix + data
    res.end(base64)
  })

修改axios的配置信息 responseType: 'blob',axios默认是json,会出现乱码问题

downloadImage({ commit }, data) {
    return this.$http({
      method: 'post',
      url: '/api/v1/house/download',
      data,
      responseType: 'blob',
    })
  },

这时候前端就可以获取到图片的流信息,URL.createObjectURL()具有处理二进制流的能力,可以生成一个本地的地址blob:http://192.168.2.227:3000/093e08bc-0c0b-436a-b24c-c065b78303d2,该地址可以用于将资源下载到本地。

  downloadImage(imgUrl) {
      if (!imgUrl) return
      const imgName = imgUrl.split('//')[1].split('/')[1]
      this.$store.dispatch('houses/downloadImage', { imgUrl }).then((res) => {
        const aLink = document.createElement('a')
        aLink.download = imgName
        aLink.href = URL.createObjectURL(res.data)
        aLink.dispatchEvent(new MouseEvent('click', {}))
      })
    },

5、用Content-disposition
方法4是有效的,但并不是优雅的解决方法,前端拿到图片的流信息后,需要做进一步转化处理,将流信息转化成一个本地的下载地址,这对前端是一种性能上的消耗,其实这一过程是多余的。HTTP协议响应头Content-disposition可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,文件直接在浏览器上显示或者在访问时弹出文件下载对话框。
在中间层开个下载接口:

router.get('/common/download', (req, res, next) => {
  const url = req.query.url
  const fileName = req.query.fileName
  request.get({ url, encoding: null }, (error, response, body) => {
    if (error) {
      next(error)
      return
    }
    const fileType = response.headers['content-type'].split('/')[1]
    res.setHeader('Content-disposition', getContentDisposition(fileName, fileType))
    res.setHeader('Content-type', response.headers['content-type'])
    res.send(body)
  })
})
function getContentDisposition(fileName, fileType) {
  return `attachment; filename=${encodeURIComponent(fileName)}.${fileType}; filename*=utf-8''${encodeURIComponent(fileName)}.${fileType};`
}

总结

逐渐意识到node做中间层带来的好处。
1、基于设配器模式的思想,有了中间层可以对后台返回的数据进行拦截,转化,过滤,可以实现由前端定义数据结构,生成适合UI组件库的数据结构而不需要放到浏览器做转化处理。
2、可以在中间层隐藏一些用户的关键信息,避免http明文传输,用户信息被轻易的抓包。
3、中间层与静态资源天生同源,不需要考虑浏览器与后台的跨域问题。
4、承接部分后台任务,让后台专注于提供数据,而前后端的通讯都交由前端管理,实现更为彻底的前后端分离。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容