diff --git a/configs/config.yaml b/configs/config.yaml index 8eca8fc..6ed691f 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -9,6 +9,13 @@ App: LogServePath: storage/logs LogFileName: app LogFileExt: .log + UploadSavePath : storage/uploads + UploadServerUrl: http://127.0.0.1:8000/static + UploadImageMaxSize: 5 # MB + UploadImageAllowExts: + - .jpg + - .jpeg + - .png Database: DBType: mysql Username: root diff --git a/internal/routers/api/upload.go b/internal/routers/api/upload.go new file mode 100644 index 0000000..c886b7b --- /dev/null +++ b/internal/routers/api/upload.go @@ -0,0 +1,45 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/lc-1010/OneBlogService/global" + "github.com/lc-1010/OneBlogService/internal/service" + "github.com/lc-1010/OneBlogService/pkg/app" + "github.com/lc-1010/OneBlogService/pkg/convert" + "github.com/lc-1010/OneBlogService/pkg/errcode" + "github.com/lc-1010/OneBlogService/pkg/upload" +) + +type Upload struct{} + +func NewUpload() Upload { + return Upload{} +} + +func (u Upload) UploadFile(c *gin.Context) { + param := service.UploadParams{ + FormName: "file", + FormFileType: "type", + } + response := app.NewResponse(c) + file, fileHeader, err := c.Request.FormFile(param.FormName) + if err != nil { + response.ToErrorResponse(errcode.InvalidParams.WithDetails(err.Error())) + return + } + fileType := convert.StrTo(c.PostForm(param.FormFileType)).MustInt() + if fileHeader == nil || fileType <= 0 { + response.ToErrorResponse(errcode.InvalidParams) + return + } + svc := service.New(c.Request.Context()) + fileInfo, err := svc.UploadFile(upload.FileType(fileType), file, fileHeader) + if err != nil { + global.Logger.Errorf(c, "svc.UploadFile err:%v", err) + response.ToErrorResponse(errcode.ErrorUpdateTagFail.WithDetails(err.Error())) + return + } + response.ToResponse(gin.H{ + "file_access_url": fileInfo.AccessUrl, + }) +} diff --git a/internal/routers/router.go b/internal/routers/router.go index d88a8d5..0e8794f 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -1,6 +1,8 @@ package routers import ( + "net/http" + "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" @@ -10,7 +12,9 @@ import ( validator "github.com/go-playground/validator/v10" _ "github.com/lc-1010/OneBlogService/docs" // 必须引入不然找不到文件 + "github.com/lc-1010/OneBlogService/global" "github.com/lc-1010/OneBlogService/internal/middleware" + "github.com/lc-1010/OneBlogService/internal/routers/api" v1 "github.com/lc-1010/OneBlogService/internal/routers/api/v1" "github.com/lc-1010/OneBlogService/internal/routers/ping" swaggerFiles "github.com/swaggo/files" @@ -40,12 +44,17 @@ func NewRouter() *gin.Engine { tags := v1.NewTag() ping := ping.NewPing() - + //upload image + upload := api.NewUpload() + r.POST("/upload/file", upload.UploadFile) + r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath)) + //ping test p := r.Group("/test") { p.GET("/ping", ping.Pong) } + // api router apiv1 := r.Group("/api/v1") { //tags diff --git a/internal/service/upload.go b/internal/service/upload.go new file mode 100644 index 0000000..debe456 --- /dev/null +++ b/internal/service/upload.go @@ -0,0 +1,49 @@ +package service + +import ( + "errors" + "mime/multipart" + "os" + + "github.com/lc-1010/OneBlogService/global" + "github.com/lc-1010/OneBlogService/pkg/upload" +) + +type FileInfo struct { + Name string + AccessUrl string +} + +type UploadParams struct { + FormName string `json:"file,omitempty"` + FormFileType string `json:"type,omitempty"` +} + +func (svc *Service) UploadFile(fileType upload.FileType, file multipart.File, + fileHeader *multipart.FileHeader) (*FileInfo, error) { + + fileName := upload.GetFileNmae(fileHeader.Filename) + if !upload.CheckAllowExt(fileType, fileName) { + return nil, errors.New("file suffix is not supported") + } + if upload.CheckOverMaxSize(fileType, file) { + return nil, errors.New("exceeded maximux file limit") + } + + uploadSavePath := upload.GetSavePath() + if upload.CheckSavePath(uploadSavePath) { + if err := upload.CreateSavePath(uploadSavePath, os.ModePerm); err != nil { + return nil, errors.New("falied to create save directory") + } + } + if upload.CheckPermission(uploadSavePath) { + return nil, errors.New("insufficient file permissions") + } + dst := uploadSavePath + "/" + fileName + if err := upload.SaveFile(fileHeader, dst); err != nil { + return nil, err + } + accessUrl := global.AppSetting.UploadServerUrl + "/" + fileName + + return &FileInfo{Name: fileName, AccessUrl: accessUrl}, nil +} diff --git a/pkg/errcode/module_code.go b/pkg/errcode/module_code.go index 7203dae..ffce828 100644 --- a/pkg/errcode/module_code.go +++ b/pkg/errcode/module_code.go @@ -7,4 +7,5 @@ var ( ErrorDeleteTagFail = NewError(20010004, "删除标签失败") ErrorCountTagFail = NewError(20010005, "统计标签失败") ErrorCrateTagExists = NewError(20010006, "标签已存在") + ErrorUploadFileFail = NewError(20030001, "上传文件失败") ) diff --git a/pkg/setting/section.go b/pkg/setting/section.go index 09ec64f..919bdab 100644 --- a/pkg/setting/section.go +++ b/pkg/setting/section.go @@ -48,6 +48,11 @@ type DatabaseSettingS struct { ParseTime bool MaxIdleConns int MaxOpenConns int + // upload image + UploadSavePath string + UploadServerUrl string + UploadImageMaxSize int + UpdateImageAllowExts []string } var sections = make(map[string]any) diff --git a/pkg/upload/file.go b/pkg/upload/file.go new file mode 100644 index 0000000..569df9c --- /dev/null +++ b/pkg/upload/file.go @@ -0,0 +1,95 @@ +package upload + +import ( + "io" + "io/ioutil" + "mime/multipart" + "os" + "path" + "strings" + + "github.com/lc-1010/OneBlogService/global" + "github.com/lc-1010/OneBlogService/pkg/util" +) + +type FileType int + +const ( + TypeImage FileType = iota + 1 + TypeExcel + TypeTxt +) + +func GetFileExt(name string) string { + return path.Ext(name) +} + +func GetSavePath() string { + return global.AppSetting.UploadSavePath +} +func CheckSavePath(dst string) bool { + _, err := os.Stat(dst) + return os.IsNotExist(err) +} + +// GetFileNmae return md5string name +func GetFileNmae(name string) string { + ext := GetFileExt(name) + fileName := strings.TrimSuffix(name, ext) + fileName = util.EncodeMD5(fileName) + return fileName + ext +} + +func CheckAllowExt(t FileType, name string) bool { + ext := GetFileExt(name) + ext = strings.ToUpper(ext) + switch t { + case TypeImage: + for _, allowExt := range global.AppSetting.UploadImageAllowExts { + if strings.ToUpper(allowExt) == ext { + return true + } + } + } + return false +} + +func CheckOverMaxSize(t FileType, f multipart.File) bool { + content, _ := ioutil.ReadAll(f) + size := len(content) + switch t { + case TypeImage: + if size >= global.AppSetting.UploadImageMaxSize*1024*1024 { + return true + } + } + return false +} + +func CreateSavePath(dst string, perm os.FileMode) error { + err := os.MkdirAll(dst, perm) + if err != nil { + return err + } + return nil +} + +func CheckPermission(dst string) bool { + _, err := os.Stat(dst) + return os.IsPermission(err) +} + +func SaveFile(file *multipart.FileHeader, dst string) error { + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + _, err = io.Copy(out, src) + return err +} diff --git a/pkg/util/md5.go b/pkg/util/md5.go new file mode 100644 index 0000000..04d800e --- /dev/null +++ b/pkg/util/md5.go @@ -0,0 +1,12 @@ +package util + +import ( + "crypto/md5" + "encoding/hex" +) + +func EncodeMD5(value string) string { + m := md5.New() + m.Write([]byte(value)) + return hex.EncodeToString(m.Sum(nil)) +}