Gin 项目优化相册图片的上传和删除

本篇是一个优化篇, 针对 image.go 中的 crud , 目的还是适量拆分一些代码逻辑, 使其更清晰, 同时这个优化也是能力提升的最佳方式.

封装图片文件处理

封装为单独一个文件: api/handlers/image/dealUploadFile.go

package image

import (
	"errors"
	"io"
	"log"
	"path/filepath"
	"strings"

	"github.com/gin-gonic/gin"
	"youge.com/pkg/utils"
)

// 定义允许的图片后缀
var allowedExtensions = map[string]bool{
	".jpg":  true,
	".jpeg": true,
	".png":  true,
	".gft":  true,
	".bmp":  true,
	".tiff": true,
	".tif":  true,
	".svg":  true,
	".webp": true,
	".heif": true,
	".heic": true,
}

// 检查图片拓展名是否合法
func isOkImageExtension(filename string) bool {
	ext := strings.ToLower(filepath.Ext(filename))
	return allowedExtensions[ext]
}

// 准备图片上传: 包括文件大小检查, 拓展名, 文件处理等
func CheckImageForUpload(c *gin.Context, maxSize int64) (io.ReadCloser, string, error) {
	// 先获取文件的一些元信息
	file, err := c.FormFile("file")
	if err != nil {
		return nil, "", errors.New("获取文件失败")
	}

	// 验证文件扩展名
	if !isOkImageExtension(file.Filename) {
		return nil, "", errors.New("不支持的图片格式")
	}

	// 验证照片大小
	if file.Size >= maxSize {
		return nil, "", errors.New("图片太大了哈")
	}

	// 分离出文件名和后缀, cjj.png => cjj; png
	fileName := filepath.Base(file.Filename)
	// index := strings.LastIndex(fileName, ".")
	// if index == -1 {
	// 	return nil, "", errors.New("无法识别文件拓展名")
	// }
	// fileNameShort := fileName[:index]
	// extensition := fileName[index+1:]

	ext := filepath.Ext(fileName) // .png; .jpeg ....
	// 用 uuid 生成新的文件名 => abcde.png
	ossFileName := utils.GetUUID() + ext

	// 读取文件流
	reader, err := file.Open()
	if err != nil {
		return nil, "", errors.New("图片数据读取错误")
	}

	log.Println(ossFileName)

	return reader, ossFileName, nil

}

这样在调用的时候就变成了:

// 图片上传校验, 调用封装的函数
	reader, ossFileName, err := CheckImageForUpload(c, 10*1024*1024)
	if err != nil {
		utils.Error(c, 500, "", err)
		return
	}

	defer reader.Close()

	// 上传到 OSS
	fileOssPath, err2 := utils.UploadImageFromReader(reader, ossFileName)
	if err2 != nil {
		utils.BadRequest(c, "上传 oss 失败")
		return
	}
	
	// ...

配置信息封装在工具函数中

更常用的方式是用类似 .env 或者 yaml 等文件弄成环境变量来读取. 我不想搞, 就直接放工具函数得了.

pkg/utils/imageOss.go

// ....
// 统一前缀, 这里是 "shop-admin/" + "xxx"
func GetOssFilePath(ossFileName string) string {
	return projectDir + "/" + ossFileName
}

这样接口中就由原来的 硬编码 改成了函数调用了.

	// "shop-admin/" + "adfsfds.png"
	ossFileName = utils.GetOssFilePath(ossFileName)

同理,

pkg/utils/tools.go

// 随机生成 uuid
func GetUUID() string {
	newUuid, _ := uuid.NewRandom()
	return newUuid.String()
}

数据库封装事务

只是尝试用 ai 搞了一下, 但始终无法完全隐藏细节.

internal/db/db.go

// 事务封装, 自动回滚, 支持返回值 T 泛型
// 事务封装,支持返回值 T
func WithTransaction[T any](fn func(*sqlx.Tx) (T, error)) (T, error) {
	var zero T

	tx, err := db.Beginx()
	if err != nil {
		return zero, err
	}

	defer func() {
		if p := recover(); p != nil {
			tx.Rollback()
			panic(p)
		}
	}()

	result, err := fn(tx)
	if err != nil {
		tx.Rollback()
		return zero, err
	}

	if err := tx.Commit(); err != nil {
		return zero, err
	}

	return result, nil
}

然后在删除图片的接口 api/handlers/image/image.go => DeleteImage 则变成了:

// 接口: 删除某张图片

func DeleteImage(c *gin.Context) {
	var req struct {
		Id int `uri:"id" validate:"required,min=1"`
	}
	if err := utils.BindAndValidate(c, &req); err != nil {
		utils.BadRequest(c, "获取图片失败")
		return
	}

	// 使用新事务函数返回 oss 路径
	imageOssPath, err := db.WithTransaction[string](func(tx *sqlx.Tx) (string, error) {
		var ossPath string
		sql_01 := `select path from image where id = ?;`
		if err := tx.Get(&ossPath, sql_01, req.Id); err != nil {
			// 没有查询到也算错哈
			utils.BadRequest(c, "获取图片失败")
			return "", err
		}

		// 先删表关系
		sql_02 := "delete from image where id = ?"
		if _, err := db.Exec(sql_02, req.Id); err != nil {
			utils.BadRequest(c, "删除图片失败")
			return "", err
		}

		// 上面事务一致了, 再返回图片路径
		return ossPath, nil
	})

	// 统一处理事务的报错
	if err != nil {
		utils.BadRequest(c, "删除失败")
		return
	}

	// 事务提交后, 再删除远程的 oss
	if err := utils.DeleteImageFromOss(imageOssPath); err != nil {
		utils.BadRequest(c, "删除图片 oss 失败")
		return
	}

	utils.Success(c, gin.H{
		"msg": "删除成功",
	})

}

但似乎在简单场景下, 不考虑多并发情况下, 也不需要用到事务的呀, 这里也是当做尝试一下而已.

基本优化就这些了, 更多优化后续持续展开, 比如这个查询分页的改进 (前端), 数据库查询单独分函数等.

posted @ 2025-05-28 23:01  致于数据科学家的小陈  阅读(60)  评论(0)    收藏  举报