Go语言实现文件服务器

2023-05-31,

主调函数,设置路由表

package main

import (
"fmt"
"net/http"
"store/handler"
) func main() {
http.HandleFunc("/file/upload", handler.UploadHandler)
http.HandleFunc("/file/upload/suc", handler.UploadSucHandler)
http.HandleFunc("/file/meta", handler.GetFileMetaHandler)
http.HandleFunc("/file/download", handler.DownloadHandler)
http.HandleFunc("/file/delete", handler.FileDelHandler)
http.HandleFunc("/file/update", handler.FileMetaUpdateHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Printf("Failed to start server: %s", err.Error())
}
}

文件元信息

package meta

// FileMeta 文件元信息结构
type FileMeta struct {
FileSha1 string // 文件哈希
FileName string // 文件名
FileSize int64 // 文件大小
Location string // 文件位置
UploadAt string // 上传时间
} // 保存文件元信息映射
var fileMetas map[string]FileMeta func init() {
fileMetas = make(map[string]FileMeta) // 初始化
} // UpdateFileMeta 更新文件元信息
func UpdateFileMeta(fileMeta FileMeta) {
fileMetas[fileMeta.FileSha1] = fileMeta
} // GetFileMeta 获取文件元信息对象
func GetFileMeta(fileSha1 string) FileMeta {
return fileMetas[fileSha1]
} // RemoveFileMeta 删除文件元信息
func RemoveFileMeta(fileSha1 string) {
delete(fileMetas, fileSha1)
}

handler逻辑

package handler

import (
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"store/meta"
"store/utils"
"time"
) const storePath = "./tmp/" func UploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
// HTML 上传页面
data, err := ioutil.ReadFile("./static/view/index.html")
if err != nil {
_, _ = io.WriteString(w, "internal server error")
return
}
_, _ = io.WriteString(w, string(data))
} else if r.Method == "POST" {
// 接收文件流
file, head, err := r.FormFile("file")
if err != nil {
log.Printf("Failed to get data: %s\n", err.Error())
_, _ = io.WriteString(w, "")
return
}
defer file.Close() fileMeta := meta.FileMeta{
FileName: head.Filename,
Location: storePath + head.Filename,
UploadAt: time.Now().Format("2006-01-02 15:04:05"),
} newFile, err := os.Create(fileMeta.Location)
if err != nil {
log.Printf("Failed to create file: %s\n", err.Error())
return
}
defer newFile.Close()
_, err = io.Copy(newFile, file)
if err != nil {
log.Printf("Failed to save data into file: %s\n", err.Error())
return
} _, _ = newFile.Seek(0, 0)
fileMeta.FileSha1 = utils.FileSha1(newFile)
meta.UpdateFileMeta(fileMeta) http.Redirect(w, r, "/file/upload/suc", http.StatusFound)
}
} func UploadSucHandler(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "Upload successfully")
} // GetFileMetaHandler 获取文件元信息
func GetFileMetaHandler(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
fileHash := r.Form["filehash"][0]
fileMeta := meta.GetFileMeta(fileHash)
data, err := json.Marshal(fileMeta)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
_, _ = w.Write(data)
} func DownloadHandler(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
fileHash := r.Form.Get("filehash")
fileMeta := meta.GetFileMeta(fileHash)
file, err := os.Open(fileMeta.Location)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
} w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment;filename=\""+fileMeta.FileName+"\"")
_, _ = w.Write(data)
} func FileDelHandler(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
fileHash := r.Form.Get("fileHash")
fileMeta := meta.GetFileMeta(fileHash)
_ = os.Remove(fileMeta.Location)
meta.RemoveFileMeta(fileHash)
w.WriteHeader(http.StatusOK)
} func FileMetaUpdateHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
} _ = r.ParseForm()
opType := r.Form.Get("op")
if opType != "0" {
w.WriteHeader(http.StatusForbidden)
return
} fileHash := r.Form.Get("filehash")
newFileName := r.Form.Get("filename")
curFileMeta := meta.GetFileMeta(fileHash)
curFileMeta.FileName = newFileName
meta.UpdateFileMeta(curFileMeta) data, err := json.Marshal(curFileMeta)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
}

