flipped-aurora/gin-vue-admin · error

文件删除失败:

Error message

文件删除失败: 

What it means

This error is returned by Local.DeleteFile when os.Remove(p) fails after the file was confirmed to exist and the delete lock was acquired. It wraps the underlying OS error (permission denied, device busy, read-only filesystem, etc.) appended after the '文件删除失败: ' prefix.

Source

Thrown at server/utils/upload/local.go:100

func (l *Local) DeleteFile(ctx context.Context, key string) error {
	p, err := l.localPath(ctx, key)
	if err != nil {
		return err
	}

	// 检查文件是否存在
	if _, err := os.Stat(p); os.IsNotExist(err) {
		return errors.New("文件不存在")
	}

	// 使用文件锁防止并发删除
	mu.Lock()
	defer mu.Unlock()

	err = os.Remove(p)
	if err != nil {
		return errors.New("文件删除失败: " + err.Error())
	}

	return nil
}

// localPath 校验 key 并拼接出本地存储的绝对路径,复用 DeleteFile 中的路径穿越防护逻辑。
func (*Local) localPath(ctx context.Context, key string) (string, error) {
	// 检查 key 是否为空
	if key == "" {
		return "", errors.New("key不能为空")
	}

	// 验证 key 是否包含非法字符或尝试访问存储路径之外的文件
	if strings.Contains(key, "..") || strings.ContainsAny(key, `\/:*?"<>|`) {
		return "", errors.New("非法的key")
	}

	return filepath.Join(global.GVA_CONFIG.Local.StorePath, key), nil

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Read the appended OS error and fix the cause: chmod/chown the StorePath directory so the process user has write+execute permission.
  2. On Windows, identify and close processes holding the file open (handle.exe / Resource Monitor), or retry after they release it.
  3. If storage is a mounted volume, verify it is mounted read-write (mount options, K8s volume spec).
  4. If it's a Stat/Remove race, retry the delete once — the second attempt will report 文件不存在 which you can treat as success.

Example fix

// before
# ls -l uploads -> drwxr-xr-x root root; server runs as appuser
// after
# shell
sudo chown appuser:appuser uploads && chmod 755 uploads
Defensive patterns

Strategy: try-catch

Validate before calling

p := filepath.Join(storePath, key)
info, err := os.Stat(p)
if err != nil {
    return fmt.Errorf("cannot access file: %w", err)
}
dir := filepath.Dir(p)
if ti, err := os.Stat(dir); err != nil || ti.Mode()&0o200 == 0 {
    return errors.New("store directory is not writable by this process")
}

Try / catch

err := local.DeleteFile(ctx, key)
if err != nil {
    if strings.HasPrefix(err.Error(), "文件删除失败") {
        // inspect appended OS error; retry once on transient EBUSY, escalate on EACCES
        time.Sleep(100 * time.Millisecond)
        err = local.DeleteFile(ctx, key)
    }
    if err != nil {
        return err
    }
}

Prevention

When it happens

Trigger: DeleteFile called on an existing file whose removal fails: process lacks write permission on the containing directory, the file is open/locked by another process (Windows), the store lives on a read-only mount, or a race removed the file between Stat and Remove.

Common situations: Uploads directory owned by root while the server runs as a non-root user; Windows services holding files open (antivirus scanners, log shippers); Kubernetes volumes mounted read-only by mistake; NFS stale handles.

Related errors


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