一、需求

egg.js 的文件上传个人觉得很一般,内置的 multipart 插件并不怎么好用。

egg-multipart 也是基于 co-busboy 实现的。

egg 官方给的文件上传的示例地址:

二、CSRF 校验

egg 文件的上传需要进行 csrf 校验,而且这个校验默认只支持放在 ctx.query._csrf 字段中,很不方便,每次文件上传都会携带。

习惯 laravel 或者某些 PHP 框架的人都知道,直接放在  的字段中方便的多。(不过我默认试过放在 input 字段中,是不起作用的,其他方法我暂时没有尝试)

至于生成 csrf 的方式,可以在 cookie 中获取,cookie 中存放了 csrfToken=token,因此是可以通过前端获取的,这为通过 ajax 实现文件上传也提供了帮助。

如果在模板文件中使用的话,可以通过 {{ctx.csrf | safe}} 生成 token。

因为 token 必须放在 query._csrf 中,所以 form 的 html 如下:

三、通过 form 表单上传文件

表单 html 不过多描述,默认使用上面的 url 进行表单提交,路由也不具体说明。

文件上传的时候使用了额外的4个依赖,依赖如下:文件读写流 ready 库,能够使用 await

await-stream-ready 主要是方便的使用 await 进行文件上传,而 stream-wormhole 是因为如果上传失败出现异常,那会导致浏览器响应崩溃,因此需要将 stream 消耗掉。

重点说下 controller 中的代码:

app/controller/post.js:// 添加文章

async add(){

await this.ctx.render('post/add');

}

// 添加文章操作

async addAction(){

const {ctx} = this;

// 获取 steam

const stream = await ctx.getFileStream();

// 生成文件名

const filename = Date.now() + '' + Number.parseInt(Math.random() * 10000) + path.extname(stream.filename);

// 写入路径

const target = path.join(this.config.baseDir, 'app/public/upload/', filename);

const writeStream = fs.createWriteStream(target);

try {

// 写入文件

await awaitStreamReady(stream.pipe(writeStream));

} catch (err) {

// 必须将上传的文件流消费掉,要不然浏览器响应会卡死

await sendToWormhole(stream);

throw err;

}

ctx.body = stream.fields;

}

上面代码中,通过 await ctx.getFileStream() 获取到流,stream 是一个 FileStream 对象,有许多有用的属性,比如下面这些属性,其中 stream.filename 能获取文件的原始名称,上面 controller 代码中,便是使用 filename 获取到了文件的后缀。

当表单的 enctype 设置成 multipart/form-data 之后,便不能使用 ctx.request.body 获取其他字段,现在是个空对象。

要获取其他字段的名字,需要使用 stream.fields 来获取其他字段的值。FileStream {

fieldname: 'photo', // 字段名

filename: '1.jpg', // 文件名

encoding: '7bit', // 编码

transferEncoding: '7bit',

mime: 'image/jpeg', // 类型

mimeType: 'image/jpeg',

fields: { title: '', description: '', author: '', content: '' } }

上传结果:{"title":"","description":"","author":"","content":""}

四、通过 ajax 上传文件

ajax 文件上传后端代码基本不用变动,只是前端代码这边构建一个 formData 即可。

下面的代码是官方示例的给的,因为用 formData 进行 ajax 文件上传很多示例,不再重复,即使使用其他插件也是无所谓的,本质上 HTTP 请求的 Content-type 还是 multipart/form-data。

关键点在于 _csrf 的获取,方法 getCsrf() 是通过 cookie 获取 token 的方式,当然如果页面还是在模板中,那我觉得通过一个隐藏的值存放 csrf token,然后再去获取更加方便。

后端代码没什么变动的,本质是一样的。$('form').submit(function(e) {

e.preventDefault();

var formData = new FormData();

formData.append('name', $('input[type=text]').val());

// Attach file

formData.append('image', $('input[type=file]')[0].files[0]);

// console.log(formData);

$.ajax({

url: '/ajax?_csrf=' + getCsrf(),

data: formData,

method: 'POST',

contentType: false, // NEEDED, DON'T OMIT THIS (requires jQuery 1.6+)

processData: false, // NEEDED, DON'T OMIT THIS

success: function(result) {

console.log(result);

},

error: function(responseStr) {

alert("error", responseStr);

}

});

// 通过 cookie 获取 csrf token

function getCsrf() {

var keyValue = document.cookie.match('(^|;) ?csrfToken=([^;]*)(;|$)');

return keyValue ? keyValue[2] : null;

}

});

五、自定义文件上传目录

按照我自己的习惯,我在上传文件的时候,自然是希望文件上传到 app/public/upload/20180707/xxxx.jpg,但是由于 writeStream 的限制,20180707 必然是需要确定文件目录存在的。

因此如果使用这种方式上传,则需要增加几行代码用来判断并且生成文件夹。

为了方便生成日期如 20180707 的目录,我用了 dayjs 库来格式化时间。// 上传基础目录

const uplaodBasePath = 'app/public/upload/';

// 生成文件名

const filename = Date.now() + '' + Number.parseInt(Math.random() * 10000) + path.extname(stream.filename);

// 生成文件夹

const dirName = dayjs(Date.now()).format('YYYYMMDD');

// 判断文件夹是否存在,不存在则直接创建文件夹

if(! fs.existsSync()) fs.mkdirSync(path.join(this.config.baseDir,uplaodBasePath,dirName));

// 生成写入路径

const target = path.join(this.config.baseDir, uplaodBasePath, dirName, filename);

// 写入流

const writeStream = fs.createWriteStream(target);

最终结果:

