一、背景
通过
Go
的net/http
包和mulpart
包设计文件存储系统的服务端和客户端。将上传的文件视为一个对象,客户端赋予它一个键,并上传到服务端。
二、客户端
// @File: upload.go
// @Author: Jason
// @Date: 2022/9/12
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"os"
"path"
"strings"
)
var (
dir string
key string
)
func init() {
flag.StringVar(&dir, "path", ".", "需要上传的文件路径")
flag.StringVar(&key, "key", "", "对象的键值")
flag.Parse()
if key == "" {
log.Panicln("the key is empty, you need to use --key to set the key.")
}
}
func UploadDir(key, dir string) {
r, w := io.Pipe()
mp := multipart.NewWriter(w)
go func() {
defer func() {
mp.Close()
w.Close()
}()
for absPath := range getFiles(dir) {
f, _ := os.Open(path.Join(dir, absPath))
fmt.Println(absPath, path.Dir(absPath))
fw, e := mp.CreateFormFile(path.Dir(absPath), absPath)
failOnErr(e, "fail to create form file")
io.Copy(fw, f)
f.Close()
}
}()
client := http.Client{}
req, e := http.NewRequest(http.MethodPut, "http://localhost:8080/", r)
req.Header.Set("Content-Type", mp.FormDataContentType())
req.Header.Set("key", key)
failOnErr(e, "fail to new a request")
res, e := client.Do(req)
failOnErr(e, "fail to do request")
log.Println(res.Status)
}
func main() {
UploadDir(key, dir)
}
// getFiles获取文件夹下的全部文件,通过chan迭代器的模式获取。
func getFiles(dir string) <-chan string {
paths := make(chan string)
go func() {
defer close(paths)
gets(paths, dir)
}()
return paths
}
func gets(paths chan string, dir_ string) {
infos, e := ioutil.ReadDir(dir_)
failOnErr(e, "")
for _, info := range infos {
subPath := path.Join(dir_, info.Name())
if info.IsDir() {
gets(paths, subPath)
} else {
abs := strings.Replace(subPath, dir, "", 1)
paths <- abs[1:]
}
}
}
func failOnErr(e error, msg string) {
if e != nil {
log.Panicf("err: %v, msg: %s", e, msg)
}
}
三、服务端
1、客户端的处理函数
// @File: handler.go
// @Author: Jason
// @Date: 2022/9/12
package server
import (
"fmt"
"io"
"log"
"net/http"
"os"
"path"
)
const (
dataPath = "receive"
)
func SaveDir(w http.ResponseWriter, r *http.Request) {
e := r.ParseMultipartForm(32 << 4)
failOnErr(e, "fail parse multipart form")
form := r.MultipartForm
fields := form.File
key := r.Header.Get("key")
dirPath := path.Join(dataPath, key)
e = os.Mkdir(dirPath, 0777)
if os.IsExist(e) {
w.WriteHeader(http.StatusConflict)
return
}
for k, files := range fields {
fmt.Println(k)
if k != "." {
e = os.Mkdir(path.Join(dirPath, k), 0777)
}
failOnErr(e, "fail to create dir!")
for _, file := range files {
f, e := file.Open()
failOnErr(e, "fail to open file!")
fw, e := os.Create(path.Join(dirPath, k, file.Filename))
failOnErr(e, "fail to create file "+file.Filename)
io.Copy(fw, f)
fw.Close()
f.Close()
}
}
w.WriteHeader(http.StatusOK)
}
func failOnErr(e error, msg string) {
if e != nil {
log.Printf("err: %v, msg: %s\n", e, msg)
}
}
2、main函数
// @File: main.go
// @Author: Jason
// @Date: 2022/9/12
package main
import (
"mulFiles/server"
"net/http"
)
func main() {
http.HandleFunc("/", server.SaveDir)
err := http.ListenAndServe(":8080", nil)
if err != nil {
panic(err)
}
}
四、运行结果
1、项目结构
(1)data
:需要传输的文件夹。
(2)receive
:接收的文件夹目录。
2、运行
(1)运行服务端的main.go
。
(2)命令行运行客户端,制定需要上传的文件夹及键值,键值为服务端接收文件夹下的子文件夹。
client> go run .\upload.go --path=..\data\ --key=key_test
goFiles/go01.go goFiles
goFiles/go02.go goFiles
goFiles/subDir/sub01.go goFiles/subDir
goFiles/subDir/sub02.go goFiles/subDir
goFiles/subDir2/sub2.txt goFiles/subDir2
goFiles/subDir2/subsub/subsub.go goFiles/subDir2/subsub
rootData.txt .
txtData/text1.txt txtData
txtData/text2.txt txtData
2022/09/19 10:31:31 200 OK
(3)运行结果
- 可以看到,需要传输的
data
文件夹传输到(服务端的)receive
文件夹下,对应的文件夹为指定的键值(key_test
)。