漫画 Go 语言项目实战 文件服务

14 漫画 Go 语言项目实战 文件服务 - 图1

文件服务实际上就是对文件的操作,对于文件的创建,文件的写入。首先还是得学习下Go语言是如何对文件进行操作的。

获取文件信息

Go语言提供了os包。关于文件信息可以通过os这个包操作。还包括了文件的读写等操作。

  1. //通过os.Stat获取文件的信息 FileInfo
  2. fileinfo, err := os.Stat("C:\\Users\\jen\\Desktop\\hjh.png")
  3. if err != nil {
  4. //打印错误信息
  5. fmt.Println(err)
  6. }
  7. fmt.Println(fileinfo.Name()) //打印文件名

os.Stat返回的是一个FileInfo。OS包中FileInfo的定义 包含了文件的名称,大小创建时间,是否是目录的信息。

  1. type FileInfo interface {
  2. Name() string // 文件名称
  3. Size() int64 // 文件大小
  4. Mode() FileMode // 文件权限
  5. ModTime() time.Time // 文件修改时间
  6. IsDir() bool // 是否是文件夹
  7. Sys() interface{} // 基础数据源接口(可以返回nil)
  8. }

文件权限

文件权限是指创建文件时指定的权限,一般有两种表达方式,一是符号表达但是习惯上还是使用八进制的数字表达,例如0777(表示:可读,可写,可执行)。

14 漫画 Go 语言项目实战 文件服务 - 图2

14 漫画 Go 语言项目实战 文件服务 - 图3

创建文件和文件夹

创建文件夹

  1. //MKdir创建文件夹
  2. err := os.Mkdir("testdir", os.ModePerm)
  3. if err != nil {
  4. fmt.Println(err)
  5. }
  6. //MkdirAll创建多级文件夹
  7. err2 := os.MkdirAll("testdir/test/aa", os.ModePerm)
  8. if err2 != nil {
  9. fmt.Println(err2)
  10. }

创建一个文件

  1. file, err := os.Create("test.txt")
  2. if err != nil {
  3. fmt.Println(err)
  4. }
  5. fmt.Println(file)

读写文件

通过os.Open()可以通过只读的模式打开文件。如果还需要操作文件中的内容,向文件中写入数据,可以通过os.OpenFile()方式打开文件。

14 漫画 Go 语言项目实战 文件服务 - 图4

  1. //通过Open只读方式打开文件
  2. file, err := os.Open("test.txt")
  3. if err != nil {
  4. fmt.Println(err)
  5. }
  6. fmt.Println(file.Name())
  7. file2, err2 := os.OpenFile("test2.txt", os.O_RDONLY|os.O_WRONLY|os.O_CREATE, os.ModePerm)
  8. if err2 != nil {
  9. fmt.Println(err2)
  10. }
  11. fmt.Println(file2)

14 漫画 Go 语言项目实战 文件服务 - 图5 文件的操作模式可以是只读模式O_RDONLY,只写模式O_WRONLY,也可以使用只读或者只写模式O_RDWR。在os包定义的常量有以下几种:

  1. const (
  2. O_RDONLY int = syscall.O_RDONLY // 以只读方式打开文件
  3. O_WRONLY int = syscall.O_WRONLY // 以只写方式打开文件
  4. O_RDWR int = syscall.O_RDWR // 以读写方式打开文件
  5. O_APPEND int = syscall.O_APPEND // 写入时向文件追加数据
  6. O_CREATE int = syscall.O_CREAT // 如果不存在创建新文件
  7. O_EXCL int = syscall.O_EXCL //与O_CREATE一起使用,文件必须不存在
  8. O_SYNC int = syscall.O_SYNC // 为同步I/O打开
  9. O_TRUNC int = syscall.O_TRUNC // 打开时截断常规可写文件
  10. )

关闭文件和删除文件 使用程序进行读取文件的时候,读取完成之后需要手动调用Close()关闭文件。

  1. file, err := os.Open("test2.txt")
  2. if err != nil {
  3. fmt.Println(err)
  4. }
  5. //使用结束之后记得关闭file
  6. defer file.Close()
  7. fmt.Println(file)

使用os.Remove()能够删除文件夹和一个空目录。

  1. err := os.Remove("test2.txt")
  2. if err != nil {
  3. fmt.Println(err)
  4. }

