express+mysql+vue,从零搭建一个商城管理系统8--文件上传,大文件分片上传

本文介绍了如何在Express应用中使用multer处理文件上传,包括安装依赖、配置上传目录、处理文件切片上传和合并,以及启用JWT验证并生成token。
摘要由CSDN通过智能技术生成

提示:学习express,搭建管理系统


前言

需求:主要学习express,所以先写service部分

一、安装multer,fs-extra

npm install multer --save
npm install fs-extra --save

在这里插入图片描述

二、新建config/upload.js

upload.js

const fs = require('fs');
const fsExtra = require('fs-extra');
const path = require('path');
const rootDir = path.resolve(__dirname,'../upload/');
const temporaryDir = path.resolve(__dirname,'../upload/temporary/');
const errFun = (msg,code)=>{
    return {
        code:code||500,
        success:false,
        msg:msg||'操作失败'
    }
}
const sucFun = (data,msg)=>{
    return {
        code:200,
        success:true,
        msg:msg||'操作成功',
        data,
    }
}
const uploadUtil = {
    upload:(req)=>{
        const { fileMD5,chunkMD5 } = req.body;
        return new Promise ((resolve,reject)=>{
            const folderPath = temporaryDir+'/'+fileMD5;
            const filePath = temporaryDir+'/'+fileMD5+'/'+chunkMD5
            if(!fs.existsSync(folderPath))fs.mkdirSync(folderPath);
            fs.writeFile(filePath,req.file.buffer,(err)=>{
                if(err)reject(errFun('切片上传失败'));
                resolve(sucFun({},'上传成功'));
            })
        });
    },
    merge:async(req)=>{
        const {fileMD5,chunkMD5Arr,type} = req.body;
        const folderPath = temporaryDir+'/'+fileMD5;
        let chunkBufferArr = [];
        for(let i=0;i<chunkMD5Arr.length;i++){
            let chunkBuffer = await fs.readFileSync(folderPath+'/'+chunkMD5Arr[i]);
            chunkBufferArr.push(chunkBuffer);
        }
        console.log(chunkBufferArr)
        let fileurl = rootDir+'/images/'+fileMD5+'-'+(new Date().getTime())+'.'+type;
        fs.writeFile(fileurl,Buffer.concat(chunkBufferArr),(err)=>{
            if(err)errFun(fileMD5+'文件切片merge失败');
            sucFun({url:fileurl},'文件切片merge成功');
            //删除临时文件夹以及文件夹下的所有文件
            fsExtra.removeSync(folderPath);
        });
    }
}
module.exports = uploadUtil;

在这里插入图片描述

三、新建routes/upload.js

const uploadUtil = require('../config/upload');
//文件流的key与multer的single方法的参数param,必须保持一致,不一致接收不到文件流
 //fd.append('xxx',chunk);
 //multer().single('xxxx');

const multer = require('multer');
const chunk = multer().single('chunk');

const uploadRoutes = (router)=>{
    router.post('/upload/upload',chunk,async (req,res)=>{
        const result = await uploadUtil.upload(req);
        res.json(result);
    });
    router.post('/upload/merge',async (req,res)=>{
        const result = await uploadUtil.merge(req);
        res.json(result);
    });
}
module.exports = uploadRoutes;

在这里插入图片描述

四、修改routes下的index.js

const userRoutes = require('./user');
const uploadRoutes = require('./upload');
const routes = (router)=>{
    //user路由
    userRoutes(router);
    //upload路由
    uploadRoutes(router);
}

module.exports = routes;

在这里插入图片描述

五、修改index.js

注释jwt的token验证,方便测试。如果打开验证,需要通过login接口生成token,上传文件request请求头部携带

const express = require('express');
const app = express();
const router = express.Router();
const jwt = require('./config/jwt');

const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

const port = 1990;

app.all('*',  (req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header("Access-Control-Allow-Headers", "Authorization,token,content-type");
    res.header('Access-Control-Allow-Methods', '*');
    res.header('Content-Type', 'application/json;charset=utf-8');
    next();
});

//全局验证token
// app.use('/*',(req,res,next)=>{
//     let notValidateData = ['/user/login','/user/register'];
//     if(notValidateData.indexOf(req.baseUrl)>-1)return next();
//     if((jwt.verify(req.headers.authorization||'')||{}).success)return next();
//     return res.json({success:false,code:500,msg:'token验证失败'});
// })

//初始化路由
require('./routes/index')(router);

app.use('/', router);
app.listen(port,()=>{
    console.log('http://localhost:'+port);
})

在这里插入图片描述

