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

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

文章目录

  • 前言
  • 一、安装multer,fs-extra
  • 二、新建config/upload.js
  • 三、新建routes/upload.js
  • 四、修改routes下的index.js
  • 五、修改index.js
  • 六、新建上传文件test.html
  • 七、开启jwt验证token,通过login接口生成token
  • 总结


前言

需求:主要学习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
parans:{
“userName”: “longlongago1”,
“password”: “long123456”
}
在这里插入图片描述

总结

踩坑路漫漫长@~@

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/419315.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式通信协议对比介绍(SPI/I2C/UART/RS485/CAN)

1 前言 首先来看看一些基本的概念&#xff1a; 串行通信&#xff1a; 这指的是在通信线路上一次只能传输一个位&#xff08;或字节&#xff09;的数据。每个位依次传输&#xff0c;因此通信是按顺序进行的。这可以用于较简单的系统或长距离通信&#xff0c;但可能导致较低的传…

抖音视频评论关键词批量下载软件|视频内容提取工具

我们团队自主研发的视频评论关键词批量下载软件&#xff0c;为您带来全新的视频获取方式&#xff0c;让您轻松实现对特定关键词的视频搜索和批量下载。以下是软件操作说明的框架&#xff1a; 软文框架&#xff1a; 操作简要说明&#xff1a;说明使用工具的基本流程&#xff0c…

记录java.util.Map.getOrDefault()方法导致JSP编译失败异常:Unable to compile class for JSP

记录java.util.Map.getOrDefault()方法导致JSP编译失败异常:Unable to compile class for JSP; 一、报错信息&#xff1a; type Exception report message Unable to compile class for JSP: description The server encountered an internal error that prevented it from fu…

C 嵌入式系统设计模式 15:基本并发概念

本书的原著为&#xff1a;《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》&#xff0c;讲解的是嵌入式系统设计模式&#xff0c;是一本不可多得的好书。 本系列描述我对书中内容的理解。本文章描述嵌入式并发和资源管理模式之一…

芯科科技为全球首批原生支持Matter-over-Thread的智能锁提供强大助力,推动Matter加速成为主流技术

智能锁领域的先锋企业U-tec和Nuki选择芯科科技解决方案&#xff0c;成为Matter-over-Thread应用的领先者 致力于以安全、智能无线连接技术&#xff0c;建立更互联世界的全球领导厂商Silicon Labs&#xff08;亦称“芯科科技”&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;今…

软考中级 软件设计师备考经验

考试介绍 软考中级的软件设计师需要考两个部分&#xff0c;选择题和大题&#xff0c;每科满分75&#xff0c;需要在同一次考试中两科同时大于等于45分才算通过。考试的内容包括计算机组成原理、数据结构、数据库、专业英语、信息安全、计算机网络等&#xff0c;范围比较广但考…

T2I:zero shot笔记

1 Title Zero-Shot Text-to-Image Generation&#xff08;Aditya Ramesh 、 Mikhail Pavlov 、Gabriel Goh Scott Gray、 Chelsea Voss 、 Alec Radford 、 Mark Chen 、Ilya Sutskever&#xff09; 2 Conclusion This study describes a simple approach for this ta…

在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新

文章目录 一、需求背景二、token刷新的方案1、根据过期时间重新获取2、定时刷新token接口3、使用了RefreshToken 三、关于RefreshToken四、Refresh Token的优点五、Refresh Token的工作原理六、Refresh Token的使用流程七、Refresh Token的实现步骤1、登录成功后保存AccessToke…

持安科技孙维伯:零信任在攻防演练下的最佳实践|DISCConf 2023

近日&#xff0c;在2023数字身份安全技术大会上&#xff0c;持安科技联合创始人孙维伯应主办方的特别邀请&#xff0c;发表了主题为“零信任在攻防演练下的最佳实践”的演讲。 孙维伯在2023数字身份安全技术大会上发表演讲 以下为本次演讲实录&#xff1a; 我是持安科技的联合…

WPF 【十月的寒流】学习笔记(2):MVVM中是怎么实现通知的

