浏览器JS拉取远端图片上传到自己的服务器
我们经常遇到这样的情况,我们填写某个表单的时候,需要上传一张图片,但是我们的业务会有这样一个逻辑,我们这个表单会默认拉取一些数据进行填充,用户在这些数据基础上进行修改,这些拉取的数据,有的时候会使用外部数据里面的图片,此时,我们需要把外部数据里面的图片保存到我们自己的服务器。这种情况有两种解决方案,一种是纯后端做,一种是前端来做。我分享一种前端来做的方案。
其实拉取数据进行填充的本质,就是不破坏我们原有的表单交互流程,前后端保持高度一致,避免多态。一般的交互是提交表单的时候提交上传图片,把上传后得到的文件 id 同表单数据一起保存。这个过程是比较顺畅的,前后端理解起来非常简单。但是,如果这里强行改变这个逻辑,为了兼容多态,后端支持传 url 字符串,实际上,这也会给前端带来比较大的处理量。因此,我们的目标是,利用图片 url 转化为本地的 File 再提交到后端,走一个简单的流程,只不过在拉取数据这一步做一个处理。
那么,怎么把外部图片转化为一个浏览器里面的 File 呢?
function getImageFile(src) { return new Promise((resolve, reject) => { const img = document.createElement('img'); img.style.position = 'fixed'; img.style.top = '-10000px'; img.crossOrigin = 'anonymous'; img.src = src; img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const context = canvas.getContext('2d'); context.drawImage(img, 0, 0); canvas.toBlob((blob) => { // @ts-ignore // eslint-disable-next-line no-param-reassign blob.lastModifiedDate = new Date(); const name = src.split('/').pop(); // @ts-ignore // eslint-disable-next-line no-param-reassign blob.name = name; const file = new File([blob], name, { type: blob.type }); resolve(file); }); document.body.removeChild(img); }; img.onerror = (e) => { document.body.removeChild(img); reject(e); }; document.body.appendChild(img); }); }
这里用到了以下知识点:
- 由于是跨域的图片,所以不能直接通过 fetch 来得到 blob,于是我们利用 canvas 来加载远程图片,再利用 canvas 的接口来转化数据类型
- canvas 要能够 draw 远程图片,必须加上 crossOrigin = anonymous
- 通过 toBlob 之后得到 blob,再使用 new File([blob]) 就可以把 blob 转化为一个本地的文件,用在提交中
- 最后需要注意,提交文件需要使用 content-type: multipart/form-data 进行提交
通过这个处理,我们就可以在浏览器里面把外部图片转化为内部图片进行使用,这样就节省了后端去做类似回源CDN之类的建设。
nginx获取顶层目录,重写至顶层目录下的index.html
最近再部署测试环境时,考虑到我们的特殊需求,希望按模块进行发布测试,每个模块被放在一个文件夹中进行发布,所以不得不进行重写,来让请求fallback到目录下的index.html. 例如当我们访问 /some-module/xxx/xxx 时,实际上fallback到 /some-module/index.html。而我们发布测试模块时增量的,所以可能过两天会发布一个 any-module 来测试,不大可能写死在 nginx 的 config 中。所以,我的想法是,在 nginx 中实现一种配置,可以实现这种能力。具体实现如下:
location / {
try_files $uri $uri/ index.html @rewrite_uri;
}
location @rewite_uri {
if ($request_uri ~ /([a-z0-9_-:]+)/) {
set $tmp /$1/index.html;
rewrite ^ $tmp break;
}
}
使用 try_files 来在 nginx 中实现 fallback 的能力。增加了一个 @rewrite_uri 的规则,这个规则里面,对 $request_uri 进行匹配,通过正则把顶层目录取出来,在用 rewrite 进行规定。
通过这种方式,我们不仅可以去除顶层目录,而且可以实现我们想要的重写效果。