uploader组件配合egg node使用form-data文件上传服务

803 阅读3分钟

uploader + node 进行文件上传服务

技术栈

  • 前端:
  1. 框架: vue
  2. UI框架: Ant Design Vue
  3. js: FileReaderFormData
  • node端:
  1. 框架: egg
  2. 插件: Form-Data
  3. nodejs: fs模块(fs.createReadStream)

文档类上传(.xlsx .xls .docx .doc .pdf)

前端的处理

<a-upload
    accept=".xlsx .xls .docx .doc .pdf"
    :file-list="fileList"
    :before-upload="beforeUpload"
    :custom-request="customRequest"
    @change="onChange"
>上传</a-upload>
data() {
    return {
        fileList: [] as any, // 上传列表
    };
},
methods: {
    // 上传之前 可以对文件做一些限制和校验(比如名字, 类型等)
    beforeUpload(file) {
        console.log('file:', file);
        // 返回false 停止上传
        if (file.name.length > 128) return false; // 若返回 false 则停止上传
    },

    // 上传文件改变时的状态
    onChange({ file, fileList }) {
        console.log(file.status);
        // 若每次只支持上传一个文件, 可以限制上传列表数量为1个
        fileList = fileList.slice(-1);
        fileList = fileList.map(file => {
            if (file.response) file.url = file.response.url;
            return file;
        });
        // 过滤掉错误的文件
        this.fileList = fileList.filter(file => !!file.status);
    },

    // 上传
    async customRequest(option) {
        console.log('option', option);
        try {
            const formData = new FormData().append('file', option.file);
            const data = await http.post('/upload', formData, {
                headers: { 'Content-Type': 'multipart/form-data' }
            });
            option.onSuccess();
        } catch(err) {
            option.onError(err);			
        }
    }
}

说明:

一、customRequest中的option参数, 除携带当前文件的信息外, 还有处理三种状态的回调可供选择 在这里插入图片描述 二、这里用到的请求(http)是我当前项目中axios封装的请求, 实际开发中, 替换成你们自己的axios请求和对应的接口

node端的处理

router
module.exports = (app) => {
    const { controller, router } = app;
    const v1 = router.namespace('/v1');
    v1.resources('uploader', '/upload', controller.upload);
}
controller
// controller/uploader.js
const { Controller } = require('egg');
class Uploader extends Controller {
    async create() {
        const { ctx, service } = this;
        const file = ctx.request.files[0];
        // 获取文件类型
        const index = file.filename.lastIndexOf('.');
        const fileType = file.filename.slice(index + 1);
        const data = await service.appTest.upload(file, fileType);
        ctx.body = data;
    }
}
module.exports = Uploader;
service
// service/uploader.js
const { Service } = require('egg');
const FormData = require('form-data');
const fs = require('fs');

class Uploader extends Service {
    async upload(file, fileType) {
        const formData = new FormData();
        const { filename } = file;
        const readStream = fs.createReadStream(file.filepath);
        formData.append('file', readStream, { filename });
        formData.append('file_type', fileType);
        formData.append('file_name', file.filename);
        // 设置content-length:
        const length = await new Promise((resolve) => {
            formData.getLength((err, length) => {
                if (err) return;
                resolve(length);
            });
        });
        const formHeaders = formData.getHeaders();
        formHeaders['Content-Length'] = length;
        const data = await this.ctx.axios.post(
        	`/java/upload`, // 后端的接口
	        formData,
    	    { headers: formHeaders }
    	);
        return data;
    }
}
module.exports = Uploader;

说明:

一、这里使用了egg-router-plus插件, 是为了解决路由过多的问题, 可以单独给每个模块开辟一个命名空间, 感兴趣可以点击查看 二、为了方便 这里使用了RESTful 的方式来定义路由 三、controller中获取的web传过来的file信息:

在这里插入图片描述 四、读取成readStream后, 最终传给后端的formaData 在这里插入图片描述


图片类上传(.png .jpg)

注意: 这里其实想介绍下图片预览的功能preview(其他在文档类上传中重复过的步骤, 这里就不再赘述了)

前端的处理

<a-upload-dragger
    name="image"
    accept=".img,.png"
    list-type="picture-card"
    :show-upload-list="{showRemoveIcon:false,showPreviewIcon:true}"
    @preview="onPreview"
    :file-list="fileList"
    :before-upload="beforeUpload"
    :custom-request="customRequest"
    @change="onChange"
></a-upload-dragger>
<a-modal :visible="previewVisible" :footer="null">
	<img :src="previewImage" />
</a-modal>

<!-- 后面四个/ fileList / beforeUpload / customRequest / onChange 相关逻辑就不再赘述了 -->
data() {
    return {
        previewImage: '',
        previewVisible: false,
    };
},
methods: {
    getBase64(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = () => resolve(reader.result);
            reader.onerror = error => reject(error);
        });
    },
    // 图片预览功能
    async handlePreview(file) {
    	console.log(file);
        if (!file.url && !file.preview) {
            // 如果没有图片路径, 则直接拿图片base64给src
            file.preview = await this.getBase64(file.originFileObj);
        }
        // 打开预览弹窗
        this.previewImage = file.url || file.preview;
        this.previewVisible = true;
    },

	// 上传
	async customRequest(option) {
		// 不再赘述, 与文档类上传一致
	}
}

说明

一、预览函数 onPreview 获取到的参数信息: 在这里插入图片描述 二、上传图片预览效果: 在这里插入图片描述

另: upload组件部分属性(详情见官网)

API说明类型默认值
accept接受上传的文件类型string
customRequest自定义上传方法Function
fileList已经上传的文件列表(受控)object[ ]
name发到后台的文件参数名string'file'
listType上传列表的内建样式,支持三种基本样式 text, picture 和 picture-cardstring'text'
showUploadList上是否展示 uploadListBoolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean }true
remove点击移除文件时的回调Function(file): boolean | Promise
事件说明回调参数默认值
change上传文件改变时的状态Function
preview点击文件链接或预览图标时的回调Function(file)
reject拖拽文件不符合 accept 类型时的回调Function(fileList)

好啦~今天就分享到这里!