{"record":{"id":"5c3343ac36d43790","repo":"flipped-aurora/gin-vue-admin","slug":"s-5c3343","errorCode":null,"errorMessage":"任务名 %s 已存在","messagePattern":"任务名 (.+?) 已存在","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/system/sys_timed_task.go","lineNumber":88,"sourceCode":"\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"executorType 必须为 %s 或 %s\", system.TimedTaskExecutorMethod, system.TimedTaskExecutorHTTP)\n\t}\n\treturn nil\n}\n\n// checkNameUnique 软删除下不建 DB 唯一索引, 由服务层保证活跃行内唯一\nfunc (s *TimedTaskService) checkNameUnique(ctx context.Context, name string, excludeID uint) error {\n\tvar count int64\n\tdb := global.GVA_DB.WithContext(ctx).Model(&system.SysTimedTask{}).Where(\"name = ?\", name)\n\tif excludeID > 0 {\n\t\tdb = db.Where(\"id <> ?\", excludeID)\n\t}\n\tif err := db.Count(&count).Error; err != nil {\n\t\treturn err\n\t}\n\tif count > 0 {\n\t\treturn fmt.Errorf(\"任务名 %s 已存在\", name)\n\t}\n\treturn nil\n}\n\n// ScheduleTask 幂等调度: 先 Clear 再按 enabled 重新注册\nfunc (s *TimedTaskService) ScheduleTask(t system.SysTimedTask) error {\n\tname := timedTaskCronName(t.ID)\n\tglobal.GVA_Timer.Clear(name)\n\tif !t.Enabled {\n\t\treturn nil\n\t}\n\ttaskCopy := t // 值拷贝, 供回调闭包持有(更新任务时会整体重调度, 不会读到旧配置)\n\tfn := func() { s.RunTask(taskCopy, system.TimedTaskTriggerAuto) }\n\tvar err error\n\tif t.WithSeconds {\n\t\t_, err = global.GVA_Timer.AddTaskByFuncWithSecond(name, t.Spec, fn, t.Name)\n\t} else {\n\t\t_, err = global.GVA_Timer.AddTaskByFunc(name, t.Spec, fn, t.Name)","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/sys_timed_task.go#L70-L106","documentation":"checkNameUnique enforces uniqueness of task names among active (non-soft-deleted) rows at the service layer, because the soft-delete design prevents a DB-level unique index. It counts matching names (excluding the current row id when updating) and returns this error if any exist.","triggerScenarios":"CreateTimedTask with a name that another live task already uses, or UpdateTimedTask renaming a task to a name owned by a different live task (excludeID filters out the task's own row).","commonSituations":"Cloning/duplicating a task without renaming it; two admins creating similarly named tasks concurrently (TOCTOU race between count and insert); renaming after a soft-delete left the original name visible in an old UI list; seed/init data colliding with user-created tasks.","solutions":["Choose a different, unique task name before saving","If the desired name belongs to a soft-deleted row, purge/rename that old row first or pick a fresh name","For updates, ensure excludeID (the task's own id) is passed so renaming to its own current name does not collide with a different row"],"exampleFix":"// before\n{\"name\":\" nightly-sync\", ...} // already used by another enabled task\n// after\n{\"name\":\"nightly-sync-v2\", ...}","handlingStrategy":"validation","validationCode":"var count int64\nglobal.GVA_DB.Model(&system.SysTimedTask{}).\n    Where(\"name = ? AND id <> ?\", proposedName, excludeID).\n    Count(&count)\nif count > 0 {\n    // surface a friendly duplicate-name message before calling the API\n}","typeGuard":null,"tryCatchPattern":"if err := svc.CreateTimedTask(ctx, req); err != nil {\n    if strings.Contains(err.Error(), \"已存在\") {\n        // show 'task name already in use' and prompt for a new name\n        return\n    }\n    return err\n}","preventionTips":["Append a timestamp or id suffix when duplicating tasks","Pre-check names with a lightweight existence query in the UI on blur","Handle the TOCTOU race on the server (recheck before insert or use a serializable transaction)"],"tags":["validation","duplicate","timed-task","go"],"backgroundTag":"duplicate-name-conflict","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}