{"record":{"id":"44349d81eb72b023","repo":"flipped-aurora/gin-vue-admin","slug":"key","errorCode":null,"errorMessage":"key不能为空","messagePattern":"key不能为空","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/utils/upload/local.go","lineNumber":110,"sourceCode":"\t}\n\n\t// 使用文件锁防止并发删除\n\tmu.Lock()\n\tdefer mu.Unlock()\n\n\terr = os.Remove(p)\n\tif err != nil {\n\t\treturn errors.New(\"文件删除失败: \" + err.Error())\n\t}\n\n\treturn nil\n}\n\n// localPath 校验 key 并拼接出本地存储的绝对路径，复用 DeleteFile 中的路径穿越防护逻辑。\nfunc (*Local) localPath(ctx context.Context, key string) (string, error) {\n\t// 检查 key 是否为空\n\tif key == \"\" {\n\t\treturn \"\", errors.New(\"key不能为空\")\n\t}\n\n\t// 验证 key 是否包含非法字符或尝试访问存储路径之外的文件\n\tif strings.Contains(key, \"..\") || strings.ContainsAny(key, `\\/:*?\"<>|`) {\n\t\treturn \"\", errors.New(\"非法的key\")\n\t}\n\n\treturn filepath.Join(global.GVA_CONFIG.Local.StorePath, key), nil\n}\n\n// Exists 检查本地文件是否存在，\"不存在\"统一降级为 (false, nil)。\nfunc (l *Local) Exists(ctx context.Context, key string) (bool, error) {\n\tp, err := l.localPath(ctx, key)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tinfo, err := os.Stat(p)","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/upload/local.go#L92-L128","documentation":"This validation error is returned by the internal localPath helper, used by DeleteFile and Exists, when the provided key is the empty string. The library refuses to resolve an empty key because it would otherwise operate on the store directory itself rather than a concrete file.","triggerScenarios":"Calling DeleteFile or Exists with key=\"\", typically from a DB record with an empty/NULL file key, a form field that wasn't filled, or a caller that didn't check the key before invoking.","commonSituations":"Legacy rows in the upload table created before keys were mandatory; frontend sending empty strings for optional file fields; batch delete lists containing blank entries.","solutions":["Validate/skip empty keys in the caller before invoking DeleteFile/Exists (trim whitespace too).","Clean up DB records holding empty keys so they never reach the delete path.","In batch DeleteFiles, filter the key slice first: keys where strings.TrimSpace(k) != \"\"."],"exampleFix":"// before\nerr := local.DeleteFile(ctx, record.Key) // Key may be \"\"\n// after\nif strings.TrimSpace(record.Key) == \"\" {\n    return nil // nothing to delete\n}\nerr := local.DeleteFile(ctx, record.Key)","handlingStrategy":"validation","validationCode":"func safeKey(key string) (string, bool) {\n    key = strings.TrimSpace(key)\n    return key, key != \"\"\n}\n// usage: k, ok := safeKey(record.Key); if !ok { skip }","typeGuard":"func validKey(key string) bool {\n    return strings.TrimSpace(key) != \"\"\n}","tryCatchPattern":"err := local.DeleteFile(ctx, key)\nif err != nil && err.Error() == \"key不能为空\" {\n    return fmt.Errorf(\"record %d has no file key; nothing to delete\", record.ID)\n}","preventionTips":["Make the file key column NOT NULL at the DB level where a file is mandatory.","Trim and validate keys at the API boundary before persisting or deleting.","Filter blank entries out of batch delete lists.","Return 400 (not 500) to clients that submit an empty key."],"tags":["validation","input-validation","go"],"backgroundTag":"empty-required-parameter","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}