Просмотр исходного кода

Merge branch 'master' into os-v3

yxh 3 лет назад
Родитель
Сommit
f469211609

+ 38 - 0
api/v1/common/upload.go

@@ -0,0 +1,38 @@
+package common
+
+import "github.com/gogf/gf/v2/frame/g"
+
+// 单图上传
+type UploadSingleImgReq struct {
+	g.Meta `path:"/singleImg" tags:"上传图片" method:"post" summary:"上传图片"`
+}
+
+// 单文件上传
+type UploadSingleFileReq struct {
+	g.Meta `path:"/singleFile" tags:"上传文件" method:"post" summary:"上传文件"`
+}
+
+type UploadSingleRes struct {
+	g.Meta `mime:"application/json"`
+	UploadResponse
+}
+
+// 多图上传
+type UploadMultipleImgReq struct {
+	g.Meta `path:"/multipleImg" tags:"上传多图片" method:"post" summary:"上传多图片"`
+}
+
+// 多文件上传
+type UploadMultipleFileReq struct {
+	g.Meta `path:"/multipleFile" tags:"上传多文件" method:"post" summary:"上传多文件"`
+}
+
+type UploadMultipleRes []*UploadResponse
+
+type UploadResponse struct {
+	Size     int64  `json:"size"`
+	Path     string `json:"path"`
+	FullPath string `json:"full_path"`
+	Name     string `json:"name"`
+	Type     string `json:"type"`
+}

+ 1 - 0
go.mod

