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
- Treat this as expected in concurrency: only call Complete once per upload session and disable the UI button after the first call.
- Query the current status of the upload row; if it is completed, fetch the resulting file record instead of merging again.
- If a row is stuck in 'merging' from a crashed process, reset its status back to 'uploading' (manually or via admin endpoint) and retry.
- If stuck in 'failed', restart the upload flow with a new session.
- 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
- Call Complete exactly once per upload; debounce/disable the button
- Add client idempotency keyed by uploadID
- Poll upload status after a conflict instead of re-invoking Complete
- Monitor for rows stuck in 'merging' and add a cleanup/reset job
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.