接口列表
接口描述 | 接口URL |
---|---|
文件上传接口 | POST:/file/upload |
文件查询接口 | GET:/file/meta |
文件下载接口 | GET:/file/download |
项目源码
链接:https://pan.baidu.com/s/1Yl7kDSdwSmi9m45JDqnftg?pwd=a79t
提取码:a79t
运行main.go,浏览器输入:http://localhost:8080/file/upload
上传接口
- 获取上传页面
- 选取本地文件,form形式上传文件
- 云端接受文件流,写入本地存储
- 云端更新文件元信息集合
项目文件
- main.go
- handler
- handler.go
- static
- view
- index.html
- meta
- filemeta.go
- util
- util.go
-
main.go
中主要是建立路由,即访问路径与事务函数的绑定。端口监听。 -
handler.go
定义事务函数 -
filemeta.go
定义文件元信息的结构体,目的是方便查询。 -
util.go
生成hash码、加密的工具
demo分析
-
main.go
- 通过
import
引入定义在handler里的事务函数,以及所需的http包,因为路由规则的绑定是由http包里的http.HandleFunc()
-
http.HandleFunc()
需要传入两个参数。第一个是字符型的url访问路径,第二个是对应的功能函数。 - 还要进行端口监听,
http.ListenAndServe(":8080", nil)
package main
import (
"filestore-server/handler"
"fmt"
"net/http"
)
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)
err := http.ListenAndServe(":8080", nil) //端口监听
if err != nil {
fmt.Printf("Failed to start server,err:%s", err)
}
}
-
handler.go
- 这里是处理请求的核心函数,主要通过
net/http
包处理网页请求的处理,通过io/ioutil
完成文件流的处理。 - 每个函数都有两个参数,
func UploadHandler(writer http.ResponseWriter, request *http.Request)
。 -
writer:http.ResponseWriter
向用户返回数据的对象 -
request: *http.Request
接受用户请求的对象指针
-
逻辑层面:主要分为
GET
和POST
方式 -
GET
是为了第一次访问时,返回文件上传页面。挺有意思的是往writer
里写的是一个字符串。 -
POST
,文件上传是以post的方式上传的,所以判断,如果请求方式是POST
则进行文件存储逻辑。 - 文件存储逻辑
- 首先从请求表单中获取文件流,利用
request.FormFile("file")
方法,里面的字符串是上传时给文件流的一个key。记得用defer关闭 - 创建新的文件路径以保存上传的文件,
newFile, err := os.Create("./tmp/" + head.Filename)
- 使用
io.Copy(newFile, file)
将上传的文件复制到本地 - 返回一个重定向页面。
package handler
import (
"encoding/json"
"filestore-server/meta"
"filestore-server/util"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
// writer:http.ResponseWriter 向用户返回数据的对象
// request: http.Request 接受用户请求的对象指针
func UploadHandler(writer http.ResponseWriter, request *http.Request) {
if request.Method == "GET" {
//返回上传的html页面
data, err := ioutil.ReadFile("./static/view/index.html")
if err != nil {
io.WriteString(writer, "load html error")
return
}
io.WriteString(writer, string(data))
} else if request.Method == "POST" {
//接受文件流及存储到本地目录
file, head, err := request.FormFile("file") //通过表单
if err != nil {
fmt.Printf("Failed to get file,error:%sn", err.Error())
return
}
defer file.Close() //使用defer在函数退出之前关闭资源
// 元信息初始化
fileMeta := meta.FileMeta{
FileName: head.Filename,
Location: "./tmp/" + head.Filename,
UploadAt: time.Now().Format("2006-01-02 15:04:05"),
}
newFile, err := os.Create(fileMeta.Location) //创建文件流,空的
if err != nil {
fmt.Printf("Filed to create file,err:%sn", err.Error())
return
}
defer newFile.Close()
//数据复制
fileMeta.FileSize, err = io.Copy(newFile, file)
if err != nil {
fmt.Printf("Filed to save file,err:%sn", err.Error())
return
}
//移动file句柄
newFile.Seek(0, 0)
fileMeta.FileSha1 = util.FileSha1(newFile)
fmt.Print(fileMeta.FileSha1)
meta.UpdateFileMeta(fileMeta) //加入到hash桶
http.Redirect(writer, request, "/file/upload/suc", http.StatusFound)
}
}
// 上传已完成
func UploadSucHandler(writer http.ResponseWriter, request *http.Request) {
io.WriteString(writer, "Upload Succeed")
}
// GetFileMetaHandler获取文件元信息
func GetFileMetaHandler(writer http.ResponseWriter, request *http.Request) {
request.ParseForm() //解析表单
filehash := request.Form["filehash"][0] //根据表单中的key获取对应数据,获取到的数据是一个数组,但是数组只有一个元素,所以取第一个就好
fMeta := meta.GetFileMeta(filehash)
data, err := json.Marshal(fMeta) //将结构体转换为json
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
return
//http.StatusInternalServerError untyped int = 500
}
writer.Write(data)
}
func DownloadHandler(writer http.ResponseWriter, request *http.Request) {
request.ParseForm()
filehash := request.Form.Get("filehash")
filemeta := meta.GetFileMeta(filehash)
f, err := os.Open(filemeta.Location) //return *os.File
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
return
}
data, err := ioutil.ReadAll(f) //return []byte
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
writer.Header().Set("Content-Type", "application/octect-stream") //
writer.Header().Set("content-disposition", "attachment;filename=""+filemeta.FileName+""")
writer.Write(data) //如果是移动端,传输byte数组即可。但网页端需要设置响应头
}
文件查询
- 文件的唯一标识,hash码
- 因此查询文件时可以通过文件hash获取meta信息
- 文件的关键信息有:文件唯一标识符,文件名,保存路径,存储时间
-
filemeta.go
- 包含元信息结构体的定义,一个存储结构体的
map(key=hashcode,value=filemeta)
, 初始化结构体函数(init
)以及两个接口函数 -
init()
:init函数,先于main函数执行,提前将fileMetas map初始化,用于存储信息。 -
UpdateFileMeta
:filemeta更新;新增或者更新文件元信息 -
GetFileMeta
:通过hash code获取文件的元信息对象
- 代码如下所示
package meta
//文件元信息结构
type FileMeta struct {
FileSha1 string //文件唯一标志
FileName string
FileSize int64
Location string
UploadAt string
}
var fileMetas map[string]FileMeta //key:hash code ; value: filemeta
//初始化:
func init() {
fileMetas = make(map[string]FileMeta)
}
//接口:filemeta更新;新增或者更新文件元信息
func UpdateFileMeta(fmeta FileMeta) {
fileMetas[fmeta.FileSha1] = fmeta
}
//通过hash code获取文件的元信息对象
func GetFileMeta(fileSha1 string) FileMeta {
return fileMetas[fileSha1]
}
在handler.go
对应的函数
- 返回的是元信息的结构体数据。
// GetFileMetaHandler获取文件元信息
func GetFileMetaHandler(writer http.ResponseWriter, request *http.Request) {
request.ParseForm() //解析表单
filehash := request.Form["filehash"][0] //根据表单中的key获取对应数据,获取到的数据是一个数组,但是数组只有一个元素,所以取第一个就好
fMeta := meta.GetFileMeta(filehash)
data, err := json.Marshal(fMeta) //将结构体转换为json
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
return
//http.StatusInternalServerError untyped int = 500
}
writer.Write(data)
}
文件下载
逻辑:前端通过url传输想要下载的文件的hashcode(http://localhost:8080/file/download?filehash=2cf273073890ad63c214a6565bfe981713f4e3
),DownloadHandler解析url信息并获取到hash code,在filemeta map中根据hash码查询对应文件元信息,根据元信息中的路径打开并读取文件([] byte
),最后将读取到的文件返回给客户端。
func DownloadHandler(writer http.ResponseWriter, request *http.Request) {
request.ParseForm()
filehash := request.Form.Get("filehash")
filemeta := meta.GetFileMeta(filehash)
f, err := os.Open(filemeta.Location) //return *os.File
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
return
}
data, err := ioutil.ReadAll(f) //return []byte 文件字节流
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
writer.Header().Set("Content-Type", "application/octect-stream") //
writer.Header().Set("content-disposition", "attachment;filename=""+filemeta.FileName+""")
writer.Write(data) //如果是移动端,传输byte数组即可。但网页端需要设置响应头,以上两行是为网页设置的
}
文章来源于互联网:go语言|服务端文件上传、查询与下载