Go Http/Web服务 · 2023年5月21日 0

go语言|服务端文件上传、查询与下载【】

接口列表

接口描述 接口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

上传接口

  1. 获取上传页面
  2. 选取本地文件,form形式上传文件
  3. 云端接受文件流,写入本地存储
  4. 云端更新文件元信息集合

项目文件

- 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 接受用户请求的对象指针

    • 逻辑层面:主要分为GETPOST方式
      • 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语言|服务端文件上传、查询与下载

打赏 赞(0) 分享'
分享到...
微信
支付宝
微信二维码图片

微信扫描二维码打赏

支付宝二维码图片

支付宝扫描二维码打赏

文章目录