@@ -11,6 +11,7 @@ require (
 	github.com/mojocn/base64Captcha v1.3.5
 	github.com/mssola/user_agent v0.5.3
 	github.com/shirou/gopsutil v3.21.11+incompatible
+	github.com/tencentyun/cos-go-sdk-v5 v0.7.34 // indirect
 	github.com/tiger1103/gfast-cache v0.0.7
 	github.com/tiger1103/gfast-token v0.1.0
 	github.com/tklauser/go-sysconf v0.3.10 // indirect

+ 15 - 0
go.sum

@@ -3,6 +3,7 @@ github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU
 github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
 github.com/casbin/casbin/v2 v2.42.0 h1:EA0aE5PZnFSYY6WulzTScOo4YO6xrGAAZkXRLs8p2ME=
 github.com/casbin/casbin/v2 v2.42.0/go.mod h1:sEL80qBYTbd+BPeL4iyvwYzFT3qwLaESq5aFKVLbLfA=
 github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@@ -10,6 +11,8 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
+github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
 github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
 github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -65,7 +68,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -83,8 +89,12 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
+github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
 github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
+github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
+github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
 github.com/mssola/user_agent v0.5.3 h1:lBRPML9mdFuIZgI2cmlQ+atbpJdLdeVl2IDodjBR578=
 github.com/mssola/user_agent v0.5.3/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -110,10 +120,15 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
 github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4=
+github.com/tencentyun/cos-go-sdk-v5 v0.7.34 h1:xm+Pg+6m486y4eugRI7/E4WasbVmpY1hp9QBSRErgp8=
+github.com/tencentyun/cos-go-sdk-v5 v0.7.34/go.mod h1:4dCEtLHGh8QPxHEkgq+nFaky7yZxQuYwgSJM87icDaw=
 github.com/tiger1103/gfast-cache v0.0.7 h1:YRuSFxFdvNlIsHAndS7XjYRLd4tmXGWhvHt9rK0LVT0=
 github.com/tiger1103/gfast-cache v0.0.7/go.mod h1:s6cRWyr87wz6IJNGKRV6Ahq9hcuVz8h2PAtGrO66JO8=
 github.com/tiger1103/gfast-token v0.1.0 h1:C41f0lZ5nLizHtgfRNvE3nf7hVQ+IO0dCCgg3a9/Oa4=

+ 19 - 0
internal/app/common/consts/upload.go

@@ -0,0 +1,19 @@
+package consts
+
+const (
+	UploadPath        = "upload_file"
+	ImgTypeKey        = "sys.uploadFile.imageType"
+	ImgSizeKey        = "sys.uploadFile.imageSize"
+	FileTypeKey       = "sys.uploadFile.fileType"
+	FileSizeKey       = "sys.uploadFile.fileSize"
+	CheckFileTypeImg  = "img"  // 文件类型(图片)
+	CheckFileTypeFile = "file" // 文件类型(任意)
+)
+
+const (
+	SourceLocal   = iota //  上传到本地
+	SourceTencent        //  上传至腾讯云
+	SourceAli            //  上传到阿里云
+	SourceQiniu          //  上传到七牛云
+	//...................
+)

+ 87 - 0
internal/app/common/controller/upload.go

@@ -0,0 +1,87 @@
+package controller
+
+import (
+	"context"
+	"github.com/gogf/gf/v2/errors/gerror"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/tiger1103/gfast/v3/api/v1/common"
+	"github.com/tiger1103/gfast/v3/internal/app/common/consts"
+	"github.com/tiger1103/gfast/v3/internal/app/common/service"
+)
+
+var Upload = new(uploadController)
+
+type uploadController struct{}
+
+// 上传单图
+func (c *uploadController) SingleImg(ctx context.Context, req *common.UploadSingleImgReq) (res *common.UploadSingleRes, err error) {
+	r := g.RequestFromCtx(ctx)
+	file := r.GetUploadFile("file")
+	if file == nil {
+		err = gerror.New("上传文件必须")
+		return
+	}
+	v, _ := g.Cfg().Get(ctx, "upload.default")
+	response, err := service.Upload().UploadFile(ctx, file, consts.CheckFileTypeImg, v.Int())
+	if err != nil {
+		return
+	}
+	res = &common.UploadSingleRes{
+		UploadResponse: response,
+	}
+	// 上传第三方
+	return
+}
+
+// 上传多图
+func (c *uploadController) MultipleImg(ctx context.Context, req *common.UploadMultipleImgReq) (res *common.UploadMultipleRes, err error) {
+	r := g.RequestFromCtx(ctx)
+	files := r.GetUploadFiles("file")
+	if len(files) == 0 {
+		err = gerror.New("上传文件必须")
+		return
+	}
+	v, _ := g.Cfg().Get(ctx, "upload.default")
+	mf, err := service.Upload().UploadFiles(ctx, files, consts.CheckFileTypeImg, v.Int())
+	if err != nil {
+		return
+	}
+	res = &mf
+	return
+}
+
+// 上传单文件
+func (c *uploadController) SingleFile(ctx context.Context, req *common.UploadSingleFileReq) (res *common.UploadSingleRes, err error) {
+	r := g.RequestFromCtx(ctx)
+	file := r.GetUploadFile("file")
+	if file == nil {
+		err = gerror.New("上传文件必须")
+		return
+	}
+	v, _ := g.Cfg().Get(ctx, "upload.default")
+	response, err := service.Upload().UploadFile(ctx, file, consts.CheckFileTypeFile, v.Int())
+	if err != nil {
+		return
+	}
+	res = &common.UploadSingleRes{
+		UploadResponse: response,
+	}
+	return
+}
+
+// 上传多文件
+func (c *uploadController) MultipleFile(ctx context.Context, req *common.UploadMultipleFileReq) (res *common.UploadMultipleRes, err error) {
+	r := g.RequestFromCtx(ctx)
+	files := r.GetUploadFiles("file")
+	if len(files) == 0 {
+		err = gerror.New("上传文件必须")
+		return
+	}
+	v, _ := g.Cfg().Get(ctx, "upload.default")
+	mf, err := service.Upload().UploadFiles(ctx, files, consts.CheckFileTypeFile, v.Int())
+	if err != nil {
+		return
+	}
+	res = &mf
+	return
+}

+ 7 - 0
internal/app/common/router/router.go

@@ -21,5 +21,12 @@ func BindController(group *ghttp.RouterGroup) {
 				controller.Captcha,
 			)
 		})
+
+		// 文件上传
+		group.Group("/upload", func(group *ghttp.RouterGroup) {
+			group.Bind(
+				controller.Upload,
+			)
+		})
 	})
 }

+ 35 - 0
internal/app/common/service/sys_config.go

@@ -10,6 +10,7 @@ package service
 import (
 	"context"
 	"errors"
+	"github.com/gogf/gf/v2/errors/gerror"
 	"github.com/gogf/gf/v2/frame/g"
 	"github.com/gogf/gf/v2/util/gconv"
 	"github.com/tiger1103/gfast/v3/api/v1/system"
@@ -27,6 +28,8 @@ type IConfig interface {
 	Get(ctx context.Context, id int) (res *system.ConfigGetRes, err error)
 	Edit(ctx context.Context, req *system.ConfigEditReq, userId uint64) (err error)
 	Delete(ctx context.Context, ids []int) (err error)
+	GetConfigByKey(ctx context.Context, key string) (config *entity.SysConfig, err error)
+	GetByKey(ctx context.Context, key string) (config *entity.SysConfig, err error)
 }
 
 type configTmpl struct {
@@ -148,3 +151,35 @@ func (s *configTmpl) Delete(ctx context.Context, ids []int) (err error) {
 	})
 	return
 }
