简单文件上传

接口列表:

(为了节省代码量只写出关键的函数实现)
实现上传接口:
- 从请求中提取文件。
- 创建一个新的 FileMeta 对象来存储文件信息,如文件名、上传时间和服务器上的存储位置。
- 在服务器上创建一个新文件来存储上传的内容。
- 将上传文件的内容复制到新创建的文件中。
- 计算文件的唯一标识符(SHA1 哈希值)。
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, "internel server error")
return
}
io.WriteString(w, string(data))
// 另一种返回方式:
// 动态文件使用http.HandleFunc设置,静态文件使用到http.FileServer设置(见main.go)
// 所以直接redirect到http.FileServer所配置的url
// http.Redirect(w, r, "/static/view/index.html", http.StatusFound)
} else if r.Method == "POST" {
// 接收文件流及存储到本地目录
file, head, err := r.FormFile("file")
if err != nil {
fmt.Printf("Failed to get data, err:%s\n", err.Error())
return
}
defer file.Close()
fileMeta := meta.FileMeta{
FileName: head.Filename,
Location: "/tmp/" + head.Filename,
UploadAt: time.Now().Format("2024-02-02 12:01:25"),
}//这里用于保存文件元信息,后面会有介绍
newFile, err := os.Create(fileMeta.Location)
if err != nil {
fmt.Printf("Failed to create file, err:%s\n", err.Error())
return
}
defer newFile.Close()
fileMeta.FileSize, err = io.Copy(newFile, file)
if err != nil {
fmt.Printf("Failed to save data into file, err:%s\n", err.Error())
return
}
newFile.Seek(0, 0)
fileMeta.FileSha1 = util.FileSha1(newFile)
//这里可以重定向回原来的上传网页
}
UploadSucHandler:
func UploadSucHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Upload finished!")
}
main中配置路由规则:
http.HandleFunc("/file/upload", handler.HTTPInterceptor(handler.UploadHandler))
http.HandleFunc("/file/upload/suc", handler.HTTPInterceptor(handler.UploadSucHandler))
//端口监听
fmt.Printf("上传服务启动中,开始监听监听[%s]...\n", cfg.UploadServiceHost)
// 启动服务并监听端口
err := http.ListenAndServe(cfg.UploadServiceHost, nil)
if err != nil {
fmt.Printf("Failed to start server, err:%s", err.Error())
}
现在运行go run main.go,访问对应的网站即可
保存文件元信息
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(fmeta FileMeta) {
fileMetas[fmeta.FileSha1] = fmeta
}
// UpdateFileMetaDB : 新增/更新文件元信息到mysql中
func UpdateFileMetaDB(fmeta FileMeta) bool {
return mydb.OnFileUploadFinished(
fmeta.FileSha1, fmeta.FileName, fmeta.FileSize, fmeta.Location)
}
// GetFileMeta : 通过sha1值获取文件的元信息对象
func GetFileMeta(fileSha1 string) FileMeta {
return fileMetas[fileSha1]
}
获取文件元信息接口
整体流程:
- 获取文件的sha1值
- 通过sha1值获取文件的元信息对象
- 将文件的元信息对象序列化为json格式
- 将json格式返回给客户端
func GetFileMetaHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
filehash := r.Form["filehash"][0]
//fMeta := meta.GetFileMeta(filehash)
fMeta, err := meta.GetFileMetaDB(filehash)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if fMeta != nil {
data, err := json.Marshal(fMeta)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(data)
} else {
w.Write([]byte(`{"code":-1,"msg":"no such file"}`))
}
}
文件下载接口
目的:这个函数允许用户从服务器下载文件。
工作原理:
首先从请求中提取"filehash"
使用这个hash从数据库中获取文件的元数据(文件信息)
根据元数据中的文件位置打开文件
读取整个文件内容
设置HTTP响应头,指定内容类型为二进制流,并设置为下载附件
将文件内容写入HTTP响应
关键点:
使用defer确保文件最后被关闭
错误处理:如果打开文件或读取内容失败,返回500内部服务器错误
设置正确的HTTP头,使浏览器将响应作为文件下载而不是在浏览器中打开
func DownloadHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fsha1 := r.Form.Get("filehash")
fm, _ := meta.GetFileMetaDB(fsha1)
f, err := os.Open(fm.Location)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/octect-stream")
// attachment表示文件将会提示下载到本地,而不是直接在浏览器中打开
w.Header().Set("content-disposition", "attachment; filename=\""+fm.FileName+"\"")
w.Write(data)
}
文件更新接口
主要涉及到文件的更新操作,包括文件的更新和删除。
实现逻辑:
func FileMetaUpdateHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
opType := r.Form.Get("op")
fileSha1 := r.Form.Get("filehash")
newFileName := r.Form.Get("filename")
if opType != "0" {
w.WriteHeader(http.StatusForbidden)
return
}
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
curFileMeta := meta.GetFileMeta(fileSha1)
curFileMeta.FileName = newFileName
meta.UpdateFileMeta(curFileMeta)
// TODO: 更新文件表中的元信息记录
data, err := json.Marshal(curFileMeta)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(data)
}
// FileDeleteHandler : 删除文件及元信息
func FileDeleteHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fileSha1 := r.Form.Get("filehash")
fMeta := meta.GetFileMeta(fileSha1)
// 删除文件
os.Remove(fMeta.Location)
// 删除文件元信息
meta.RemoveFileMeta(fileSha1)
// TODO: 删除表文件信息
w.WriteHeader(http.StatusOK)
}

浙公网安备 33010602011771号