文章目录 前言相关链接代码仓库项目配置代码初始代码ViewPersonViewModel 尝试老办法通知解决方案ObservableCollectionBindingListICollectionView 总结 前言 我们这次详细了解一下列表通知的底层是怎么实现的 相关链接 十月的寒流 MVVM实战技巧之&#xff1a;可被观测的集合…

深入理解Java中的优先级队列(堆)——PriorityQueue

引言&#xff1a; 在Java中&#xff0c;优先级队列&#xff08;PriorityQueue&#xff09;是一种基于堆结构实现的队列&#xff0c;其中每个元素都有一个优先级&#xff0c;优先级高的元素在队列中具有更高的优先级&#xff0c;排在前面。优先级队列常用于任务调度、事件处理等…

js 面试运行机制和存储(从以下几方面理解),栈和堆的理解

1 工作原理 每个浏览器都有自己的引擎&#xff0c;通过引擎把代码解析运行起来。 2 生命周期 3-1 内存分配 3-2 内存使用 3-3 内存回收 3 栈和堆的理解 timer也是个函数--所以也是引用类型。 4 如何运行 以下可忽略 首先声明变量&#xff0c;放在左侧栈中执行&#xff0c;在执行…

MATLAB环境下一种新颖的类脉冲信号的高分辨率时频分析方法

一般情况下&#xff0c;机械振动信号或地震信号是非平稳的。而传统傅立叶变换只能应用于平稳信号分析&#xff0c;故不适用于非平稳信号。所以&#xff0c;我们需要采用时频分析方法。时频分析方法能达到同时在时间域和频率域对信号进行分析的目的&#xff0c;得到信号在不同时…

makefileGDB使用

一、makefile 1、make && makefile makefile带来的好处就是——自动化编译&#xff0c;一旦写好&#xff0c;只需要一个make命令&#xff0c;整个工程完全自动编译&#xff0c;极大的提高了软件开发的效率 下面我们通过如下示例来进一步体会它们的作用&#xff1a; ①…

从预训练到通用智能(AGI)的观察和思考

1.预训练词向量 预训练词向量&#xff08;Pre-trained Word Embeddings&#xff09;是指通过无监督学习方法预先训练好的词与向量之间的映射关系。这些向量通常具有高维稠密特征&#xff0c;能够捕捉词语间的语义和语法相似性。最著名的预训练词向量包括Google的Word2Vec&#…

(2024,MixLoRA,任务干扰,独立因子选择,条件因子选择)使用 LoRA 的条件混合进行多模态指令调优

Multimodal Instruction Tuning with Conditional Mixture of LoRA 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 任务干扰在多模态指令调优中的 LoRA 应用 3.1 背景&am…

深度神经网络联结主义的本质

一、介绍 在新兴的人工智能 (AI) 领域&#xff0c;深度神经网络 (DNN) 是一项里程碑式的成就&#xff0c;突破了机器学习、模式识别和认知模拟的界限。这一技术奇迹的核心是一个与认知科学本身一样古老的思想&#xff1a;联结主义。本文深入探讨了联结主义的基本原理&#xff0…

Nodejs 第四十三章(redis)

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据结构存储系统&#xff0c;它提供了一个高效的键值存储解决方案&#xff0c;并支持多种数据结构&#xff0c;如字符串&#xff08;Strings&#xff09;、哈希&#xff08;Hashes&#xff09;、列表&a…

React之组件定义和事件处理

一、组件的分类 在react中&#xff0c;组件分为函数组件和class组件&#xff0c;也就是无状态组件和有状态组件。 * 更过时候我们应该区别使用无状态组件&#xff0c;因为如果有状态组件会触发生命周期所对应的一些函数 * 一旦触发他生命周期的函数&#xff0c;它就会影响当前项…

X-pin扁线电机制造工艺复杂 联合电子率先实现其量产

X-pin扁线电机制造工艺复杂 联合电子率先实现其量产 扁线电机是扁平铜包线绕组的电机。扁线电机是目前主流电机绕组形式&#xff0c;根据技术路线不同&#xff0c;扁线电机分为I-pin扁线电机、Hair-pin扁线电机、X-pin扁线电机等&#xff0c;其中X-pin扁线电机是指采用X-pin绕组…