通过 node 上传大文件

通过 node 上传大文件 背景 因为工作上的原因,开发了一个客户端文件,用来做数据迁移 实际上的流程很简单,下载,清洗,再调用接口导入到另一个网站上 出现问题 本地的上传其实是通过 const formData = new FormData(); formData.append('file', blob); axios.post(xxx, formdata) 通过在 nodejs 里边写 formdata 并且通过 axios 进行上传的 遇到的第一个问题是 Buffer问题,因为 formdata 需要使用 blob 作为 file 字段进行上传的 最开始的时候我们的 blob 是通过 fs.readFileSync()生成的 但是在 node 环境下,我们的 node 实际上对 buffer 是有大小限制的 所以我一开始采用的方案是通过流式获取所有的 buffer 之后一起生成一个 blob const blob = await new Promise((resolve, reject) => { const stream = fs.createReadStream(); const blobData = []; stream.on('data', chunk => blobData.push(chunk)) stream.on('end', () => resolve(new Blob(blobData))) }) 这样一开始解决了读取 buffer 过大的问题 实际问题解决 实际问题还是上传下载的时候,如果读取为 blob的话,也是需要占用一段内存的 这也就造成了在传输 2g 以上大文件的时候出现了闪退 这个时候其实应该会有好几种解决方案 通过分片进行上传,实际上因为后端采用 minio 这个也是天然支持的,但是经过询问之后,发现上传 minio 其实也是后端额外封装了一层接口实现的,但是后端没有实现分片合并的逻辑,所以通过分片的方案就不行了 流式上传,这样可以减少内存的占用 所以上传的时候其实是需要将整个上传流程的 form 也转换成一个 stream ...

May 7, 2024 · 2 min · 401 words · 水华

axios兜底错误处理

