GO HTTP大文件上传后端(MultipartReader流式读取),一次读取上传的多个文件
1. GO代码: main.go
package main
import (
_ "embed"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"strings"
)
var index []byte
func upload(w http.ResponseWriter, r *http.Request) {
mr, _ := r.MultipartReader()
var errors = ""
for {
fp, err := mr.NextPart()
if err == io.EOF {
break
}
errors += saveFile(fp)
}
var respStr = []byte("upload ok")
if errors != "" {
respStr = []byte(errors)
}
w.Write(respStr)
}
func saveFile(fp *multipart.Part) string {
var errors = ""
dst, _ := os.OpenFile("./"+fp.FileName(),
os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
defer dst.Sync()
defer dst.Close()
buf := make([]byte, 1024)
fmt.Println("start saveing file ", fp.FileName())
for {
n, err := fp.Read(buf)
if n > 0 {
_, err = dst.Write(buf[0:n])
if err != nil {
errors += err.Error() + "\r\n"
continue
}
}
if err != nil {
if err != io.EOF {
errors += err.Error() + "\r\n"
}
break
}
}
fmt.Println("end saveing file ", fp.FileName())
return errors
}
func download(w http.ResponseWriter, req *http.Request) {
var p = req.URL.Query().Get("path")
p, _ = url.QueryUnescape(p)
f, e := os.Open(p)
if e != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(w, "Bad request")
return
}
defer f.Close()
w.Header().Add("Content-type", "application/octet-stream")
var i = strings.LastIndex(p, "/")
filename := p[i+1:]
w.Header().Add("content-disposition", "attachment;filename=\""+filename+"\"")
_, _ = io.Copy(w, f)
}
func main() {
fmt.Println("by default server will startup with port 8080, params for port is p=port")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write(index)
})
http.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
os.Exit(0)
})
http.HandleFunc("/upload", upload)
http.HandleFunc("/download", download)
var port = "8080"
for _, args := range os.Args {
if strings.HasPrefix(args, "p=") {
port = args[2:]
break
}
}
fmt.Println("server will start on port: ", port)
err := http.ListenAndServe(":"+port, nil)
if err != nil {
fmt.Println("startup error! ", err)
}
}
2. 前端html&js: index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>upload and download</title>
<script>
const downPathChange = e => {
const path = e.value.replaceAll("\\", "/");
const d = document.getElementById("downBtn")
d.href = "/download?path=" + encodeURIComponent(path)
d.download = path.substring(path.lastIndexOf("/") + 1)
}
const uploadFileXhr = () => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
const firstDateTime = new Date().getTime()
let progressTimeoutCount,progressLastDateTime=firstDateTime,progressLastLoaded=0,first=true
let size = 0;
for(const i of fileIn.files){
size += i.size;
}
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const loaded = e.loaded
clearTimeout(progressTimeoutCount)
let timeLong;
progressTimeoutCount=setTimeout(()=>{
const time = new Date().getTime()
timeLong = time-progressLastDateTime
if(first){
first=false
}else if(timeLong<1000){
return
}
const rate = (loaded/size)*100
const speed = (loaded-progressLastLoaded)/1024/1024/timeLong*1000
const timeLongToStart = (time-firstDateTime)/1000/60
uploadProgress.innerText='已上传'+(e.loaded/1024/1024).toFixed(4)+'MB,'+'进度'+rate.toFixed(4) + '%,速率约'+speed.toFixed(4)+'MB每秒,已耗时'+timeLongToStart.toFixed(1)+'分钟。';
progressLastDateTime=time
progressLastLoaded=loaded
})
}
};
xhr.onload = function () {
if (this.readyState === 4&&this.status === 200) {
uploadProgress.innerText=uploadProgress.innerText+" -> 耗时"+((new Date().getTime()-firstDateTime)/1000/60).toFixed(2)+"分钟后上传完成!"+this.responseText;
}
};
xhr.onerror = function () {
if(this.status===0){
uploadProgress.innerText=uploadProgress.innerText+" -> 异常!网络错误!";
}else{
uploadProgress.innerText=uploadProgress.innerText+" -> 异常!上传错误!"+this.status+","+this.responseText;
}
}
const formData = new FormData();
for(const i of fileIn.files){
formData.append("file", i);
}
const sizeMb = size/1024/1024;
document.getElementById('uploadTotal').innerText='总大小:'+(sizeMb<1024?(sizeMb.toFixed(3)+'M'):((sizeMb/1024).toFixed(3)+'G'))+'B,开始时间:'+new Date(firstDateTime).toLocaleString()+'。'
xhr.send(formData);
}
</script>
</head>
<body>
文件<input type="file" id="fileIn" multiple>
<button onclick="uploadFileXhr()">上传</button>
<span id="uploadTotal"></span>
<span id="uploadProgress"></span>
<hr>
文件路径<input id="downPath" onchange="downPathChange(this)">
<a id="downBtn">下载</a>
<hr><a href="/shutdown">停止服务</a>
</body>
</html>
3. go语言在windows编译linux可执行程序
SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build -o simple_upload