Go语言实现文件服务器
主调函数,设置路由表
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南