项目的网络请求层使用了axios,axios是一个及其好用的库,但是在业务使用上出现了一些问题 出现原因 众所周知,前端一般通过ajax向后端请求数据然后处理后显示在网页/app上,当然人都是懒得,总是希望有一些自动完成的事,比如这一次,如果不是需要特殊处理的特定错误,直接弹toast结束。 错误处理还是很容易,axios增加拦截器,如果是指定的错误,直接throw new Error('错误') 剩下的交给promise.catch干就好了,问题集中在如何兜底这个catch,如果不处理,会变成unhandledrejection错误 解决方案 最原始的方案 高阶函数 throwMessage,自动在promise之后增加catch函数,问题是这个要记得手动引入,和自动这个理念还是有点区别的. export function throwMessage<T = any>(responsePromise: Promise<T>) { return responsePromise.catch((e: Error) => { console.error(`error`, e) message.error(e.message) }) } 每次使用都要throwMessage(promise)容易忘,如果有必要,还要写lint规则防止忘记 webpack方案 既然打包的是webpack,那么使用webpack在转译原始代码的时候注入就好了嘛. 推荐一个promise-catch-loader自动处理好这一系列问题 具体因为没有太研究,不做展开 事件监听方案 上文提到了,如果promise没有捕获错误,会自动变成unhandlered rejection错误,这个是可以全局监听的,具体在MDN上可以看 那么 我们在window上直接监听就好了 window.addEventListener('unhandledrejection', event => { event.preventDefault() }); 问题是,太全局了 处理Promise链方案 这种是最终处理的方案,当然也是最魔改的方案,考虑到只运行在前端,内存溢出情况不多,方能实现 axios返回的是promise,promise是遵循PromiseA+方案,内部其实是有一条promise链的,我们的目的就在于,在promise链条的最后一个promise上挂上catch,反正预先被处理了最后一个也不会触发 问题来了 如果你直接看promise的话,是只能看到,then,catch,finally这些方法的,并没办法看到promise链,也没办法顺着链访问到最后 如何解决 我们的目标是将其变为链式结构,简单来说,只要每个then和catch都能记下他的后继即可。 所以最终我选择注入promise的两个方法 实现代码 /** * 继承然后注入两个方法 */ export class ChainedPromise<T> extends Promise<T> { nextPromise?: Promise<any> then<TResult1 = T, TResult2 = never>( onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null, ): Promise<TResult1 | TResult2> { const promise = super.then(onfulfilled, onrejected) this.nextPromise = promise return promise } catch<TResult = never>( onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null, ): Promise<T | TResult> { const promise = super.catch(onrejected) this.nextPromise = promise return promise } } /** * 暴露给外面的方法,axios通过拦截器,通过封装service都能调用它来在入口处自动注入 */ function autoTravel<T>(promise: Promise<T>): Promise<T> { const getLastPromise = function(promise: ChainedPromise<any>): Promise<any> { if (promise.nextPromise) { return getLastPromise(promise.nextPromise) } return promise } /** * Promise内部的自动形成链条,就能顺着链条找到最后一个了 */ const autoTravelPromise = new ChainedPromise<T>((resolve, reject) => { promise.then(resolve).catch(responseError => { // throwMessage为之前定义的,promise上自动加catch的方法 throwMessage(getLastPromise(autoTravelPromise)) reject(responseError) }) }) return autoTravelPromise }

April 29, 2024 · 1 min · 189 words · 水华

vue-cli项目如何同时打包一个express项目

vue-cli项目一般只会生成一个纯前端的vue项目,不过有时候,如果我们想开发一个express项目同时又想用vue作为前端该怎么搞呢? 几个解决方案 两个项目分别开发 mono-repo 其他方案 vue/express一个项目一次打包 其实因为怕打包麻烦,所以我是希望一个项目内,可以同时开发express/vue打包的时候又能只打包输出到一个文件夹下 所以我选择在vue-cli生产的项目内,增加一个server文件夹,用来开发express相关的代码,只有在最后打包的时候,变为一个express项目 改造的要点 前后端独立的hmr 前后端最终成果打包在一起 改造过程 独立热更同时前端可调用express服务 首先,我们在文件目录下建立server文件夹,将express服务器的开发,src下为原先前端相关的代码 其次,安装nodemon用来做express项目的开发启动 package.json添加命令"start:be": "nodemon ./server/index.js" 添加vue.config.js, 加入 module.exports = { devServer: { proxy: { '/{express服务的路径,例如api}': { target: 'http://127.0.0.1:{express端口}, ws: true, }, } } } 这样,启动npm run serve的时候同时启动start: be即可同时开启前后端 打包让express应用调用vue结果 express有static功能,假定用的是public 文件夹 package.json添加命令"build:be": "cp package.json ./dist/ && cp server/* ./dist/" 这样来打包express应用 同时express内添加一行,app.use(express.static('./public')) 接着,来改造vue.config.js module.exports = { outputDir: "./dist/public" } 之后build命令运行完,运行build:fe即可 ...

June 18, 2022 · 1 min · 68 words · 水华

Electron构建如何支持m1芯片

起因 换了台m1的macbook, 我之前的看板electron程序构建的还是老的x64版本,使用上还需要使用rosetta2进行转译,性能损失不说,说不定还有潜在的问题,所幸看到 electron11开始支持使用arm64芯片的mac了,正好升级一波 写在前面 目前工具库还在preview,taobao镜像不可用 顺带提一下我之前的项目结构 基于 @vue/cli4 使用 vue-cli-plugin-electron-builder注入的electron相关 构建流 本地正常构建,自动构建流基于samuelmeuli/action-electron-builder@v1自动构建 项目地址点击这里 开工 升级依赖 首先需要升级使用的electron到支持新芯片的版本,目前能够使用的是electron11 npm install --save electron@11 同时我们需要升级electron-builder到支持打包新芯片的版本, 根据最新的方案,arch需要支持arm64并且可以打包dmg的话,需要升级到22.10.4 npm install --save-dev electron-builder@22.10.4 不过因为还在preview 只有npm官方仓库可以安装 接下来我们改造构建选项,因为我使用vue的插件, 所以我写在vue.config.js module.exports = { pluginOptions: { electronBuilder: { builderOptions: { artifactName: '${productName}-${version}-${os}-${arch}.${ext}', mac: { target: { arch: 'universal', target: 'dmg' } } }, } } } 这样可以打包一个自适应芯片的版本,当然你想打两个包可以把arch换成arch: ['x64', 'arm64'] ...

January 9, 2021 · 1 min · 78 words · 水华

文字超过x行后补上省略号的几种办法

文字超过 x 行后补上省略号的几种办法 原文 简单来说,在 pc 端,文字过长溢出的话,溢出的部分会被替换成...,然而在显示情况中,更多的是在超过 x 行之后才启用这个特性,例如 <div class="module"> <p> Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. </p> </div> 最终希望三行之后到这种效果: 为了演示,我们先定义基础的 css,剩下的方法都在这个 css 的基础之上 .module { width: 250px; overflow: hidden; } Weird WebKit Flexbox Way 使用 webkit 内置的方案 ...

October 18, 2019 · 1 min · 141 words · 水华

自动给表格空单元格补上字符串

自动给表格空单元格补上字符串 背景 ui 设计中,如果单元格内部为空的话,需要填充一个默认字符串标明是个空格子,本身计划使用 js 来做的,但是考虑到要需要遍历,判断等等一系列操作,而且 js 里 0 做 boolean 判断也是返回 false,无形中增加了判断的步骤,所以这个时候还是求助于万能的 css 吧 以下为 less 代码 td:empty { &::after { content: "-"; } }

September 23, 2019 · 1 min · 26 words · 水华

上传app卡在通过app store进行鉴定

cd ~ mv .itmstransporter/ .old_itmstransporter/ "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/itms/bin/iTMSTransporter"

July 9, 2019 · 1 min · 7 words · 水华

如何删除除了Master之外的所有分支

如何删除除了Master之外的所有分支 切换到 master 删了其他的 git stash && git checkout master && git branch | grep -v "master" | xargs git branch -D

June 5, 2019 · 1 min · 22 words · 水华

electron开机自动启动

直接 直接上 直接上代码 const exeName = path.basename(process.execPath) app.setLoginItemSettings({ openAtLogin: !openAtLogin, path: process.execPath, args: [ '--processStart', `"${exeName}"` ] }) }

February 11, 2019 · 1 min · 19 words · 水华

html上实现div按照宽高比自适应

html 上实现 div 按照宽高比自适应 原理很简单,padding 的百分比是根据宽度作为百分比自动设置的,所以容器上使用 .container { height: 0; padding-top: 114%; .data { width: 100%; height: 100%; } } 就可以了

November 25, 2018 · 1 min · 21 words · 水华