如果一个目录中包含了子目录或者目录不是空的,就不能用os.Remove()方法删除。需要使用os.RemoveAll()删除。好用是好用,但是注意,通过程序调用os.RemoveAll()方法,不会经过回收站,删掉就找不回来了。

  1. err := os.RemoveAll("testdir")
  2. if err != nil {
  3. fmt.Println(err)
  4. }

IO操作

IO操作也叫做流操作。I指的是Input,O指的是Output。所以IO操作也叫输入或者输出操作,用于读或者写数据。

读取数据操作

14 漫画 Go 语言项目实战 文件服务 - 图6

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. //第一步打开文件
  8. file, err := os.Open("test.txt")
  9. if err != nil {
  10. fmt.Println(err)
  11. }
  12. //第三步读取完后关闭文件
  13. defer file.Close()
  14. //第二步读取数据
  15. //创建一个切片用于读取数据
  16. data := make([]byte, 4, 4)
  17. //读取数据到切片中
  18. n, e := file.Read(data)
  19. if e != nil {
  20. fmt.Println(e)
  21. }
  22. //读取到的字节数量
  23. fmt.Println(n)
  24. //读取到的数据内容转为string
  25. fmt.Println(string(data))
  26. }

写入数据操作

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. //读取文件
  8. file, err := os.OpenFile("write.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
  9. if err != nil {
  10. fmt.Println(err)
  11. }
  12. //关闭文件
  13. defer file.Close()
  14. //要写入的数据字符串
  15. str := "haojiahuo2020"
  16. b := []byte(str)
  17. //写入到文件
  18. n, e := file.Write(b)
  19. if e != nil {
  20. fmt.Println(e)
  21. }
  22. //写入到文件中的字节数
  23. fmt.Println(n)
  24. }

在写入时使用Write()方法每次都是默认从头开始写入文件。

14 漫画 Go 语言项目实战 文件服务 - 图7

写文件时,操作模式使用os.O_APPEND 表示每次将数据追加到文件末尾。

14 漫画 Go 语言项目实战 文件服务 - 图8

文件复制

14 漫画 Go 语言项目实战 文件服务 - 图9 复制文件方法一 边读边写

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. )
  7. func main() {
  8. //源文件
  9. file1, err1 := os.Open("yuan.txt")
  10. if err1 != nil {
  11. fmt.Println(err1)
  12. }
  13. //目标文件
  14. file2, err2 := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
  15. if err2 != nil {
  16. fmt.Println(err2)
  17. }
  18. //使用结束关闭文件
  19. defer file1.Close()
  20. defer file2.Close()
  21. bytestr := make([]byte, 1024, 1024)
  22. for {
  23. n, e := file1.Read(bytestr)
  24. if e == io.EOF || n == 0 {
  25. fmt.Println("读取文件结束")
  26. break
  27. }
  28. //循环写入读取到的文件
  29. file2.Write(bytestr[:n])
  30. }
  31. }

文件复制方法二 使用内置函数读取复制文件io.Copy(file2, file1)

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. )
  7. func main() {
  8. //源文件
  9. file1, err1 := os.Open("yuan.txt")
  10. if err1 != nil {
  11. fmt.Println(err1)
  12. }
  13. //目标文件
  14. file2, err2 := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE, os.ModePerm)
  15. if err2 != nil {
  16. fmt.Println(err2)
  17. }
  18. //使用结束关闭文件
  19. defer file1.Close()
  20. defer file2.Close()
  21. n, e := io.Copy(file2, file1)
  22. if e != nil {
  23. fmt.Println(e)
  24. }
  25. fmt.Println(n)
  26. }

文件复制方法三 ioutil包

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. )
  7. func main() {
  8. //ioutil.ReadFile读取文件内容
  9. bytestr, err := ioutil.ReadFile("yuan.txt")
  10. if err != nil {
  11. fmt.Println(err)
  12. }
  13. //ioutil.WriteFile将读取到的文件写入newtext.txt文件中
  14. err2 := ioutil.WriteFile("newtext.txt", bytestr, os.ModePerm)
  15. if err2 != nil {
  16. fmt.Println(err2)
  17. }
  18. }

文件服务

