提示:学习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
params:{
“userName”: “longlongago1”,
“password”: “long123456”
}
总结
踩坑路漫漫长@~@