+
+// GetConfigByKey 通过key获取参数(从缓存获取)
+func (s *configTmpl) GetConfigByKey(ctx context.Context, key string) (config *entity.SysConfig, err error) {
+	if key == "" {
+		err = gerror.New("参数key不能为空")
+		return
+	}
+	cache := Cache()
+	cf := cache.Get(ctx, consts.CacheSysConfigTag+key)
+	if cf != nil && !cf.IsEmpty() {
+		err = gconv.Struct(cf, &config)
+		return
+	}
+	config, err = s.GetByKey(ctx, key)
+	if err != nil {
+		return
+	}
+	if config != nil {
+		cache.Set(ctx, consts.CacheSysConfigTag+key, config, 0, consts.CacheSysConfigTag)
+	}
+	return
+}
+
+// GetByKey 通过key获取参数(从数据库获取)
+func (s *configTmpl) GetByKey(ctx context.Context, key string) (config *entity.SysConfig, err error) {
+	err = dao.SysConfig.Ctx(ctx).Where("config_key", key).Scan(&config)
+	if err != nil {
+		g.Log().Error(ctx, err)
+		err = gerror.New("获取配置失败")
+	}
+	return
+}

+ 306 - 0
internal/app/common/service/upload.go

@@ -0,0 +1,306 @@
+package service
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"github.com/gogf/gf/v2/errors/gerror"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
+	"github.com/gogf/gf/v2/os/gfile"
+	"github.com/gogf/gf/v2/os/gtime"
+	"github.com/gogf/gf/v2/text/gregex"
+	"github.com/gogf/gf/v2/text/gstr"
+	"github.com/gogf/gf/v2/util/gconv"
+	"github.com/gogf/gf/v2/util/grand"
+	"github.com/tencentyun/cos-go-sdk-v5"
+	"github.com/tencentyun/cos-go-sdk-v5/debug"
+	"github.com/tiger1103/gfast/v3/api/v1/common"
+	"github.com/tiger1103/gfast/v3/internal/app/common/consts"
+	"github.com/tiger1103/gfast/v3/internal/app/common/model/entity"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type IUpload interface {
+	UploadFile(ctx context.Context, file *ghttp.UploadFile, checkFileType string, source int) (result common.UploadResponse, err error)
+	UploadFiles(ctx context.Context, files []*ghttp.UploadFile, checkFileType string, source int) (result common.UploadMultipleRes, err error)
+}
+
+type uploadTmpl struct{}
+
+func Upload() IUpload {
+	return &uploadTmpl{}
+}
+
+// 上传多文件
+func (s *uploadTmpl) UploadFiles(ctx context.Context, files []*ghttp.UploadFile, checkFileType string, source int) (result common.UploadMultipleRes, err error) {
+	for _, item := range files {
+		f, e := s.UploadFile(ctx, item, checkFileType, source)
+		if e != nil {
+			return
+		}
+		result = append(result, &f)
+	}
+	return
+}
+
+// 上传单文件
+func (s *uploadTmpl) UploadFile(ctx context.Context, file *ghttp.UploadFile, checkFileType string, source int) (result common.UploadResponse, err error) {
+
+	// 检查文件类型
+	err = s.CheckType(ctx, checkFileType, file)
+	if err != nil {
+		return
+	}
+
+	// 检查文件大小
+	err = s.CheckSize(ctx, checkFileType, file)
+	if err != nil {
+		return
+	}
+
+	// 非图片文件只能上传至本地
+	if checkFileType == consts.CheckFileTypeFile {
+		source = consts.SourceLocal
+	}
+
+	switch source {
+	// 上传至本地
+	case consts.SourceLocal:
+		result, err = s.UploadLocal(ctx, file)
+	// 上传至腾讯云
+	case consts.SourceTencent:
+		result, err = s.UploadTencent(ctx, file)
+	default:
+		err = errors.New("source参数错误!")
+	}
+
+	if err != nil {
+		return
+	}
+	return
+}
+
+// 上传至腾讯云
+func (s *uploadTmpl) UploadTencent(ctx context.Context, file *ghttp.UploadFile) (result common.UploadResponse, err error) {
+	v, err := g.Cfg().Get(ctx, "upload.tencentCOS")
+	if err != nil {
+		return
+	}
+	m := v.MapStrVar()
+	var (
+		upPath    = m["upPath"].String()
+		rawUrl    = m["rawUrl"].String()
+		secretID  = m["secretID"].String()
+		secretKey = m["secretKey"].String()
+	)
+	name := gfile.Basename(file.Filename)
+	name = strings.ToLower(strconv.FormatInt(gtime.TimestampNano(), 36) + grand.S(6))
+	name = name + gfile.Ext(file.Filename)
+
+	path := upPath + name
+
+	url, _ := url.Parse(rawUrl)
+	b := &cos.BaseURL{BucketURL: url}
+	client := cos.NewClient(b, &http.Client{
+		Transport: &cos.AuthorizationTransport{
+			SecretID:  secretID,
+			SecretKey: secretKey,
+			Transport: &debug.DebugRequestTransport{
+				RequestHeader:  false,
+				RequestBody:    false,
+				ResponseHeader: false,
+				ResponseBody:   false,
+			},
+		},
+	})
+	opt := &cos.ObjectPutOptions{
+		ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
+			ContentLength: int64(file.Size),
+		},
+	}
+	var f io.ReadCloser
+	f, err = file.Open()
+	if err != nil {
+		return
+	}
+	defer f.Close()
+	_, err = client.Object.Put(context.Background(), path, f, opt)
+	result = common.UploadResponse{
+		Size:     file.Size,
+		Path:     rawUrl + path,
+		FullPath: rawUrl + path,
+		Name:     file.Filename,
+		Type:     file.Header.Get("Content-type"),
+	}
+	return
+}
+
+// 上传本地
+func (s *uploadTmpl) UploadLocal(ctx context.Context, file *ghttp.UploadFile) (result common.UploadResponse, err error) {
+	if file == nil {
+		err = errors.New("文件必须!")
+		return
+	}
+	r := g.RequestFromCtx(ctx)
+	urlPerfix := fmt.Sprintf("http://%s/", r.Host)
+	p := strings.Trim(consts.UploadPath, "/")
+	sp := s.getStaticPath(ctx)
+	if sp != "" {
+		sp = strings.Trim(sp, "/")
+	}
+	nowData := time.Now().Format("2006-01-02")
+	// 包含静态文件夹的路径
+	fullDirPath := sp + "/" + p + "/" + nowData
+	fileName, err := file.Save(fullDirPath, true)
+	if err != nil {
+		return
+	}
+	// 不含静态文件夹的路径
+	fullPath := p + "/" + nowData + "/" + fileName
+
+	result = common.UploadResponse{
+		Size:     file.Size,
+		Path:     fullPath,
+		FullPath: urlPerfix + fullPath,
+		Name:     file.Filename,
+		Type:     file.Header.Get("Content-type"),
+	}
+	return
+}
+
+// 检查上传文件大小
+func (s *uploadTmpl) CheckSize(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) {
+
+	var (
+		configSize *entity.SysConfig
+	)
+
+	if checkFileType == consts.CheckFileTypeFile {
+
+		//获取上传大小配置
+		configSize, err = s.getUpConfig(ctx, consts.FileSizeKey)
+		if err != nil {
+			return
+		}
+	} else if checkFileType == consts.CheckFileTypeImg {
+
+		//获取上传大小配置
+		configSize, err = s.getUpConfig(ctx, consts.ImgSizeKey)
+		if err != nil {
+			return
+		}
+	} else {
+		return errors.New(fmt.Sprintf("文件检查类型错误:%s|%s", consts.CheckFileTypeFile, consts.CheckFileTypeImg))
+	}
+
+	var rightSize bool
+	rightSize, err = s.checkSize(configSize.ConfigValue, file.Size)
+	if err != nil {
+		return
+	}
+	if !rightSize {
+		err = gerror.New("上传文件超过最大尺寸:" + configSize.ConfigValue)
+		return
+	}
+	return
+}
+
+// 检查上传文件类型
+func (s *uploadTmpl) CheckType(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) {
+
+	var (
+		configType *entity.SysConfig
+	)
+
+	if checkFileType == consts.CheckFileTypeFile {
+		//获取上传类型配置
+		configType, err = s.getUpConfig(ctx, consts.FileTypeKey)
+		if err != nil {
+			return
+		}
+
+	} else if checkFileType == consts.CheckFileTypeImg {
+		//获取上传类型配置
+		configType, err = s.getUpConfig(ctx, consts.ImgTypeKey)
+		if err != nil {
+			return
+		}
+	} else {
+		return errors.New(fmt.Sprintf("文件检查类型错误:%s|%s", consts.CheckFileTypeFile, consts.CheckFileTypeImg))
+	}
+
+	rightType := s.checkFileType(file.Filename, configType.ConfigValue)
+	if !rightType {
+		err = gerror.New("上传文件类型错误,只能包含后缀为:" + configType.ConfigValue + "的文件。")
+		return
+	}
+	return
+}
+
+//获取上传配置
+func (s *uploadTmpl) getUpConfig(ctx context.Context, key string) (config *entity.SysConfig, err error) {
+	config, err = Config().GetConfigByKey(ctx, key)
+	if err != nil {
+		return
+	}
+	if config == nil {
+		err = gerror.New("上传文件类型未设置,请在后台配置")
+		return
+	}
+	return
+}
+
+//判断上传文件类型是否合法
+func (s *uploadTmpl) checkFileType(fileName, typeString string) bool {
+	suffix := gstr.SubStrRune(fileName, gstr.PosRRune(fileName, ".")+1, gstr.LenRune(fileName)-1)
+	imageType := gstr.Split(typeString, ",")
+	rightType := false
+	for _, v := range imageType {
+		if gstr.Equal(suffix, v) {
+			rightType = true
+			break
+		}
+	}
+	return rightType
+}
+
+//检查文件大小是否合法
+func (s *uploadTmpl) checkSize(configSize string, fileSize int64) (bool, error) {
+	match, err := gregex.MatchString(`^([0-9]+)(?i:([a-z]*))$`, configSize)
+	if err != nil {
+		return false, err
+	}
+	if len(match) == 0 {
+		err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB)")
+		return false, err
+	}
+	var cfSize int64
+	switch gstr.ToUpper(match[2]) {
+	case "MB", "M":
+		cfSize = gconv.Int64(match[1]) * 1024 * 1024
+	case "KB", "K":
+		cfSize = gconv.Int64(match[1]) * 1024
+	case "":
+		cfSize = gconv.Int64(match[1])
+	}
+	if cfSize == 0 {
+		err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB),最大单位为MB")
+		return false, err
+	}
+	return cfSize >= fileSize, nil
+}
+
+// 静态文件夹目录
+func (s *uploadTmpl) getStaticPath(ctx context.Context) string {
+	value, _ := g.Cfg().Get(ctx, "server.serverRoot")
+	if !value.IsEmpty() {
+		return value.String()
+	}
+	return ""
+}

