zky_mandate/library/upload_chunk/upload_chunk.go
2023-09-12 22:20:14 +08:00

312 lines
7.1 KiB
Go

package upload_chunk
import (
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"io"
"log"
"mime/multipart"
"os"
"strconv"
"strings"
"sync"
)
type UploadChunk struct{}
// 检查分片
func (u *UploadChunk) CheckChunk(uploadReq UploadReq) (result *CheckRes, err error) {
err = uploadReq.MustIdentifier().MustTotalChunks().MustFilename().MustTotalSize().Check()
if err != nil {
return
}
identifier, totalChunks, filename, totalSize := uploadReq.Identifier, uploadReq.TotalChunks, uploadReq.Filename, uploadReq.TotalSize
dir, prefix, suffix := parseFilePath(filename)
_, _, _ = dir, prefix, suffix
if !strings.Contains(suffix, ".") {
err = errors.New("文件名解析错误")
return
}
result = &CheckRes{}
// 秒传
resultFilePath := u.Tmp() + identifier + suffix
if fileExists(resultFilePath) {
result.SkipUpload = true
result.Url = resultFilePath
result.Filename = filename
result.TotalSize = totalSize
result.RelativePath()
return
}
// 断点续传
for i := 1; i <= totalChunks; i++ {
chunkFilePath := u.chunkPath(identifier, strconv.FormatInt(int64(i), 10))
if fileExists(chunkFilePath) {
result.Uploaded = append(result.Uploaded, i)
}
}
return
}
// 合并文件
func (u *UploadChunk) MergeChunk(uploadReq UploadReq) (result *MergeRes, err error) {
err = uploadReq.MustIdentifier().MustTotalChunks().MustTotalSize().MustFilename().Check()
if err != nil {
return
}
identifier, totalChunks, totalSize, filename := uploadReq.Identifier, uploadReq.TotalChunks, uploadReq.TotalSize, uploadReq.Filename
_, _, suffix := parseFilePath(filename)
if !strings.Contains(suffix, ".") {
return nil, errors.New("文件名解析错误")
}
// 合并后的文件
resultFilePath := u.Tmp() + identifier + suffix
ok, err := createFile(resultFilePath)
if err != nil {
return
}
result = new(MergeRes)
// 文件已存在
if ok {
result.Url = resultFilePath
result.Filename = filename
result.TotalSize = totalSize
result.RelativePath()
return
}
// 检查分片文件是否完整
ok, err = u.checkChunkAll(identifier, totalChunks, totalSize)
if err != nil {
err = fmt.Errorf("分片文件检查错误:%s", err)
return
}
if !ok {
err = errors.New("分片文件不完整")
return
}
var chunkSize int64
var wg sync.WaitGroup
ch := make(chan struct{}, 10)
for i := 1; i <= totalChunks; i++ {
// 分片文件
filePath := u.chunkPath(identifier, fmt.Sprintf("%d", i))
if chunkSize == 0 {
fi, _ := os.Stat(filePath)
if chunkSize = fi.Size(); chunkSize == 0 {
err = errors.New("分片文件大小为0")
return
}
}
ch <- struct{}{}
wg.Add(1)
go func(i int) {
defer func() {
<-ch
wg.Done()
}()
uploadChunk := &UploadChunk{}
err = uploadChunk.mergeFile(filePath, resultFilePath, chunkSize*int64(i-1))
if err != nil {
log.Println(err)
return
}
}(i)
}
wg.Wait()
result.Url = resultFilePath
result.Filename = filename
result.TotalSize = totalSize
result.RelativePath()
return
}
// 上传分片文件
func (u *UploadChunk) Upload(uploadReq UploadReq) (result *UpLoadRes, err error) {
err = uploadReq.MustTotalChunks().MustChunkNumber().MustIdentifier().MustFile().MustTotalSize().MustFilename().Check()
if err != nil {
return
}
totalChunks, chunkNumber, identifier, upFile, filename := uploadReq.TotalChunks, uploadReq.ChunkNumber, uploadReq.Identifier, uploadReq.File, uploadReq.Filename
var fullFilePath string
if totalChunks > 1 {
// 分片文件路径
fullFilePath = u.chunkPath(identifier, strconv.Itoa(chunkNumber))
} else {
_, _, suffix := parseFilePath(filename)
if !strings.Contains(suffix, ".") {
return nil, errors.New("文件名解析错误")
}
fullFilePath = u.Tmp() + identifier + suffix
}
// 创建文件
ok, err := createFile(fullFilePath)
if err != nil {
return
}
if ok {
// 文件已经存在
result = &UpLoadRes{
BaseRes: BaseRes{
Filename: uploadReq.Filename,
TotalSize: uploadReq.TotalSize,
Url: fullFilePath,
},
NeedMerge: totalChunks > 1,
Identifier: uploadReq.Identifier,
TotalChunks: uploadReq.TotalChunks,
}
result.RelativePath()
return
}
// 打开分片文件
file, err := os.OpenFile(fullFilePath, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return
}
defer file.Close()
// 文件偏移量
var fi os.FileInfo
fi, err = os.Stat(fullFilePath)
if err != nil {
return
}
start := fi.Size()
// 写入分片文件
_, err = u.writeFile(upFile, start, file, start)
if err != nil {
return
}
result = &UpLoadRes{
BaseRes: BaseRes{
Filename: uploadReq.Filename,
TotalSize: uploadReq.TotalSize,
Url: fullFilePath,
},
NeedMerge: totalChunks > 1,
Identifier: uploadReq.Identifier,
TotalChunks: uploadReq.TotalChunks,
}
result.RelativePath()
return
}
func (u *UploadChunk) Tmp() string {
return g.Cfg().MustGet(gctx.New(), "server.serverRoot").String() + "/big_file/" // 文件保存目录
}
// 合并文件
func (u *UploadChunk) mergeFile(chunkFile, mergeFile string, offset int64) (err error) {
// 合并后的文件
file, err := os.OpenFile(mergeFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return
}
defer file.Close()
_, err = file.Seek(offset, 0)
if err != nil {
return
}
// 分片文件
chunkFileObj, err := os.Open(chunkFile)
if err != nil {
return
}
defer chunkFileObj.Close()
// 写入数据
data := make([]byte, 1024, 1024)
for {
tal, e := chunkFileObj.Read(data)
if e == io.EOF {
chunkFileObj.Close()
os.Remove(chunkFile)
break
}
_, e = file.Write(data[:tal])
if e != nil {
return e
}
}
return
}
// 检查分片文件是否完整
func (u *UploadChunk) checkChunkAll(identifier string, totalChunks int, totalSize int64) (ok bool, err error) {
if identifier == "" || totalChunks == 0 {
return false, errors.New("checkChunkAll 参数错误")
}
var _totalSize int64
for i := 1; i <= totalChunks; i++ {
filePath := u.chunkPath(identifier, fmt.Sprintf("%d", i))
fi, e := os.Stat(filePath)
if e != nil {
return false, e
}
_totalSize += fi.Size()
}
return _totalSize == totalSize, nil
}
// 获取分片文件路径
func (u *UploadChunk) chunkPath(identifier string, chunkNumber string) string {
return fmt.Sprintf("%s%s", u.Tmp(), getChunkFilename(identifier, chunkNumber))
}
// 检查文件完整性
func (u *UploadChunk) verifyFileSize(fullFilePath string, size int64) (ok bool, err error) {
fi, err := os.Stat(fullFilePath)
if err != nil {
return
}
if fi.Size() == size {
return true, nil
}
return false, nil
}
// 写入文件
func (u *UploadChunk) writeFile(upfile multipart.File, upSeek int64, file *os.File, fSeek int64) (result int, err error) {
// 上传文件大小记录
fileSzie := 0
// 设置上传偏移量
upfile.Seek(upSeek, 0)
// 设置文件偏移量
file.Seek(fSeek, 0)
data := make([]byte, 1024, 1024)
for {
total, e := upfile.Read(data)
if e == io.EOF {
// 文件复制完毕
break
}
l, e := file.Write(data[:total])
if e != nil {
return 0, errors.New("文件上传失败")
}
// 记录上传长度
fileSzie += l
}
return fileSzie, nil
}