|
|
|
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("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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 上传本地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 ""
|
|
|
|
}
|