最近阴差阳错的三个项目都涉及到了前后端文件传输,特别是对图片文件的处理这一技术点,结合最近上传博客经常卡壳,速度也很慢这一现状,干脆用 go 给自己的博客搭一个图床吧。
一些约定 链接到标题
系统: Ubuntu 20.04
图片存储格式: webp
图片文件名称生成格式: UUID
图片支持上传的格式: jpeg、png
基础框架 链接到标题
我选择 gin 作为前后端对接的框架,redis 简单储存一些需要用到的数据,搭好一个简单的登录与鉴权框架即可。
业务实现 链接到标题
图片接收与存储 链接到标题
前端会将图片以 form-data 的形式进行传输,后端只需调用 gin 框架对文件的接受与保存方法即可实现。
func UploadImg(c *gin.Context) {
form, _ := c.MultipartForm()
img := form.File["img"][0]
imgName := img.Filename
_ = c.SaveUploadedFile(img, "./tmp/"+imgName)
}
将 jpeg 格式的图片转换为 webp 格式存储 链接到标题
这一步因为 go 的标准库内只有 webp 格式的 decode 方法,没有我想要的 encode 方法,只能选择第三方库 go-webp 来实现这一功能。
func UploadImg(c *gin.Context) {
// 上文的图片存储代码,略
file, _ := os.Open("./tmp/" + imgName)
defer file.Close()
imgNew, _ := jpeg.Decode(file)
fileName := strings.TrimSuffix(img.Filename, path.Ext(path.Base(img.Filename))) + ".webp"
output, _ := os.Create("./img/" + fileName)
defer output.Close()
options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, 75)
if err != nil {
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
err = webp.Encode(output, imgNew, options)
if err != nil {
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
utils.JsonSuccessResponse(c, config.GetWebpUrlKey()+fileName)
}
处理 png 格式的问题 链接到标题
因为我没找到 png 直接转换为 webp 的库,因此选择先理由标准库把 png 转换为 jpeg,再利用我刚刚实现的 jpeg 转 webp 的代码处理。
func UploadImg(c *gin.Context) {
// 上文的图片存储与打开文件代码,略
buffer := make([]byte, 512)
_, _ = file.Read(buffer)
contentType := http.DetectContentType(buffer)
file.Close()
file, _ = os.Open("./tmp/" + imgName)
defer file.Close()
if contentType == "image/png" {
newTypeName := "./tmp/" + strings.TrimSuffix(img.Filename, path.Ext(path.Base(img.Filename))) + ".png"
_ = os.Rename("./tmp/"+imgName, newTypeName)
imgNew, err := png.Decode(file)
if err != nil {
fmt.Println(err)
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
out, err := os.Create("./tmp/" + strings.TrimSuffix(img.Filename, path.Ext(path.Base(img.Filename))) + ".jpg")
if err != nil {
fmt.Println(err)
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
defer out.Close()
err = jpeg.Encode(out, imgNew, &jpeg.Options{Quality: 95})
if err != nil {
fmt.Println(err)
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
_ = os.Remove(newTypeName)
imgName = strings.TrimSuffix(img.Filename, path.Ext(path.Base(img.Filename))) + ".jpg"
file.Close()
file, _ = os.Open("./tmp/" + imgName)
}
// 上文 jpeg 转 webp 格式并存储的代码,略
}
其他处理 链接到标题
转换后的图片如果仍使用原文件名可能会遇到冲突的问题,因此我选择生成一个 UUID 来命名新文件,确保独一,且在转换存储成功后加了一条语句删除了之前存储的临时文件。
最终代码如下:
func UploadImg(c *gin.Context) {
form, _ := c.MultipartForm()
img := form.File["img"][0]
imgName := img.Filename
_ = c.SaveUploadedFile(img, "./tmp/"+imgName)
file, _ := os.Open("./tmp/" + imgName)
buffer := make([]byte, 512)
_, _ = file.Read(buffer)
contentType := http.DetectContentType(buffer)
file.Close()
file, _ = os.Open("./tmp/" + imgName)
defer file.Close()
if contentType == "image/png" {
newTypeName := "./tmp/" + strings.TrimSuffix(img.Filename, path.Ext(path.Base(img.Filename))) + ".png"
_ = os.Rename("./tmp/"+imgName, newTypeName)
imgNew, err := png.Decode(file)
if err != nil {
fmt.Println(err)
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
out, err := os.Create("./tmp/" + strings.TrimSuffix(img.Filename, path.Ext(path.Base(img.Filename))) + ".jpg")
if err != nil {
fmt.Println(err)
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
defer out.Close()
err = jpeg.Encode(out, imgNew, &jpeg.Options{Quality: 95})
if err != nil {
fmt.Println(err)
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
_ = os.Remove(newTypeName)
imgName = strings.TrimSuffix(img.Filename, path.Ext(path.Base(img.Filename))) + ".jpg"
file.Close()
file, _ = os.Open("./tmp/" + imgName)
}
imgNew, err := jpeg.Decode(file)
if err != nil {
fmt.Println(err)
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
fileName := uuid.NewString() + ".webp"
output, _ := os.Create("./img/" + fileName)
defer output.Close()
options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, 75)
if err != nil {
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
err = webp.Encode(output, imgNew, options)
if err != nil {
_ = c.AbortWithError(200, apiException.ImgTypeError)
return
}
_ = os.Remove("./tmp/" + imgName)
utils.JsonSuccessResponse(c, config.GetWebpUrlKey()+fileName)
}