# 接口列表

接口描述接口 URL
文件上传接口POST:/file/upload
文件查询接口GET:/file/meta
文件下载接口GET:/file/download
文件重命名接口POST:/file/update
文件删除接口POST:/file/delete

项目源码

1
2
链接:https://pan.baidu.com/s/1Yl7kDSdwSmi9m45JDqnftg?pwd=a79t 
提取码:a79t

# 上传接口

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

# 项目文件

1
2
3
4
5
6
7
8
9
10
- 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)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      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) 将上传的文件复制到本地
      • 返回一个重定向页面。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        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:%s\n", 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:%s\n", err.Error())
                    return
                }
                defer newFile.Close()
                //数据复制
                fileMeta.FileSize, err = io.Copy(newFile, file)
                if err != nil {
                    fmt.Printf("Filed to save file,err:%s\n", 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 获取文件的元信息对象
    • 代码如下所示
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      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 对应的函数

  • 返回的是元信息的结构体数据。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 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 ),最后将读取到的文件返回给客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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数组即可。但网页端需要设置响应头,以上两行是为网页设置的
}

# 文件删除

文件删除包含两个删除

  • 删除文件元信息
  • 物理删除

# 删除文件元信息

该部分需要在 filemeta 下定义一个删除函数

1
2
3
4
//删除元信息  
func RemoveFileMeta(fileSha1 string){
delete(fileMetas,fileSha1)
}

# 物理删除

1
2
3
4
5
6
7
8
9
10
11
func FileDeleteHandler(writer http.ResponseWriter, request *http.Request) {  
request.ParseForm()
fileHash := request.Form.Get("filehash")

//物理删除
fileMeta := meta.GetFileMeta(fileHash)
os.Remove(fileMeta.Location)
meta.RemoveFileMeta(fileHash) //从内存中删除
fmt.Println("删除成功")
writer.WriteHeader(http.StatusOK)
}

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

colagold 微信支付

微信支付

colagold 支付宝

支付宝