常见方法
FormData
前端
1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="file" id="fileInp" accept="image/*">
<br>
<img src="" alt="" id="serverImg">
<script src="./js/md5.min.js"></script>
<script src="./js/ajax.js"></script>
<script>
const limitType = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']
const limitSize = 100 * 1024
fileInp.onchange = async function() {
let file = fileInp.files[0]
if(!file) return
if(!limitType.includes(file.type)){
alert('文件格式不支持!')
fileInp.value = null
return
}
if(file.size > limitSize) {
alert('文件大小超限!')
fileInp.value = null
return
}
let formData = new FormData() // Content-Type: mutilpart/form-data
formData.append('chunk', file)
formData.append('filename', $formatFileName(file.name).filename)
const result = await $ajax({
url: 'http://127.0.0.1:5678/single',
data: formData
})
if (result.code == 0) {
serverImg.src = result.path
}
}
</script>
</body>
</html>
js/ajax.js
function $ajax(options) {
options = Object.assign({
url: '',
method: 'post',
data: null,
headers: {}
}, options)
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest
xhr.open(options.method, options.url)
Object.keys(options.headers).forEach(key => {
xhr.setRequestHeader(key, options.headers[key])
})
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(/^(2|3)\d{2}$/.test(xhr.status)) {
resolve(JSON.parse(xhr.responseText))
return
}
reject(xhr)
}
}
xhr.send(options.data)
})
}
function $formatFileName(filename) {
const dotIndex = filename.lastIndexOf('.')
let name = filename.substring(0, dotIndex)
const ext = filename.substring(dotIndex+1)
name = md5(name) + new Date().getTime()
return {
hash: name,
ext,
filename: `${name}.${ext}`
}
}
后端
server.js
const express = require('express')
const bodyParser = require('body-parser')
const multiparty = require('multiparty')
const fs = require('fs')
const path = require('path')
const config = require('./config')
const app = express()
app.listen(config.PORT, () => {
console.log(`server is running port : ${config.PORT}`)
})
app.use((req, res, next) => {
const {
ALLOW_ORIGIN,
CREDENTIALS,
HEADERS,
ALLOW_METHODS
} = config.CROS
res.setHeader('Access-Control-Allow-Origin', ALLOW_ORIGIN)
res.setHeader('Access-Control-Allow-Credentials', CREDENTIALS)
res.setHeader('Access-Control-Allow-Headers', HEADERS)
res.setHeader('Access-Control-Allow-Methods', ALLOW_METHODS)
req.method === 'OPTIONS' ? res.send('Current services support CROSS domain request') : next()
})
app.use(bodyParser.urlencoded({
extended: false,
limit: '1024mb'
}))
const upload_dir = path.resolve(__dirname, '', 'upload')
app.post('/single', (req,res) => {
new multiparty.Form().parse(req, function (err, fields, file) {
if (err) {
res.send({
code: 1,
msg: err
})
return
}
const [chunk] = file.chunk
const [filename] = fields.filename
const chunk_dir = `${upload_dir}/${filename}`
const readStream = fs.createReadStream(chunk.path)
const writeStream = fs.createWriteStream(chunk_dir)
readStream.pipe(writeStream)
readStream.on('end', function(){
fs.unlinkSync(chunk.path)
})
res.send({
code: 0,
msg: '',
path: `http://127.0.0.1:${config.PORT}/upload/${filename}`
})
})
})
app.use(express.static('./'))
app.use((req, res) => {
res.status(404)
res.send('Not Found')
})
config.js
module.exports = {
PORT: 5678,
CROS: {
ALLOW_ORIGIN: '*',
ALLOW_METHODS: 'PUT,POST,GET,DELETE,OPTIONS,HEAD',
HEADERS: 'Content-Type,Content-Length,Authorization, Accept,X-Requested-With',
CREDENTIALS: false
}
}
Base64编码
前端
2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="file" id="fileInp" accept="image/*">
<br>
<img src="" alt="" id="serverImg">
<script src="./js/md5.min.js"></script>
<script src="./js/ajax.js"></script>
<script>
fileInp.onchange = async function() {
let file = fileInp.files[0]
if(!file) return
const base64 = await converBase64(file)
const result = await $ajax({
url: 'http://127.0.0.1:5678/single2',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: `chunk=${encodeURIComponent(base64)}&filename=${$formatFileName(file.name).filename}`
})
if (result.code == 0) {
serverImg.src = result.path
}
}
function converBase64(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.readAsDataURL(file)
fileReader.onload = ev => {
// console.log(ev.target.result)
resolve(ev.target.result)
}
})
}
</script>
</body>
</html>
后端
server.js
app.post('/single2', (req, res) => {
let { chunk, filename } = req.body
const chunk_dir = `${upload_dir}/${filename}`
chunk = decodeURIComponent(chunk).replace(/^data:image\/\w+;base64,/, '')
chunk = Buffer.from(chunk, 'base64')
fs.writeFileSync(chunk_dir, chunk)
res.send({
code: 0,
msg: '',
path: `http://127.0.0.1:${config.PORT}/upload/${filename}`
})
})
大文件上传
思路及原理:
1.将大文件切片 ,file是Blob类的实例,利用其slice方法可以将文件切片(HTTP可以多个并发传递(6-7))
2.同时并发n个切片的上传
3.等n个都上传完,再发送请求合并切片(为啥不自动合并呢)
前端
3.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="file" id="fileInp" accept="image/*">
<br>
<span id="progress">0%</span>
<br>
<img src="" alt="" id="serverImg">
<script src="./js/md5.min.js"></script>
<script src="./js/ajax.js"></script>
<script>
const _data = new Proxy({ total: 0 }, {
set(target, key, value) {
target[key] = value
if (_data.total > 100) {
progress.innerHTML = '上传完成'
return
}
progress.innerHTML = `${_data.total}%`
}
})
fileInp.onchange = async function() {
let file = fileInp.files[0]
if(!file) return
// 1.切片
const partSize = file.size / 5
let cur = 0
let i = 0
let partList = []
let { hash, ext, filename } = $formatFileName(file.name)
while(i < 5) {
partList.push({
chunk: file.slice(cur, cur + partSize),
filename: `${hash}-${i}.${ext}`
})
cur += partSize
i++
}
// 2.并发上传
partList = partList.map(item => {
const formData = new FormData()
formData.append('chunk', item.chunk)
formData.append('filename', item.filename)
return $ajax({
url: 'http://127.0.0.1:5678/chunk',
data: formData
}).then(result => {
if (result.code == 0) {
_data.total += 20
return Promise.resolve(result)
}
return Promise.reject(result)
})
})
// 3.合并切片
await Promise.all(partList)
// 延时
await new Promise(resolve => {
setTimeout(_ => {
resolve()
}, 200)
})
const result = await $ajax({
url: 'http://127.0.0.1:5678/merge',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: `filename=${filename}`
})
if (result.code === 0) {
serverImg.src = result.path
}
}
</script>
</body>
</html>
后端
server.js
app.post('/chunk', (req, res) => {
new multiparty.Form().parse(req, function(err, fields, file){
if (err) {
res.send({
code: 1,
msg: err
})
}
let [chunk] = file.chunk
let [filename] = fields.filename
let filepath = filename.substring(0, filename.indexOf('-'))
let chunk_dir = `${upload_dir}/${filepath}`
if(!fs.existsSync(chunk_dir)) {
fs.mkdirSync(chunk_dir)
}
chunk_dir = `${upload_dir}/${filepath}/${filename}`
const readStream = fs.createReadStream(chunk.path)
const writeStream = fs.createWriteStream(chunk_dir)
readStream.pipe(writeStream)
readStream.on('end', function() {
fs.unlinkSync(chunk.path)
})
res.send({
code: 0,
msg: ''
})
})
})
app.post('/merge', (req, res) => {
const { filename } = req.body
const dotIndex = filename.lastIndexOf('.')
const filepath = `${upload_dir}/${filename.substring(0, dotIndex)}`
const filenamePath = `${upload_dir}/${filename}`
fs.writeFileSync(filenamePath, '')
const pathList = fs.readdirSync(filepath)
pathList.sort((a, b) => a.localeCompare(b))
.forEach(item => {
fs.appendFileSync(filenamePath, fs.readFileSync(`${filepath}/${item}`))
fs.unlinkSync(`${filepath}/${item}`)
})
fs.rmdirSync(filepath)
res.send({
code: 0,
msg: '',
path: `http://127.0.0.1:${config.PORT}/upload/${filename}`
})
})
附 ajax单文件上传进度显示
ajax.js
function $ajax(options) {
options = Object.assign({
url: '',
method: 'post',
data: null,
headers: {},
progress: Function.prototype
}, options)
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest
xhr.upload.onprogress = options.progress
xhr.open(options.method, options.url)
Object.keys(options.headers).forEach(key => {
xhr.setRequestHeader(key, options.headers[key])
})
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(/^(2|3)\d{2}$/.test(xhr.status)) {
resolve(JSON.parse(xhr.responseText))
return
}
reject(xhr)
}
}
xhr.send(options.data)
})
}
4.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="file" id="fileInp" accept="image/*">
<br>
<span id="progress">0%</span>
<br>
<img src="" alt="" id="serverImg">
<script src="./js/md5.min.js"></script>
<script src="./js/ajax.js"></script>
<script>
const _data = new Proxy({ total: 0 }, {
set(target, key, value) {
target[key] = value
if (_data.total > 100) {
progress.innerHTML = '上传完成'
return
}
progress.innerHTML = `${_data.total}%`
}
})
fileInp.onchange = async function() {
let file = fileInp.files[0]
if(!file) return
let formData = new FormData() // Content-Type: mutilpart/form-data
formData.append('chunk', file)
formData.append('filename', $formatFileName(file.name).filename)
const result = await $ajax({
url: 'http://127.0.0.1:5678/single',
data: formData,
progress: ev => {
// ev.loaded 已经上传的大小
// ev.total 总大小
_data.total = Math.ceil(ev.loaded / ev.total * 100)
}
})
if (result.code == 0) {
serverImg.src = result.path
}
}
</script>
</body>
</html>