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+'/'+chunkMD5if(!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+porttimeout: 5000,headers: {'Content-Type': 'multipart/form-data',"authorization":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6Imxvbmdsb25nYWdvMSIsInBhc3N3b3JkIjoibG9uZzEyMzQ1NiIsImlhdCI6MTcwOTIwMjQ1NiwiZXhwIjoxNzA5MjA2MDU2fQ.XztBbjm2BbeQB7-OIXX040xuNxR5gnioCCgNV2c5NGI",},withCredentials: false, // defaultresponseType: 'json', // defaultmaxContentLength: 2000,});//上传文件计数对象let postFileObj = {};//上传切片计数对象let postChunkObj = {};//组件上传文件const goUpload = (files)=>{postFileObj = {};postChunkObj = {};upload(files[0])}//上传文件const upload = async (file)=>{//文件blobconst 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转换成stringconst blodToString = (blob)=>{return new Promise((resolve,reject)=>{reader.onloadend = (e)=>{resolve(md5(e.target.result));}reader.readAsText(blob);});}//file转换成chunksconst fileToChunks = (file)=>{//切片数组let chunks = [];//每个chunk大小const chunkSize = 1024*128;//转换成多少个chunkconst 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}失败`))})});}//合并多个chunkmergeChunks = (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”
}

总结
踩坑路漫漫长@~@
相关文章:
express+mysql+vue,从零搭建一个商城管理系统7--文件上传,大文件分片上传
提示:学习express,搭建管理系统 文章目录 前言一、安装multer,fs-extra二、新建config/upload.js三、新建routes/upload.js四、修改routes下的index.js五、修改index.js六、新建上传文件test.html七、开启jwt验证token,通过login接…...
markdown的使用(Typora)
文章目录 markdown的使用(Typora)一.标题二.段落格式2.1 换行2.2 分割线2.3 字体2.4 上下标2.5 脚注2.6 改变字体颜色 三.列表3.1 无序列表3.2 有序列表3.3 列表嵌套3.4 任务列表 四.区块五.代码显示5.1 行内代码5.2 代码块 六.链接七.图片八.表格九.表情符号大纲十、流程图10.…...
【python】json转成成yaml中文编码异常显示成:\u5317\u4EAC\u8DEF123\u53F7
姊妹篇:【python】json转成成yaml json数据 {"name": "张三","age": 30,"isMarried": false,"children": [{"name": "小王","age": 5},{"name": "小李",&qu…...
Python 实现Excel自动化办公(中)
在上一篇文章的基础上进行一些特殊的处理,这里的特殊处理主要是涉及到了日期格式数据的处理(上一篇文章大家估计也看到了日期数据的处理是不对的)以及常用的聚合数据统计处理,可以有效的实现你的常用统计要求。代码如下࿱…...
MCTS代码
这段代码的背景是玩一个游戏。游戏的参数有NUM_TURNS,在第i回合,你可以从一个整数[-2,2,3,-3]*(NUM_TURNS1-i)中进行选择。例如,在一个4回合的游戏中,在第1回合,你可以从[-8,8,12&am…...
Java 中notify 和 notifyAll 方法介绍
1. notify 方法 notify() 方法是 Java 中 Object 类的一个方法,它用来唤醒在该对象的监视器(monitor)上等待的单个线程。如果有多个线程都在该对象上等待,则会随机唤醒其中一个线程。被唤醒的线程将会尝试重新获取对象锁ÿ…...
Leetcode :杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 思路:双循环,一个是层数,一个是当前数组的生成;两侧为1,需要边界判断条件…...
MWC 2024丨美格智能CEO杜国彬出席中国联通创新成果发布会并发表主题演讲
2月26日,中国联通在MWC2024 巴塞罗那期间举办了以“算网为基,智领未来”为主题的创新成果发布会,集中展示最新的创新成果与最佳实践。 中国通信标准化协会理事长闻库、GSMA首席财务官Louise Easterbrook、中国联通副总经理梁宝俊、华为ICT销…...
个人建站前端篇(七)vite + vue3企业级项目模板
一、vite命令行创建项目 npm create vitelatest根据提示选择模板,选择vite vue3 ts即可。 二、项目连接远程仓库 git init git remote add origin https://gitee.com/niech_project/vite-vue3-template.git git pull origin master git checkout -b dev三、项目…...
centos7 安装 docker-compose
1、直接参考官方: Install Compose standalone | Docker Docs 1、安装命令 curl -SL https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose 2、修改 docker-compose 执行权限 不修改执行权…...
剑指offer面试题28:对称的二叉树
#试题28:对称的二叉树 题目: 请设计一个函数判断一棵二叉树是否 轴对称 。 示例 1: 输入:root [6,7,7,8,9,9,8] 输出:true 解释:从图中可看出树是轴对称的。示例 2: 输入:root …...
JS:原型与原型链(附带图解与代码)
一、原型 写在前面: 任何对象都有原型。 函数也是对象,所以函数也有原型。 1.什么是原型 在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]],它要么为 null,要么就是对另一个对象的引用,该对象…...
电子电器架构新趋势 —— 最佳着力点:域控制器
电子电器架构新趋势 —— 最佳着力点:域控制器 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师…...
C++记录
常用快捷键: CTRL -向后定位 CTRL SHIFT -向前定位 1.注释:CTRLKC 2.取消注释:CTRLKU 11.调试(启动):F5 20.查找:CTRLF 21.替换:CTRLH 31.跳转到指定的某一行 1)方法1:组合键“CtrlG…...
ConcurrentModificationException并发修改异常
ConcurrentModificationException并发修改异常 原因分析 可以通过遍历索引也可以通过迭代器进行遍历。在我们使用迭代器进行遍历集合的时候,会获取到当前集合的迭代对象。在里面有封装了迭代器的remove方法与集合自带的remove方法,如果我们调用迭代器对…...
小程序事件处理
事件处理 一个应用仅仅只有界面展示是不够的,还需要和用户做交互,例如:响应用户的点击、获取用户输入的值等等,在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作 1. 事件绑定和事件对象 小程序中绑定事件与…...
蓝桥杯-单片机组基础6——定时计数器与外部中断混合使用(附小蜜蜂课程代码)
蓝桥杯单片机组备赛指南请查看这篇文章:戳此跳转蓝桥杯备赛指南文章 本文章针对蓝桥杯-单片机组比赛开发板所写,代码可直接在比赛开发板上使用。 型号:国信天长4T开发板(绿板),芯片:IAP15F2K6…...
交友社交软件开发-php交友聊天系统-
为了开发一个高效的交友系统,需要一个完善的信息管理和筛选机制。这个系统应该能够根据用户的个人信息、兴趣爱好、价值观等标准进行筛选,并向用户提供符合他们要求心仪的人的信息。为了实现这个目标,系统可以利用人工智能技术,分…...
vue2 开发记录
el-select 如何修改选择项的样式/el-select-dropdown__item 文字上下显示 测试代码 <div stylemargin-left: 100px><!-- 测试代码--><el-select filterablesizemini><div classxxx-el-select><el-optionv-foritem in [{key:1,des:2,…...
QML中表格中数据获取
1.在生成的动态表格中获取某格数据的内容 import QtQuick 2.15 import QtQuick.Window 2.15import QtQuick.Controls 2.0 import Qt.labs.qmlmodels 1.0 import QtQuick.Layouts 1.15Window {width: 640height: 480visible: truetitle: qsTr("Hello World")TableMod…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