有了文件操作的基本知识,结合http框架就可以动手搭建文件服务。

  • 1,将图片或文件通过多媒体表单提交到服务端。
  • 2,图片裁剪压缩处理。
  • 3,将图片路径与图片名称返回。

14 漫画 Go 语言项目实战 文件服务 - 图10 执行命令 bee api fileservice 创建http服务。创建一个Post接口,通过多媒体表单multipart/form-data上传文件。服务端从formDate中获取file文件。

  1. // @Title 保存图片
  2. // @Description 保存图片同时可生成缩略图和大图 [{"mode":"Fill","w":160,"h":160,"name":"simg"},{"mode":"Fit","w":800,"h":10000,"name":"bimg"}]
  3. // @Summary 保存图片
  4. // @Param file formData file true "文件"
  5. // @Param zipstr formData string true "str"
  6. // @router / [post]
  7. func (this *ImageController) Post() {
  8. beego.Info("接收到请求:", time.Now().String())
  9. zipArgs := this.GetString("zipstr", "")
  10. beego.Debug("zipArgs:", zipArgs)
  11. file, header, e := this.GetFile("file")
  12. if e != nil {
  13. this.Data["json"] = map[string]string{
  14. "error": e.Error(),
  15. }
  16. this.ServeJSON()
  17. return
  18. }
  19. defer file.Close()
  20. imageInfo := models.NewImageInfo(file, header, zipArgs)
  21. allow := strings.Index(_ImageAllowType, strings.ToLower(imageInfo.FileExt))
  22. if allow < 0 {
  23. this.Data["json"] = map[string]string{
  24. "error": imageInfo.FileExt + "后缀文件不允许上传!",
  25. }
  26. this.ServeJSON()
  27. return
  28. }
  29. beego.Debug(imageInfo.FileSize)
  30. saveOK := imageInfo.SaveFile()
  31. if !saveOK {
  32. this.Data["json"] = map[string]string{
  33. "error": "图片保存失败",
  34. }
  35. this.ServeJSON()
  36. return
  37. }
  38. result := map[string]models.FileResponse{}
  39. //原始文件
  40. result["raw"] = models.FileResponse{
  41. FileId: imageInfo.FileID,
  42. Uri: imageInfo.Path.UriPath + imageInfo.FileID + imageInfo.FileExt,
  43. }
  44. //返回缩略图和压缩图
  45. if len(imageInfo.ZipImg) > 0 {
  46. for i := 0; i < len(imageInfo.ZipImg); i++ {
  47. num := strconv.Itoa(i)
  48. if imageInfo.ZipImg[i] == nil {
  49. result[num] = models.FileResponse{}
  50. continue
  51. }
  52. if imageInfo.ZipImg[i].Name != "" {
  53. num = imageInfo.ZipImg[i].Name
  54. }
  55. result[num] = models.FileResponse{
  56. FileId: imageInfo.ZipImg[i].FileID,
  57. Uri: imageInfo.Path.UriPath + imageInfo.ZipImg[i].FileID + imageInfo.ZipImg[i].FileExt,
  58. }
  59. beego.Debug(imageInfo.Path.FullPath + "/" + imageInfo.ZipImg[i].FileID + imageInfo.ZipImg[i].FileExt)
  60. }
  61. }
  62. this.Data["json"] = result
  63. this.ServeJSON()
  64. }

NewFileInfo方法创建文件对象

  1. //创建文件信息对象
  2. func NewImageInfo(file multipart.File, fileHeader *multipart.FileHeader, zipArgs string) *ImageInfo {
  3. ii := &ImageInfo{
  4. FileID: NewObjectId().Hex(),
  5. File: file,
  6. FileHeader: fileHeader,
  7. Path: NewPathInfo(_IMAGETYPE),
  8. FileSize: file.(Sizer).Size(),
  9. }
  10. ii.FileExt = filepath.Ext(ii.FileHeader.Filename)
  11. ii.FullName = filepath.Join(ii.Path.FullPath, ii.FileID) + ii.FileExt
  12. ii.zipArgs = zipArgs
  13. return ii
  14. }

