Skip to content

Commit

Permalink
<feat>: Add image upload service
Browse files Browse the repository at this point in the history
- Add new Upload service for image upload
- Add configuration in `config.yaml`
- Generate access URL for articles

Issue #6
  • Loading branch information
lc-1010 committed Jun 28, 2023
1 parent 3611f2b commit 236e7de
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 1 deletion.
7 changes: 7 additions & 0 deletions configs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions internal/routers/api/upload.go
Original file line number Diff line number Diff line change
@@ -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,
})
}
11 changes: 10 additions & 1 deletion internal/routers/router.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions internal/service/upload.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions pkg/errcode/module_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ var (
ErrorDeleteTagFail = NewError(20010004, "删除标签失败")
ErrorCountTagFail = NewError(20010005, "统计标签失败")
ErrorCrateTagExists = NewError(20010006, "标签已存在")
ErrorUploadFileFail = NewError(20030001, "上传文件失败")
)
5 changes: 5 additions & 0 deletions pkg/setting/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
95 changes: 95 additions & 0 deletions pkg/upload/file.go
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions pkg/util/md5.go
Original file line number Diff line number Diff line change
@@ -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))
}

0 comments on commit 236e7de

Please sign in to comment.