You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
8.7 KiB
340 lines
8.7 KiB
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" |
|
"io" |
|
"log" |
|
"net/http" |
|
"net/url" |
|
"strconv" |
|
"strings" |
|
"time" |
|
"tyj_admin/api/v1/common" |
|
"tyj_admin/internal/consts" |
|
"tyj_admin/internal/model/entity" |
|
) |
|
|
|
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.SourceDownload |
|
} |
|
|
|
switch source { |
|
// 上传至本地 |
|
case consts.SourceLocal: |
|
result, err = s.UploadLocal(ctx, file) |
|
// 上传至本地 |
|
case consts.SourceDownload: |
|
result, err = s.UploadLocalDownload(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(consts.DATE_FORMAT) |
|
// 包含静态文件夹的路径 |
|
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 |
|
} |
|
|
|
// 上传本地download |
|
func (s *uploadTmpl) UploadLocalDownload(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.DownloadPath, "/") |
|
//sp := s.getStaticPath(ctx) |
|
//if sp != "" { |
|
// sp = strings.Trim(sp, "/") |
|
//} |
|
//nowData := time.Now().Format("2006-01-02") |
|
// 包含静态文件夹的路径 |
|
//fullDirPath := p |
|
fileName, err := file.Save(p) |
|
if err != nil { |
|
return |
|
} |
|
// 不含静态文件夹的路径 |
|
fullPath := p + "/" + fileName |
|
log.Printf("fullPath: %s, name: %s", fullPath, file.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("上传文件类型未设置,请在后台配置" + key) |
|
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 "" |
|
}
|
|
|