SaveFile方法保存文件到本地

  1. //保存文件到本地
  2. func (this *ImageInfo) SaveFile() bool {
  3. beego.Debug("开始写文件")
  4. //创建文件保存目录
  5. this.Path.CreateDirectory()
  6. //保存原始图片
  7. beego.Debug("创建原始图片:", this.FullName)
  8. f, err := os.OpenFile(this.FullName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
  9. if err != nil {
  10. return false
  11. }
  12. defer f.Close()
  13. io.Copy(f, this.File)
  14. beego.Debug("保存完毕.")
  15. //保存压缩图
  16. beego.Debug(this.zipArgs)
  17. if this.zipArgs != "" {
  18. this.ZipImg = NewZipImageArray(this.zipArgs, this.Path, this.FileExt)
  19. beego.Debug("this.ZipImg length:", len(this.ZipImg))
  20. rawImage, e := imaging.Open(this.FullName)
  21. if e != nil {
  22. beego.Debug("原图打开失败:", e.Error())
  23. beego.Debug("rawImageFullName:", this.FullName)
  24. return false
  25. }
  26. if len(this.ZipImg) > 0 {
  27. for i := 0; i < len(this.ZipImg); i++ {
  28. iofile, _ := os.Open(this.FullName)
  29. defer iofile.Close()
  30. result := this.ZipImg[i].SaveFile(rawImage, iofile)
  31. if !result {
  32. this.ZipImg[i] = nil
  33. }
  34. }
  35. }
  36. beego.Debug(this.FullName)
  37. }
  38. return true
  39. }

图片的压缩与裁剪处理

在调用上传图片接口时需要指定压缩参数,zipstr 是一个json数组字符串[{"mode":"Fill", "fillType":"TopRight","w":160,"h":160,"name":"simg"},{"mode":"Fit","w":800,"h":10000,"name":"bimg"}] 需要返回几种图片类型,json数组字符串中就写多少个对象。 14 漫画 Go 语言项目实战 文件服务 - 图11

14 漫画 Go 语言项目实战 文件服务 - 图12

  1. //压缩图片处理
  2. func (this *ZipImage) SaveFile(rawImage image.Image, osfile *os.File) bool {
  3. c, _, err := image.DecodeConfig(osfile)
  4. if err == nil && this.W == 1 && this.H == 1 {
  5. this.W = c.Width
  6. this.H = c.Height
  7. }
  8. beego.Debug("压缩图处理开始.")
  9. var dst *image.NRGBA
  10. switch this.Mode {
  11. case "Resize":
  12. //按固定大小缩放会造成图片变形
  13. dst = imaging.Resize(rawImage, this.W, this.H, imaging.Lanczos)
  14. case "Fit":
  15. //等比例缩放
  16. dst = imaging.Fit(rawImage, this.W, this.H, imaging.Lanczos)
  17. case "Fill":
  18. //按照固定模式裁剪缩放
  19. var anchor imaging.Anchor
  20. switch this.FillType {
  21. case "Center":
  22. //裁剪中间部分
  23. anchor = imaging.Center
  24. case "TopLeft":
  25. //裁剪左上部分
  26. anchor = imaging.TopLeft
  27. case "Top":
  28. //裁上部分
  29. anchor = imaging.Top
  30. case "TopRight":
  31. //裁剪右上部分
  32. anchor = imaging.TopRight
  33. case "Left":
  34. anchor = imaging.Left
  35. case "Right":
  36. anchor = imaging.Right
  37. case "BottomLeft":
  38. anchor = imaging.BottomLeft
  39. case "Bottom":
  40. anchor = imaging.Bottom
  41. case "BottomRight":
  42. anchor = imaging.BottomRight
  43. default:
  44. anchor = imaging.Center
  45. }
  46. dst = imaging.Fill(rawImage, this.W, this.H, anchor, imaging.Lanczos)
  47. }
  48. beego.Debug("缩放完成\n Stride:", dst.Stride, "\n Rect:", dst.Rect, "\n Pix:", len(dst.Pix))
  49. beego.Debug(this.FullName)
  50. e := imaging.Save(dst, this.FullName)
  51. if e != nil {
  52. beego.Debug("压缩图保存失败:", e.Error())
  53. beego.Debug("压缩图文件名:", this.FullName)
  54. return false
  55. }
  56. return true
  57. }

14 漫画 Go 语言项目实战 文件服务 - 图13

14 漫画 Go 语言项目实战 文件服务 - 图14

项目地址

文件服务git地址:github.com/haojiahuogo…