修改文件上传逻辑
This commit is contained in:
@@ -1,13 +1,20 @@
|
|||||||
// Code scaffolded by goctl. Safe to edit.
|
// fileupload/upload_file_logic.go
|
||||||
// goctl 1.9.2
|
|
||||||
|
|
||||||
package fileupload
|
package fileupload
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/JACKYMYPERSON/hldrCenter/config"
|
"github.com/JACKYMYPERSON/hldrCenter/config"
|
||||||
"github.com/JACKYMYPERSON/hldrCenter/internal/file/internal/types"
|
"github.com/JACKYMYPERSON/hldrCenter/internal/file/internal/types"
|
||||||
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
@@ -30,7 +37,203 @@ func NewUploadFileLogic(ctx context.Context, cfg *config.Config, c *gin.Context)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *UploadFileLogic) UploadFile() (resp *types.UploadFileResp, err error) {
|
func (l *UploadFileLogic) UploadFile() (resp *types.UploadFileResp, err error) {
|
||||||
// todo: add your logic here and delete this line
|
// 1. 获取上传文件
|
||||||
|
fileHeader, err := l.c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
l.c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "获取文件失败,请重新上传",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 打开文件
|
||||||
|
file, err := fileHeader.Open()
|
||||||
|
if err != nil {
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "打开文件失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// 3. 检查文件大小
|
||||||
|
if fileHeader.Size > l.cfg.FileUpload.MaxFileSize {
|
||||||
|
l.c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": fmt.Sprintf("文件大小不能超过 %dMB", l.cfg.FileUpload.MaxFileSize>>20),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 检查文件类型
|
||||||
|
fileType := fileHeader.Header.Get("Content-Type")
|
||||||
|
allowTypes := strings.Split(l.cfg.FileUpload.AllowFileTypes, ",")
|
||||||
|
isAllowed := false
|
||||||
|
for _, typ := range allowTypes {
|
||||||
|
if strings.TrimSpace(typ) == fileType {
|
||||||
|
isAllowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isAllowed {
|
||||||
|
l.c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": fmt.Sprintf("不支持的文件类型,仅允许:%s", l.cfg.FileUpload.AllowFileTypes),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 初始化OSS客户端
|
||||||
|
client, err := oss.New(
|
||||||
|
l.cfg.OSS.Endpoint,
|
||||||
|
l.cfg.OSS.AccessKeyID,
|
||||||
|
l.cfg.OSS.AccessKeySecret,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "初始化OSS客户端失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 获取Bucket实例
|
||||||
|
bucket, err := client.Bucket(l.cfg.OSS.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "获取Bucket失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 生成文件存储路径
|
||||||
|
timestamp := time.Now().Format("20060102150405")
|
||||||
|
fileExt := filepath.Ext(fileHeader.Filename)
|
||||||
|
filename := fmt.Sprintf("%s_%s%s", timestamp, strings.TrimSuffix(fileHeader.Filename, fileExt), fileExt)
|
||||||
|
|
||||||
|
var fileDir string
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(fileType, "video/"):
|
||||||
|
fileDir = "videos/"
|
||||||
|
case fileType == "application/pdf":
|
||||||
|
fileDir = "pdfs/"
|
||||||
|
case strings.HasPrefix(fileType, "image/"):
|
||||||
|
fileDir = "images/"
|
||||||
|
case fileType == "text/plain":
|
||||||
|
fileDir = "txts/"
|
||||||
|
case fileType == "application/json":
|
||||||
|
fileDir = "jsons/"
|
||||||
|
default:
|
||||||
|
fileDir = "other/"
|
||||||
|
}
|
||||||
|
objectKey := l.cfg.OSS.ObjectPrefix + fileDir + filename
|
||||||
|
|
||||||
|
// 8. 分片上传(100KB/片)
|
||||||
|
const partSize = 100 * 1024 // 100KB
|
||||||
|
totalSize := fileHeader.Size
|
||||||
|
partCount := (totalSize + partSize - 1) / partSize
|
||||||
|
var partETags []oss.PartETag
|
||||||
|
|
||||||
|
// 重置文件指针
|
||||||
|
if _, err = file.Seek(0, 0); err != nil {
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "重置文件指针失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.1 初始化分片上传
|
||||||
|
imur, err := bucket.InitiateMultipartUpload(objectKey)
|
||||||
|
if err != nil {
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "初始化分片上传失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.2 上传每个分片
|
||||||
|
for i := 1; i <= int(partCount); i++ {
|
||||||
|
partStart := int64((i - 1) * partSize)
|
||||||
|
partEnd := partStart + partSize
|
||||||
|
if partEnd > totalSize {
|
||||||
|
partEnd = totalSize
|
||||||
|
}
|
||||||
|
partLen := partEnd - partStart
|
||||||
|
|
||||||
|
// 读取分片数据
|
||||||
|
data := make([]byte, partLen)
|
||||||
|
if _, err = file.Seek(partStart, 0); err != nil {
|
||||||
|
_ = bucket.AbortMultipartUpload(imur)
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": fmt.Sprintf("定位分片%d失败", i),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = io.ReadFull(file, data); err != nil && err != io.EOF {
|
||||||
|
_ = bucket.AbortMultipartUpload(imur)
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": fmt.Sprintf("读取分片%d失败", i),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传分片(旧版:传入 imur)
|
||||||
|
partResult, err := bucket.UploadPart(imur, i, bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
_ = bucket.AbortMultipartUpload(imur)
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": fmt.Sprintf("上传分片%d失败", i),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
partETags = append(partETags, oss.PartETag{
|
||||||
|
PartNumber: i,
|
||||||
|
ETag: partResult.ETag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8.3 按分片号排序(必须)
|
||||||
|
sort.Slice(partETags, func(i, j int) bool {
|
||||||
|
return partETags[i].PartNumber < partETags[j].PartNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
// 8.4 完成分片上传(旧版:传入 imur)
|
||||||
|
_, err = bucket.CompleteMultipartUpload(imur, partETags)
|
||||||
|
if err != nil {
|
||||||
|
_ = bucket.AbortMultipartUpload(imur)
|
||||||
|
l.c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"message": "合并分片失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 返回成功结果
|
||||||
|
fileURL := fmt.Sprintf("https://%s.%s/%s",
|
||||||
|
l.cfg.OSS.BucketName,
|
||||||
|
strings.TrimPrefix(l.cfg.OSS.Endpoint, "https://"),
|
||||||
|
objectKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
l.c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"message": "文件上传成功",
|
||||||
|
"data": gin.H{
|
||||||
|
"url": fileURL,
|
||||||
|
"filename": fileHeader.Filename,
|
||||||
|
"size": fileHeader.Size,
|
||||||
|
"type": fileType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user