| @@ -13,13 +13,13 @@ const ( | |||
| type FileChunk struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| UUID string `xorm:"uuid UNIQUE"` | |||
| Md5 string `xorm:"UNIQUE"` | |||
| Md5 string `xorm:"INDEX"` | |||
| IsUploaded int `xorm:"DEFAULT 0"` // not uploaded: 0, uploaded: 1 | |||
| UploadID string `xorm:"UNIQUE"`//minio upload id | |||
| TotalChunks int | |||
| Size int64 | |||
| UserID int64 `xorm:"INDEX"` | |||
| CompletedParts []string // chunkNumber+etag eg: ,1-asqwewqe21312312.2-123hjkas | |||
| CompletedParts []string `xorm:"DEFAULT """`// chunkNumber+etag eg: ,1-asqwewqe21312312.2-123hjkas | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| } | |||
| @@ -40,6 +40,22 @@ func getFileChunkByMD5(e Engine, md5 string) (*FileChunk, error) { | |||
| return fileChunk, nil | |||
| } | |||
| // GetFileChunkByMD5 returns fileChunk by given id | |||
| func GetFileChunkByMD5AndUser(md5 string, userID int64) (*FileChunk, error) { | |||
| return getFileChunkByMD5AndUser(x, md5, userID) | |||
| } | |||
| func getFileChunkByMD5AndUser(e Engine, md5 string, userID int64) (*FileChunk, error) { | |||
| fileChunk := new(FileChunk) | |||
| if has, err := e.Where("md5 = ? and user_id = ?", md5, userID).Get(fileChunk); err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, ErrFileChunkNotExist{md5, ""} | |||
| } | |||
| return fileChunk, nil | |||
| } | |||
| // GetAttachmentByID returns attachment by given id | |||
| func GetFileChunkByUUID(uuid string) (*FileChunk, error) { | |||
| return getFileChunkByUUID(x, uuid) | |||
| @@ -1,10 +1,10 @@ | |||
| package storage | |||
| import ( | |||
| "encoding/json" | |||
| "encoding/xml" | |||
| "path" | |||
| "sort" | |||
| "strconv" | |||
| "strings" | |||
| "sync" | |||
| "time" | |||
| @@ -127,7 +127,7 @@ func NewMultiPartUpload(uuid string) (string, error){ | |||
| return core.NewMultipartUpload(bucketName, objectName, miniov6.PutObjectOptions{}) | |||
| } | |||
| func CompleteMultiPartUpload(uuid string, uploadID string, complParts string) (string, error){ | |||
| func CompleteMultiPartUpload(uuid string, uploadID string, complParts []string) (string, error){ | |||
| _, core, err := getClients() | |||
| if err != nil { | |||
| log.Error("getClients failed:", err.Error()) | |||
| @@ -138,19 +138,16 @@ func CompleteMultiPartUpload(uuid string, uploadID string, complParts string) (s | |||
| bucketName := minio.Bucket | |||
| objectName := strings.TrimPrefix(path.Join(minio.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | |||
| //complParts:{"completedParts":[{"partNumber":2,"eTag":'"684929e7fe8b996d495e7b152d34ae37"'}]} | |||
| var parts CompleteParts | |||
| err = json.Unmarshal([]byte(complParts), &parts) | |||
| if err != nil { | |||
| log.Error("json.Unmarshal(%s) failed:(%s)", complParts, err.Error()) | |||
| return "", err | |||
| } | |||
| // Complete multipart upload. | |||
| var complMultipartUpload completeMultipartUpload | |||
| for _,part := range parts.Data { | |||
| for _,part := range complParts { | |||
| partNumber, err := strconv.Atoi(strings.Split(part,"-")[0]) | |||
| if err != nil { | |||
| log.Error(err.Error()) | |||
| return "",err | |||
| } | |||
| complMultipartUpload.Parts =append(complMultipartUpload.Parts, miniov6.CompletePart{ | |||
| PartNumber:part.PartNumber, | |||
| ETag:part.ETag, | |||
| PartNumber: partNumber, | |||
| ETag: strings.Split(part,"-")[1], | |||
| }) | |||
| } | |||
| @@ -336,7 +336,7 @@ func UpdateAttachmentDecompressState(ctx *context.Context) { | |||
| func GetSuccessChunks(ctx *context.Context) { | |||
| fileMD5 := ctx.Query("md5") | |||
| fileChunk, err := models.GetFileChunkByMD5(fileMD5) | |||
| fileChunk, err := models.GetFileChunkByMD5AndUser(fileMD5, ctx.User.ID) | |||
| if err != nil { | |||
| if models.IsErrFileChunkNotExist(err) { | |||
| ctx.JSON(200, map[string]string{ | |||
| @@ -447,7 +447,6 @@ func GetMultipartUploadUrl(ctx *context.Context) { | |||
| func CompleteMultipart(ctx *context.Context) { | |||
| uuid := ctx.Query("uuid") | |||
| uploadID := ctx.Query("uploadID") | |||
| completedParts := ctx.Query("completedParts") | |||
| fileChunk, err := models.GetFileChunkByUUID(uuid) | |||
| if err != nil { | |||
| @@ -459,7 +458,7 @@ func CompleteMultipart(ctx *context.Context) { | |||
| return | |||
| } | |||
| _, err = storage.CompleteMultiPartUpload(uuid, uploadID, completedParts) | |||
| _, err = storage.CompleteMultiPartUpload(uuid, uploadID, fileChunk.CompletedParts) | |||
| if err != nil { | |||
| ctx.Error(500, fmt.Sprintf("CompleteMultiPartUpload failed: %v", err)) | |||
| return | |||
| @@ -28,7 +28,7 @@ | |||
| export default { | |||
| data () { | |||
| return { | |||
| options: { | |||
| /*options: { | |||
| target: 'http://localhost:9000/upload', | |||
| testChunks: false, | |||
| chunkSize: 1024*1024*64, //64MB | |||
| @@ -45,9 +45,9 @@ | |||
| .replace(/\sminutes?/, '分钟') | |||
| .replace(/\sseconds?/, '秒') | |||
| } | |||
| }, | |||
| },*/ | |||
| attrs: { | |||
| accept: '*' | |||
| accept: 'img/*' | |||
| }, | |||
| panelShow: false, //选择文件后,展示上传panel | |||
| collapse: false, | |||
| @@ -83,7 +83,6 @@ | |||
| file.uuid = response.data.uuid; | |||
| file.uploaded = response.data.uploaded; | |||
| file.chunks = response.data.chunks; | |||
| console.log(file.chunks); | |||
| resolve(response); | |||
| }).catch(function (error) { | |||
| console.log(error); | |||
| @@ -194,7 +193,6 @@ | |||
| await uploadMinio(urls[currentChunk], e); | |||
| if (etags[currentChunk] != "") { | |||
| //更新数据库:分片上传结果 | |||
| console.log(etags[currentChunk]); | |||
| await updateChunk(currentChunk); | |||
| } else { | |||
| return; | |||
| @@ -212,7 +210,9 @@ | |||
| axios.post('/attachments/complete_multipart', qs.stringify({ | |||
| uuid: file.uuid, | |||
| uploadID: file.uploadID, | |||
| etag: etags[currentChunk], | |||
| file_name: file.name, | |||
| size: file.size, | |||
| //dataset_id: datasetID, | |||
| _csrf: csrf | |||
| })).then(function (response) { | |||
| resolve(response); | |||
| @@ -233,8 +233,10 @@ | |||
| console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk +1}/${chunks}个分片上传`); | |||
| await loadNext(); | |||
| } else { | |||
| completeUpload(); | |||
| //console.log(dataset_id) | |||
| await completeUpload(); | |||
| console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`); | |||
| //window.location.reload(); | |||
| } | |||
| }; | |||
| } | |||
| @@ -324,22 +326,18 @@ | |||
| await this.newMultiUpload(file); | |||
| if (file.uploadID != "" && file.uuid != "") { | |||
| file.chunks = ""; | |||
| //todo:开始分片上传:分片,获取分片上传地址,上传 | |||
| this.multipartUpload(file); | |||
| } else { | |||
| return; | |||
| } | |||
| } else { | |||
| if (file.uploaded == "1") { //已上传成功 | |||
| //todo:结束上传 | |||
| //秒传 | |||
| console.log("文件已上传完成"); | |||
| //window.location.reload(); | |||
| } else { | |||
| //todo:查询已上传成功的分片,重新上传未成功上传的分片 | |||
| var successChunks = new Array(); | |||
| var successParts = new Array(); | |||
| successParts = file.chunks.split(","); | |||
| for (let i = 0; i < successParts.length; i++) { | |||
| successChunks[i] = successParts[i].split("-")[0].split("\"")[1]; | |||
| } | |||
| //断点续传 | |||
| this.multipartUpload(file); | |||
| } | |||
| } | |||
| }, | |||
| @@ -379,7 +377,7 @@ | |||
| <style> | |||
| .uploader-app { | |||
| width: 880px; | |||
| width: 850px; | |||
| padding: 15px; | |||
| margin: 40px auto 0; | |||
| font-size: 12px; | |||
| @@ -3172,11 +3172,15 @@ function initVueApp() { | |||
| } | |||
| function initVueUploader() { | |||
| //console.log($dataset.data('dataset-id')); | |||
| /* eslint-disable no-new */ | |||
| new Vue({ | |||
| el: '#uploader', | |||
| components: { App }, | |||
| template: '<App/>' | |||
| /*props: { | |||
| dataset_id: $dataset.data('dataset-id') | |||
| }*/ | |||
| }); | |||
| } | |||