最终的 controller 代码如下:// 添加文章操作

async addAction(){

const {ctx} = this;

// 获取 steam

const stream = await ctx.getFileStream();

// 上传基础目录

const uplaodBasePath = 'app/public/upload/';

// 生成文件名

const filename = Date.now() + '' + Number.parseInt(Math.random() * 10000) + path.extname(stream.filename);

// 生成文件夹

const dirName = dayjs(Date.now()).format('YYYYMMDD');

if(! fs.existsSync()) fs.mkdirSync(path.join(this.config.baseDir,uplaodBasePath,dirName));

// 生成写入路径

const target = path.join(this.config.baseDir, uplaodBasePath, dirName, filename);

// 写入流

const writeStream = fs.createWriteStream(target);

try {

// 写入文件

await awaitStreamReady(stream.pipe(writeStream));

} catch (err) {

// 必须将上传的文件流消费掉,要不然浏览器响应会卡死

await sendToWormhole(stream);

throw err;

}

ctx.body = stream.fields;

}

egg.js ajax上传文件,egg.js 通过 form 和 ajax 两种方式上传文件并自定义目录和文件名...相关推荐

  1. POI读取word文件,(支持HSSF和XSSF两种方式)

    POI读取word文件,(支持HSSF和XSSF两种方式) 参考:HSSF,XSSF,SXSSF三种方式 1.引用maven(版本必须一致) <dependency><groupId ...

  2. springMVC两种方式实现多文件上传及效率比较

    springMVC实现多文件上传的方式有两种,一种是我们经常使用的以字节流的方式进行文件上传,另外一种是使用springMVC包装好的解析器进行上传.这两种方式对于实现多文件上传效率上却有着很大的差距 ...

  3. 运行python程序的两种方式交互式和文件式_执行Python程序的两种方式

    交互式(了解) 交互式环境下,敲完一条命令按下enter键马上能看到结果,调试程序方便.程序无法永久保存,关掉cmd窗口数据就消失了. 命令行式(了解) 打开文本编辑器,在文本编辑器中写入一串字符. ...

  4. MyBatis获取参数值的两种方式以及传参情况

    MyBatis获取参数值的两种方式 MyBatis获取参数值的两种方式:${}和#{} 传参情况 演示环境 1.单个字面量类型的参数 2.多个字面量类型的参数 3.map集合类型的参数 4.实体类类型 ...

  5. 运行python程序的两种方式交互式和文件式_Python基础知识2

    运行Python程序的两种方式 小白学习,如有错误欢迎指点 一.每位小白写的第一个Python程序 1.运行Python程序的两种方式 1.1 交互式模式(即时对话) 打开cmd,打开Python解释 ...

  6. 运行python程序的两种方式交互式和文件式_教你如何编写、保存与运行 Python 程序...

    第一步 接下来我们将看见如何在 Python 中运行一个传统的"Hello World"程序.Python教程本章将会教你如何编写.保存与运行 Python 程序. 通过 Pyth ...

  7. 两种方式读取Json文件 数据

    首先下载LitJson.dll 文件,并将其拖入 Unity项目中的 的 Assets/Plugins目录中 其次在你的Unity项目中创建好Assets/StreamingAssets文件夹,用于存 ...

  8. url传参时中文乱码转码的两种方式

    问题: 当前台在url中传递中文参数时,如果是web项目且未设置URIEncoding的话后台获取到的参数会出现乱码的情况,故先总结两种解决办法. 写法一 需要使用try catch String p ...

  9. vue 路由传参 params 与 query两种方式的区别(转载)

    vue 路由传参 params 与 query两种方式的区别 初学vue的时候,不知道如何在方法中跳转界面并传参,百度过后,了解到两种方式,params 与 query.然后,错误就这么来了:  ro ...

最新文章

  1. Vmware VsPhere下的VM如何安装Hyper-v服务
  2. 8.分布式数据库HBase第4部分
  3. xhtml文件的后缀名是什么?
  4. Useful code snippet to parse the key value pairs in URL
  5. mysql把用户权限授予新用户_MySQL新建普通用户和库并授予新用户对新库的所有权限...
  6. 幼儿使用计算机需要注意事项,儿童玩电脑注意事项
  7. C++指针、this指针、静态成员
  8. Tomcat5.5.9+JSP经典配置实例
  9. java实现手机充电_java – 如何知道手机是否正在充电
  10. UncategorizedSQLException异常处理办法
  11. np.unique 的实现
  12. Python基于seaborn绘制喜欢的热力图,不同色系一览
  13. Linux 内存管理之 SLUB分配器(3):Object分配逻辑
  14. 防火墙阻止tftp_再谈突破TCP-IP过滤/防火墙进入内网(icmp篇)
  15. 药品数据查询系统工具(非付费官网50个)
  16. 普通话和英语发音_incomplete
  17. android 色彩管理,你买的贵价屏幕只是半成品?谈谈色彩管理那点事
  18. 使用nginx配置子域名
  19. 谷歌文档_如何比较Google文档中的文档
  20. 自动化运维工具——puppet详解(二)

热门文章

  1. boost条件变量使用
  2. PyCharm断点调试
  3. python公众号推荐 知乎_爬取公众号及知乎专栏文章的标题链接的方法汇总
  4. 使用 SwitchHosts 一个修改、管理、切换多个 hosts 无法生效问题
  5. autodesk-forge 模型旋转代码
  6. oracle sql语言模糊查询--通配符like的使用教程
  7. indent expected
  8. 基于ibeacons签到系统
  9. python多个领域140个常用库 (标准库/第三方库)
  10. 题目0159-对称美学