+ 47 - 0
library/libUtils/utils.go

@@ -17,6 +17,7 @@ import (
 	"github.com/gogf/gf/v2/frame/g"
 	"github.com/gogf/gf/v2/net/ghttp"
 	"net"
+	"net/http"
 	"os"
 	"path"
 	"strings"
@@ -124,3 +125,49 @@ func ParseFilePath(pathStr string) (fileName string, fileType string) {
 	fileName = strings.TrimSuffix(fileNameWithSuffix, fileType)
 	return
 }
+
+// IsNotExistMkDir 检查文件夹是否存在
+// 如果不存在则新建文件夹
+func IsNotExistMkDir(src string) error {
+	if exist := !FileIsExisted(src); exist == false {
+		if err := MkDir(src); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// MkDir 新建文件夹
+func MkDir(src string) error {
+	err := os.MkdirAll(src, os.ModePerm)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+//获取文件后缀
+func GetExt(fileName string) string {
+	return path.Ext(fileName)
+}
+
+// GetType 获取文件类型
+func GetType(p string) (result string, err error) {
+	file, err := os.Open(p)
+	if err != nil {
+		g.Log().Error(context.TODO(), err)
+		return
+	}
+	buff := make([]byte, 512)
+
+	_, err = file.Read(buff)
+
+	if err != nil {
+		g.Log().Error(context.TODO(), err)
+		return
+	}
+	filetype := http.DetectContentType(buff)
+	return filetype, nil
+}

+ 8 - 1
manifest/config/config.yaml.bak

@@ -83,4 +83,11 @@ gfcli:
         noModelComment:  true
         path: "./internal/app/system"
 
-
+# 上传配置
+upload:
+  default: 0   # 默认上传至本地
+  tencentCOS:
+    upPath : "/gfast/"
+    rawUrl :    "https://yxh-1301841944.cos.ap-chongqing.myqcloud.com"
+    secretID :  "AKIDsKckd83HTh0795wlKcyreyCfvDHYZmbk"
+    secretKey : "2tc6Lj6LODppZ3aWCeqElBLcqzwz06zg"