209 lines
6.2 KiB
Go

package upload
import (
"context"
"errors"
"fmt"
"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/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
"github.com/tiger1103/gfast/v3/internal/app/common/consts"
"github.com/tiger1103/gfast/v3/internal/app/common/model"
"github.com/tiger1103/gfast/v3/library/libUtils"
"github.com/tiger1103/gfast/v3/library/liberr"
"sort"
"strconv"
"strings"
"time"
)
type Local struct {
}
func (s *Local) Upload(ctx context.Context, file *ghttp.UploadFile) (result *model.UploadResponse, err error) {
if file == nil {
err = errors.New("文件必须!")
return
}
urlPerfix := libUtils.GetDomain(ctx, true)
p := strings.Trim(consts.UploadPath, "/")
sp := s.getStaticPath(ctx)
if sp != "" {
sp = strings.TrimRight(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 = &model.UploadResponse{
Size: file.Size,
Path: fullPath,
FullPath: urlPerfix + "/" + fullPath,
Name: file.Filename,
Type: file.Header.Get("Content-type"),
}
return
}
// 静态文件夹目录
func (s *Local) getStaticPath(ctx context.Context) string {
value, _ := g.Cfg().Get(ctx, "server.serverRoot")
if !value.IsEmpty() {
return value.String()
}
return ""
}
func (s *Local) CheckMultipart(ctx context.Context, req *model.CheckMultipartReq) (res *model.CheckMultipartRes, err error) {
err = g.Try(ctx, func(ctx context.Context) {
res = new(model.CheckMultipartRes)
//计算分片数
for i := 0; i < req.ShardsCount; i++ {
res.WaitUploadIndex = append(res.WaitUploadIndex, i+1)
}
var progress *model.MultipartProgress
progress, err = s.GetMultipartProgress(ctx, req)
liberr.ErrIsNil(ctx, err)
if progress != nil && len(progress.UploadedIndex) > 0 {
res.WaitUploadIndex = libUtils.DiffSlice(progress.UploadedIndex, res.WaitUploadIndex)
}
})
return
}
func (s *Local) UploadPart(ctx context.Context, req *model.UploadPartReq) (res *model.UploadPartRes, err error) {
err = g.Try(ctx, func(ctx context.Context) {
pq := &model.CheckMultipartReq{
UploadType: req.UploadType,
FileName: req.FileName,
Size: req.Size,
Md5: req.Md5,
ShardsCount: req.ShardsCount,
DriverType: req.DriverType,
UserId: req.UserId,
AppId: req.AppId,
}
var progress *model.MultipartProgress
progress, err = s.GetMultipartProgress(ctx, pq)
liberr.ErrIsNil(ctx, err)
if progress == nil {
liberr.ErrIsNil(ctx, errors.New("分片上传信息不存在"))
}
for _, i := range progress.UploadedIndex {
if req.Index == i {
liberr.ErrIsNil(ctx, errors.New("分片已上传"))
}
}
uploadId := s.GenUploadId(req.CheckMultipartReq)
fullPath := s.getPartPath(ctx, uploadId)
//保存分片
req.File.Filename = gconv.String(req.Index) + ".part"
_, err = req.File.Save(fullPath)
liberr.ErrIsNil(ctx, err, "写入分片文件内容失败")
res = new(model.UploadPartRes)
//已上传完毕
if req.ShardsCount == req.Index {
res.Finish = true
//合并文件
sp := s.getStaticPath(ctx)
if sp != "" {
sp = strings.TrimRight(sp, "/")
}
nowData := time.Now().Format("2006-01-02")
// 包含静态文件夹的路径
sp = sp + "/" + strings.Trim(consts.UploadPath, "/") + "/" + nowData
fileName := s.GenNewFileName(req.FileName)
err = s.MergerPath(fullPath, sp+"/"+fileName)
liberr.ErrIsNil(ctx, err, "合并分片失败")
//删除临时文件
gfile.Remove(fullPath)
path := gstr.SubStr(sp, strings.Index(sp, "/"+consts.UploadPath+"/")+1)
res.Attachment = &model.UploadResponse{
Size: req.Size,
Path: path + "/" + fileName,
FullPath: libUtils.GetDomain(ctx, true) + "/" + path + "/" + fileName,
Name: req.FileName,
Type: req.File.FileHeader.Header.Get("Content-type"),
}
}
})
return
}
func (s *Local) GenNewFileName(oldFileName string) string {
ext := gfile.Ext(oldFileName)
fileName := strconv.FormatInt(gtime.TimestampNano(), 36) + grand.S(6)
return strings.ToLower(fileName + ext)
}
func (s *Local) MergerPath(src string, dst string) error {
parts, err := gfile.ScanDirFile(src, "*.part")
if err != nil {
return err
}
sort.Slice(parts, func(i, j int) bool {
fileI := gfile.Basename(parts[i])
fileJ := gfile.Basename(parts[j])
return gconv.Int(gstr.TrimRight(fileI, ".part")) < gconv.Int(gstr.TrimRight(fileJ, ".part"))
})
for _, file := range parts {
if err = gfile.PutBytesAppend(dst, gfile.GetBytes(file)); err != nil {
return err
}
}
return nil
}
// GenUploadId 生成上传ID
func (s *Local) GenUploadId(req *model.CheckMultipartReq) string {
return fmt.Sprintf("%s_%d_%s_%d", req.Md5, req.UserId, req.AppId, req.DriverType)
}
func (s *Local) getPartPath(ctx context.Context, uploadId string) string {
p := strings.Trim(consts.UploadPath, "/")
sp := s.getStaticPath(ctx)
if sp != "" {
sp = strings.TrimRight(sp, "/")
}
// 包含静态文件夹的路径
return sp + "/" + p + "/tmp/" + uploadId
}
// GetMultipartProgress 获取或创建分片上传事件进度
func (s *Local) GetMultipartProgress(ctx context.Context, req *model.CheckMultipartReq) (res *model.MultipartProgress, err error) {
err = g.Try(ctx, func(ctx context.Context) {
uploadId := s.GenUploadId(req)
fullDirPath := s.getPartPath(ctx, uploadId)
res = &model.MultipartProgress{
UploadId: uploadId,
CreatedAt: gtime.Now(),
ShardCount: req.ShardsCount,
UploadedIndex: []int{},
}
//路径不存在说明不存在分片信息
if !gfile.Exists(fullDirPath) {
return
}
//读取路径下的分片
var filePath []string
filePath, err = gfile.ScanDirFile(fullDirPath, "*.part")
liberr.ErrIsNil(ctx, err, "获取分片文件失败")
for _, v := range filePath {
index := gfile.Basename(v)
index = strings.TrimSuffix(index, ".part")
i := gconv.Int(index)
res.UploadedIndex = append(res.UploadedIndex, i)
}
})
return
}