六、新建上传文件test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test upload</title>
</head>
<body>
    <input type="file" onchange="goUpload(this.files)">
    <script type="text/javascript" src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script>
    <script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.16.0/js/md5.js"></script>
    <script>
        const reader = new FileReader();
        const instance = axios.create({
            baseURL: 'http://localhost:1990/', //后台接口url+port
            timeout: 5000,
            headers: {
                'Content-Type': 'multipart/form-data',
                "authorization":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6Imxvbmdsb25nYWdvMSIsInBhc3N3b3JkIjoibG9uZzEyMzQ1NiIsImlhdCI6MTcwOTIwMjQ1NiwiZXhwIjoxNzA5MjA2MDU2fQ.XztBbjm2BbeQB7-OIXX040xuNxR5gnioCCgNV2c5NGI",
            },
            withCredentials: false, // default
            responseType: 'json', // default
            maxContentLength: 2000,
        });
        //上传文件计数对象
        let postFileObj = {};
        //上传切片计数对象
        let postChunkObj = {};
        //组件上传文件
        const goUpload = (files)=>{
            postFileObj = {};
            postChunkObj = {};
            upload(files[0])
        }
        //上传文件
        const upload = async (file)=>{
            //文件blob
            const fileBlob = file.slice(0,file.size);
            //文件hash,用作后端新建临时文件夹名和上传完成的文件命名参数  并且,相同文件内容(文件名可不同)进行上传,生成的fileMD5是相同的,如有需要,可根据存储的文件名判断,不需要重新上传
            const fileMD5 = await blodToString(fileBlob);
            //切片数组
            const chunks = fileToChunks(file);
            //切片hash 用来命名生成的临时文件,并在所有切片上传完成,按顺序,读取切片,生成文件,如果不按照顺序,生成文件会因为写入顺序不对而乱码
            const chunkMD5Arr = [];
            //初始化当前文件上传计数
            if(!postFileObj[fileMD5])postFileObj[fileMD5] = {count:1}
            for(let i=0;i<chunks.length;i++){
                const chunkMD5 = await blodToString(chunks[i]);
                chunkMD5Arr.push(chunkMD5);
                //初始化key为fileMD5的切片组
                postChunkObj[fileMD5] = {};
                //初始化key为fileMD5的切片组中key为chunkMD5的切片计数对象
                postChunkObj[fileMD5][chunkMD5] = {success:false,count:1}
                await postChunk(chunks[i],fileMD5,chunkMD5);
            }
            let postAllChunk = true;
            //判断切片是否都已上传完成
            for(let key in postChunkObj[fileMD5]){
                if(!postChunkObj[fileMD5][key].success){
                        postAllChunk = false;
                        break
                }
            }
            // 所有切片都已上传成功
            if(postAllChunk){
                //调用merge方法
                const typeArr = file.name.split('.');
                mergeChunks(fileMD5,chunkMD5Arr,typeArr[typeArr.length-1]);
            }else{
                //有切片上传失败,重新执行上传
                postFileObj[fileMD5].count++;
                if(postFileObj[fileMD5].count>10) return console.log('文件上传失败');
                //重新上传
                await upload(file);
            }
        }
        //blob转换成string
        const blodToString = (blob)=>{
            return new Promise((resolve,reject)=>{
                reader.onloadend = (e)=>{
                    resolve(md5(e.target.result));
                }
                reader.readAsText(blob);
            });
        }
        //file转换成chunks
        const fileToChunks = (file)=>{
            //切片数组
            let chunks = [];
            //每个chunk大小
            const chunkSize = 1024*128;
            //转换成多少个chunk
            const chunkCount = Math.ceil(file.size/chunkSize);
            for(let i=0;i<chunkCount;i++){
                if(i==chunkCount-1){
                    chunks.push(file.slice(i*chunkSize,file.size));
                }else{
                    chunks.push(file.slice(i*chunkSize,(i+1)*chunkSize));
                }
            }
            return chunks;
        }
        //上传切片
        const postChunk = (chunk,fileMD5,chunkMD5)=>{
            let fd = new FormData();
            //文件流的key与multer的single方法的参数param,必须保持一致,不一致接收不到文件流
            /**
             * fd.append('xxx',chunk);
             * multer().single('xxxx');
             * **/
            fd.append('chunk',chunk);   
            fd.append('fileMD5',fileMD5);
            fd.append('chunkMD5',chunkMD5);
            let postCount = 0;
            return new Promise((resolve,reject)=>{
                instance.post('upload/upload',fd).then((res)=>{
                    if(res.data&&res.data.success){
                        postChunkObj[fileMD5][chunkMD5].success = true;
                        resolve(console.log(`上传切片${chunkMD5}成功`));
                    }else{
                        //记录当前切片上传次数
                        postChunkObj[fileMD5][chunkMD5].count++;
                        //当前切片上传超过10次,终止上传
                        if(postChunkObj[fileMD5][chunkMD5].count<11) { 
                            //上传失败,重新上传
                            postChunk(chunk,fileMD5,chunkMD5);
                        }
                    }
                    resolve(console.log(`上传切片${chunkMD5}失败`))
                })
            });
        }
        //合并多个chunk
        mergeChunks = (fileMD5,chunkMD5Arr,type)=>{
            // let fd = new FormData();
            // fd.append('chunk',Buffer.form(''));   
            // fd.append('fileMD5',fileMD5);
            // fd.append('chunkMD5Arr',chunkMD5Arr);
            // fd.append('type',type);
            // instance.post('upload/merge',fd).then((res)=>{

            // });
            axios.post('http://localhost:1990/upload/merge',{fileMD5,chunkMD5Arr,type}).then((res)=>{

            });
        }
    </script>
    
</body>
</html>

七、开启jwt验证token,通过login接口生成token

添加用户
url:http://localhost:1990/user/login
name:/user/login
params:{
“userName”: “longlongago1”,
“password”: “long123456”
}
在这里插入图片描述

总结

踩坑路漫漫长@~@

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值