加密

package utils

import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"hash"
"io"
"os"
"path/filepath"
) type Sha1Stream struct {
_sha1 hash.Hash
} func (obj *Sha1Stream) Update(data []byte) {
if obj._sha1 == nil {
obj._sha1 = sha1.New()
}
obj._sha1.Write(data)
} func (obj *Sha1Stream) Sum() string {
return hex.EncodeToString(obj._sha1.Sum([]byte("")))
} func Sha1(data []byte) string {
_sha1 := sha1.New()
_sha1.Write(data)
return hex.EncodeToString(_sha1.Sum([]byte("")))
} func FileSha1(file *os.File) string {
_sha1 := sha1.New()
_, _ = io.Copy(_sha1, file)
return hex.EncodeToString(_sha1.Sum(nil))
} func MD5(data []byte) string {
_md5 := md5.New()
_md5.Write(data)
return hex.EncodeToString(_md5.Sum([]byte("")))
} func FileMD5(file *os.File) string {
_md5 := md5.New()
_, _ = io.Copy(_md5, file)
return hex.EncodeToString(_md5.Sum(nil))
} func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
} func GetFileSize(filename string) int64 {
var result int64
_ = filepath.Walk(filename, func(path string, f os.FileInfo, err error) error {
result = f.Size()
return nil
})
return result
}

前端:

<html lang="">

<head>
<!-- bootstrap 4.x is supported. You can also use the bootstrap css 3.3.x versions -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.9/css/fileinput.min.css" media="all"
rel="stylesheet" type="text/css" />
<!-- if using RTL (Right-To-Left) orientation, load the RTL CSS file after fileinput.css by uncommenting below -->
<!-- link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.9/css/fileinput-rtl.min.css" media="all" rel="stylesheet" type="text/css" /-->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- piexif.min.js is needed for auto orienting image files OR when restoring exif data in resized images and when you
wish to resize images before upload. This must be loaded before fileinput.min.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.9/js/plugins/piexif.min.js"
type="text/javascript"></script>
<!-- sortable.min.js is only needed if you wish to sort / rearrange files in initial preview.
This must be loaded before fileinput.min.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.9/js/plugins/sortable.min.js"
type="text/javascript"></script>
<!-- purify.min.js is only needed if you wish to purify HTML content in your preview for
HTML files. This must be loaded before fileinput.min.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.9/js/plugins/purify.min.js"
type="text/javascript"></script>
<!-- popper.min.js below is needed if you use bootstrap 4.x. You can also use the bootstrap js
3.3.x versions without popper.min.js. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js"></script>
<!-- bootstrap.min.js below is needed if you wish to zoom and preview file content in a detail modal
dialog. bootstrap 4.x is supported. You can also use the bootstrap js 3.3.x versions. -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js"
type="text/javascript"></script>
<!-- the main fileinput plugin file -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.9/js/fileinput.min.js"></script>
<!-- optionally if you need a theme like font awesome theme you can include it as mentioned below -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-fileinput/4.4.9/themes/fa/theme.js"></script>
<!-- optionally if you need translation for your language then include locale file as mentioned below -->
<title></title>
</head> <body style="width:100%;height:100%;text-align:center;">
<div style="width:60%;height:30%;text-align:center;">
<form action='#' method="post" enctype="multipart/form-data">
<input id="file" name="file" type="file" class="file" data-msg-placeholder="选择文件">
</form>
</div>
</body> </html>

目录结构

.
├── go.mod
├── handler
│   └── handler.go
├── main.go
├── meta
│   └── file.go
├── static
│   └── view
│   ├── index.html
│   └── search.html
├── tmp
│   └── apple.jpg
└── utils
└── utils.go

Go语言实现文件服务器的相关教程结束。

《Go语言实现文件服务器.doc》

下载本文的Word格式文档,以方便收藏与打印。