flipped-aurora/gin-vue-admin · warning

上传不在可合并状态(可能已在合并或已完成)

Error message

上传不在可合并状态(可能已在合并或已完成)

What it means

Complete() performs an atomic compare-and-set UPDATE moving the MediaUpload row from status 'uploading' to 'merging'. If RowsAffected is 0, another request already won the merge race or the row is in a non-mergeable state, so it returns '上传不在可合并状态(可能已在合并或已完成)'.

Source

Thrown at server/service/media/media_upload.go:118

	var up media.MediaUpload
	if err := global.GVA_DB.WithContext(ctx).First(&up, uploadID).Error; err != nil {
		return m, errors.New("上传会话不存在")
	}
	if up.UserID != userID {
		return m, errors.New("无权操作该上传")
	}
	if err := upload.ValidateFileExtension(up.FileName); err != nil {
		return m, err
	}
	// 原子抢占:仅 uploading -> merging 的赢家继续
	res := global.GVA_DB.WithContext(ctx).Model(&media.MediaUpload{}).
		Where("id = ? AND status = ?", uploadID, media.UploadStatusUploading).
		Update("status", media.UploadStatusMerging)
	if res.Error != nil {
		return m, res.Error
	}
	if res.RowsAffected == 0 {
		return m, errors.New("上传不在可合并状态(可能已在合并或已完成)")
	}

	fail := func(e error) (media.FileUploadAndDownload, error) {
		global.GVA_DB.WithContext(ctx).Model(&media.MediaUpload{}).Where("id = ?", uploadID).Update("status", media.UploadStatusFailed)
		return m, e
	}

	// 校验分片齐全
	var cnt int64
	global.GVA_DB.WithContext(ctx).Model(&media.MediaUploadChunk{}).Where("upload_id = ?", uploadID).Count(&cnt)
	if int(cnt) != up.ChunkTotal {
		return fail(fmt.Errorf("分片不全: %d/%d", cnt, up.ChunkTotal))
	}

	// 合并到临时成品
	merged := filepath.Join(upload.ChunkDir(uploadID), "merged.bin")
	gotMd5, err := upload.MergeChunks(uploadID, up.ChunkTotal, merged)
	if err != nil {

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Treat this as expected in concurrency: only call Complete once per upload session and disable the UI button after the first call.
  2. Query the current status of the upload row; if it is completed, fetch the resulting file record instead of merging again.
  3. If a row is stuck in 'merging' from a crashed process, reset its status back to 'uploading' (manually or via admin endpoint) and retry.
  4. If stuck in 'failed', restart the upload flow with a new session.
  5. Add idempotency on the client: remember successful completion keyed by uploadID.

Example fix

// before: completing again after timeout regardless of state
await api.complete({ uploadId }) // retried on timeout -> conflict
// after: complete once, and on conflict poll the final state
try {
  await api.complete({ uploadId })
} catch (e) {
  const st = await api.getUploadStatus(uploadId)
  if (st.status === 'completed') return st.file // already merged by another call
  throw e
}
Defensive patterns

Strategy: retry

Validate before calling

// check current state before completing
const st = await api.getUploadStatus(uploadId)
if (st.status !== 'uploading') throw new Error('upload not in mergeable state: ' + st.status)

Try / catch

try {
  await svc.Complete(ctx, userID, uploadID)
} catch (err) {
  if (strings.Contains(err.Error(), "不在可合并状态")) {
    // not an error in concurrent flows: re-read status;
    // if completed, treat as success; if merging, back off and poll
  } else {
    return err
  }
}

Prevention

When it happens

Trigger: Two concurrent Complete calls for the same uploadID (only one UPDATE wins); the upload already finished (status completed); a previous failed merge left status failed/merging; calling Complete after Cancel; calling Complete before all chunk uploads set status to uploading.

Common situations: Frontend double-clicks or retries Complete while the first request is still merging; a load balancer duplicates the request; a stuck 'merging' row left behind by a crashed merge blocks retries; completing an upload that already succeeded earlier.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31). Data as JSON: /api/errors/40277e081e5e8617. Report an issue: GitHub.