From 4589c649238b9b95fedb1c83365c88e8d80c2db5 Mon Sep 17 00:00:00 2001 From: engigu Date: Tue, 3 Mar 2026 17:25:38 +0800 Subject: [PATCH] feat: refact id column define --- CHANGELOG.md | 7 + cmd/cmd.go | 2 + cmd/migrate/migrate.go | 29 ++ go.mod | 1 + go.sum | 2 + internal/bootstrap/bootstrap.go | 5 + internal/controllers/agent_controller.go | 82 ++--- internal/controllers/dashboard_controller.go | 8 +- internal/controllers/dependency_controller.go | 13 +- internal/controllers/env_controller.go | 19 +- internal/controllers/executor_controller.go | 4 +- internal/controllers/log_controller.go | 27 +- internal/controllers/log_ws_controller.go | 14 +- internal/controllers/script_controller.go | 17 +- internal/controllers/settings_controller.go | 6 +- internal/controllers/task_controller.go | 50 ++- internal/controllers/terminal_controller.go | 12 +- internal/database/migrate.go | 2 +- internal/executor/executor.go | 16 +- internal/executor/scheduler.go | 22 +- internal/middleware/auth.go | 30 +- internal/models/agent.go | 16 +- internal/models/dependency.go | 2 +- internal/models/env.go | 8 +- internal/models/language.go | 2 +- internal/models/login_log.go | 2 +- internal/models/send_stats.go | 4 +- internal/models/setting.go | 2 +- internal/models/task.go | 18 +- internal/models/user.go | 2 +- internal/models/vo/agent_vo.go | 4 +- internal/models/vo/dep_vo.go | 2 +- internal/models/vo/script_vo.go | 2 +- internal/models/vo/sys_vo.go | 6 +- internal/models/vo/task_vo.go | 12 +- internal/services/agent_service.go | 38 +- internal/services/agent_ws_service.go | 30 +- internal/services/backup_service.go | 20 +- internal/services/dependency_service.go | 8 +- internal/services/env_service.go | 50 +-- internal/services/login_log_service.go | 2 + internal/services/migration_v3.go | 326 ++++++++++++++++++ internal/services/mise_service.go | 3 +- internal/services/script_service.go | 20 +- internal/services/send_stats_service.go | 6 +- internal/services/settings_service.go | 21 +- internal/services/tasks/executor_service.go | 113 +++--- internal/services/tasks/task_log_service.go | 24 +- internal/services/tasks/task_service.go | 18 +- internal/services/tasks/tiny_log.go | 14 +- internal/services/user_service.go | 4 +- internal/utils/id.go | 19 + internal/utils/token.go | 10 +- web/src/api/index.ts | 74 ++-- web/src/views/agents/Agents.vue | 12 +- web/src/views/dependencies/Dependencies.vue | 2 +- web/src/views/environments/Environments.vue | 8 +- web/src/views/history/History.vue | 22 +- web/src/views/loginlogs/LoginLogs.vue | 2 +- web/src/views/tasks/RepoDialog.vue | 2 +- web/src/views/tasks/TaskDialog.vue | 10 +- web/src/views/tasks/Tasks.vue | 27 +- 62 files changed, 881 insertions(+), 454 deletions(-) create mode 100644 cmd/migrate/migrate.go create mode 100644 internal/services/migration_v3.go create mode 100644 internal/utils/id.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b712f42..e890903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ > 出于安全及环境隔离考虑,推荐使用 Docker/Compose 部署方式。[镜像地址](https://github.com/engigu/baihu-panel/pkgs/container/baihu) +## ⚠️ 重大升级与迁移说明 (v3 数据结构) +本次更新包含底层数据结构的重大突破,将所有数据的 ID 类型从数字编号平滑迁移为20位的字符式全局唯一标识符(`xid`)。系统在启动时会**自动进行数据的清洗、映射、拷贝与外键修补**,以确保旧数据被妥善对接。 +- **备份位置**:执行迁移前,即使有程序自动转换逻辑(data\migration_v3_backup_backup_xxx.zip),为了数据安全,仍然建议您**提前手动进行备份**。系统的默认 SQLite 数据库文件通常位于配置的 `data/` 目录中。 +- **如果遇到失败**:由于不同用户原本的数据和环境复杂度存在差异,如果遇到未预期的迁移失败或数据显示丢失,**请使用原本备份的数据库** 并 **降级至 `v1.0.10` 及以下旧版本** 进行恢复与使用。 +- **全新启用**:对部分希望拥抱新数据结构的用户而言,也可以根据情况选择在新版本中直接重新建立配置。 +- **致谢与展望**:这次数据结构级的大幅重构,主要是为了后续项目功能扩展(含分布式管理、多租户隔离、数据同步等)的底层根基准备,再不改以后改不动了。给大家带来的使用不便敬请见谅,感谢支持! + ## 快速部署 ### 🐳 方式一:Docker 部署(推荐) diff --git a/cmd/cmd.go b/cmd/cmd.go index 0d4769b..de29c36 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -4,6 +4,7 @@ import ( "github.com/engigu/baihu-panel/cmd/reposync" "github.com/engigu/baihu-panel/cmd/resetpwd" "github.com/engigu/baihu-panel/cmd/restore" + // "github.com/engigu/baihu-panel/cmd/migrate" ) // CommandHandler 定义命令执行函数 @@ -14,4 +15,5 @@ var Handlers = map[string]CommandHandler{ "reposync": reposync.Run, "resetpwd": resetpwd.Run, "restore": restore.Run, + // "migrate": migrate.Run, } diff --git a/cmd/migrate/migrate.go b/cmd/migrate/migrate.go new file mode 100644 index 0000000..6321e64 --- /dev/null +++ b/cmd/migrate/migrate.go @@ -0,0 +1,29 @@ +package migrate + +import ( + "fmt" + "github.com/engigu/baihu-panel/internal/bootstrap" + "github.com/engigu/baihu-panel/internal/services" +) + +func Run(args []string) { + fmt.Println("Starting Migration V3...") + // 初始化基础环境(配置和数据库,但不运行常规 Migrate,因为我们想手动控) + // 不过 bootstrap.New() 会调用 Migrate(). + // 我们可以调用 InitBasic() + app := bootstrap.InitBasic() + if app == nil { + fmt.Println("Failed to initialize app") + return + } + + // 此时数据库已经连接,Migrate() 已经运行过了(因为 bootstrap.InitBasic 调用了 app.initDatabase) + // 由于我们在 Migrate() 中集成了 RunMigrationV3(),所以其实已经跑过了。 + // 如果用户想重复跑,或者单独跑: + err := services.RunMigrationV3() + if err != nil { + fmt.Printf("Migration failed: %v\n", err) + return + } + fmt.Println("Migration V3 completed successfully.") +} diff --git a/go.mod b/go.mod index 63558b8..d21aafe 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rs/xid v1.6.0 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.9.2 // indirect diff --git a/go.sum b/go.sum index 83e3d61..433deee 100644 --- a/go.sum +++ b/go.sum @@ -244,6 +244,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 23fba9b..8e02b8d 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -84,6 +84,11 @@ func (a *App) initDatabase() { logger.Fatalf("Failed to init database: %v", err) } + // 执行 V3 迁移(ID 变更迁移) + if err := services.RunMigrationV3(); err != nil { + logger.Fatalf("Failed to run V3 migration: %v", err) + } + if err := database.Migrate(); err != nil { logger.Fatalf("Failed to migrate database: %v", err) } diff --git a/internal/controllers/agent_controller.go b/internal/controllers/agent_controller.go index a47269a..ede7e08 100644 --- a/internal/controllers/agent_controller.go +++ b/internal/controllers/agent_controller.go @@ -49,8 +49,8 @@ func (c *AgentController) List(ctx *gin.Context) { // Update 更新 Agent func (c *AgentController) Update(ctx *gin.Context) { - id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) - if err != nil { + id := ctx.Param("id") + if id == "" { utils.BadRequest(ctx, "无效的 ID") return } @@ -67,14 +67,14 @@ func (c *AgentController) Update(ctx *gin.Context) { } // 获取旧状态 - oldAgent := c.agentService.GetByID(uint(id)) + oldAgent := c.agentService.GetByID(id) if oldAgent == nil { utils.NotFound(ctx, "Agent 不存在") return } wasEnabled := oldAgent.Enabled - if err := c.agentService.Update(uint(id), req.Name, req.Description, req.Enabled); err != nil { + if err := c.agentService.Update(id, req.Name, req.Description, req.Enabled); err != nil { utils.ServerError(ctx, err.Error()) return } @@ -83,14 +83,14 @@ func (c *AgentController) Update(ctx *gin.Context) { if wasEnabled != req.Enabled { if req.Enabled { // 启用:发送任务列表 - c.wsManager.SendToAgent(uint(id), services.WSTypeEnabled, map[string]interface{}{ + c.wsManager.SendToAgent(id, services.WSTypeEnabled, map[string]interface{}{ "message": "Agent 已启用", }) // 发送任务列表 - c.wsManager.BroadcastTasks(uint(id)) + c.wsManager.BroadcastTasks(id) } else { // 禁用:发送禁用消息,Agent 收到后清空任务 - c.wsManager.SendToAgent(uint(id), services.WSTypeDisabled, map[string]interface{}{ + c.wsManager.SendToAgent(id, services.WSTypeDisabled, map[string]interface{}{ "message": "Agent 已禁用", }) } @@ -101,13 +101,13 @@ func (c *AgentController) Update(ctx *gin.Context) { // Delete 删除 Agent func (c *AgentController) Delete(ctx *gin.Context) { - id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) - if err != nil { + id := ctx.Param("id") + if id == "" { utils.BadRequest(ctx, "无效的 ID") return } - if err := c.agentService.Delete(uint(id)); err != nil { + if err := c.agentService.Delete(id); err != nil { utils.BadRequest(ctx, err.Error()) return } @@ -117,13 +117,13 @@ func (c *AgentController) Delete(ctx *gin.Context) { // RegenerateToken 重新生成 Token func (c *AgentController) RegenerateToken(ctx *gin.Context) { - id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) - if err != nil { + id := ctx.Param("id") + if id == "" { utils.BadRequest(ctx, "无效的 ID") return } - token, err := c.agentService.RegenerateToken(uint(id)) + token, err := c.agentService.RegenerateToken(id) if err != nil { utils.ServerError(ctx, err.Error()) return @@ -324,13 +324,13 @@ func (c *AgentController) GetVersion(ctx *gin.Context) { // ForceUpdate 强制更新指定 Agent func (c *AgentController) ForceUpdate(ctx *gin.Context) { - id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) - if err != nil { + id := ctx.Param("id") + if id == "" { utils.BadRequest(ctx, "无效的 ID") return } - if err := c.agentService.SetForceUpdate(uint(id)); err != nil { + if err := c.agentService.SetForceUpdate(id); err != nil { utils.ServerError(ctx, err.Error()) return } @@ -390,20 +390,20 @@ func (c *AgentController) WSConnect(ctx *gin.Context) { ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } - logger.Infof("[AgentWS] 注册成功: Agent #%d, isNew=%v", agent.ID, isNewAgent) + logger.Infof("[AgentWS] 注册成功: Agent #%s, isNew=%v", agent.ID, isNewAgent) } if !agent.Enabled { c.wsManager.RecordConnectFail(ip) - logger.Warnf("[AgentWS] Agent #%d 已禁用, IP=%s", agent.ID, ip) + logger.Warnf("[AgentWS] Agent #%s 已禁用, IP=%s", agent.ID, ip) ctx.JSON(http.StatusForbidden, gin.H{"error": "Agent 已禁用"}) return } - logger.Infof("[AgentWS] 准备升级连接: Agent #%d, IP=%s", agent.ID, ip) + logger.Infof("[AgentWS] 准备升级连接: Agent #%s, IP=%s", agent.ID, ip) conn, err := agentUpgrader.Upgrade(ctx.Writer, ctx.Request, nil) if err != nil { - logger.Errorf("[AgentWS] 升级连接失败: %v, Agent #%d, IP=%s", err, agent.ID, ip) + logger.Errorf("[AgentWS] 升级连接失败: %v, Agent #%s, IP=%s", err, agent.ID, ip) return } @@ -434,7 +434,7 @@ func (c *AgentController) WSConnect(ctx *gin.Context) { }, }) - logger.Infof("[AgentWS] Agent #%d 连接成功 (配置: workers=%d, queue=%d, rate=%d)", + logger.Infof("[AgentWS] Agent #%s 连接成功 (配置: workers=%d, queue=%d, rate=%d)", agent.ID, workerCount, queueSize, rateInterval) // 启动读写协程 @@ -449,9 +449,9 @@ func (c *AgentController) WSConnect(ctx *gin.Context) { func (c *AgentController) wsReadPump(ac *services.AgentConnection, agent *models.Agent) { defer func() { if r := recover(); r != nil { - logger.Errorf("[AgentWS] Agent #%d wsReadPump panic: %v", agent.ID, r) + logger.Errorf("[AgentWS] Agent #%s wsReadPump panic: %v", agent.ID, r) } - logger.Infof("[AgentWS] Agent #%d wsReadPump 退出", agent.ID) + logger.Infof("[AgentWS] Agent #%s wsReadPump 退出", agent.ID) c.wsManager.Unregister(agent.ID, ac) }() @@ -471,7 +471,7 @@ func (c *AgentController) wsReadPump(ac *services.AgentConnection, agent *models for { _, message, err := ac.ReadMessage() if err != nil { - logger.Warnf("[AgentWS] Agent #%d 读取错误: %v", agent.ID, err) + logger.Warnf("[AgentWS] Agent #%s 读取错误: %v", agent.ID, err) break } @@ -488,9 +488,9 @@ func (c *AgentController) wsReadPump(ac *services.AgentConnection, agent *models func (c *AgentController) wsWritePump(ac *services.AgentConnection) { defer func() { if r := recover(); r != nil { - logger.Errorf("[AgentWS] Agent #%d wsWritePump panic: %v", ac.AgentID, r) + logger.Errorf("[AgentWS] Agent #%s wsWritePump panic: %v", ac.AgentID, r) } - logger.Infof("[AgentWS] Agent #%d wsWritePump 退出", ac.AgentID) + logger.Infof("[AgentWS] Agent #%s wsWritePump 退出", ac.AgentID) }() ticker := time.NewTicker(30 * time.Second) @@ -500,15 +500,15 @@ func (c *AgentController) wsWritePump(ac *services.AgentConnection) { select { case message, ok := <-ac.Send: if !ok { - logger.Warnf("[AgentWS] Agent #%d Send channel 已关闭", ac.AgentID) + logger.Warnf("[AgentWS] Agent #%s Send channel 已关闭", ac.AgentID) return } if ac.IsClosed() { - logger.Warnf("[AgentWS] Agent #%d 连接已关闭(write)", ac.AgentID) + logger.Warnf("[AgentWS] Agent #%s 连接已关闭(write)", ac.AgentID) return } if err := ac.WriteMessage(message); err != nil { - logger.Warnf("[AgentWS] Agent #%d 写入消息失败: %v", ac.AgentID, err) + logger.Warnf("[AgentWS] Agent #%s 写入消息失败: %v", ac.AgentID, err) return } case <-ticker.C: @@ -516,7 +516,7 @@ func (c *AgentController) wsWritePump(ac *services.AgentConnection) { return } if err := ac.WritePing(); err != nil { - logger.Warnf("[AgentWS] Agent #%d 发送 Ping 失败: %v", ac.AgentID, err) + logger.Warnf("[AgentWS] Agent #%s 发送 Ping 失败: %v", ac.AgentID, err) return } } @@ -546,15 +546,15 @@ func (c *AgentController) handleWSMessage(ac *services.AgentConnection, agent *m // handleTaskHeartbeat 处理任务心跳 func (c *AgentController) handleTaskHeartbeat(agent *models.Agent, data json.RawMessage) { var req struct { - LogID uint `json:"log_id"` - Duration int64 `json:"duration"` + LogID string `json:"log_id"` + Duration int64 `json:"duration"` } if err := json.Unmarshal(data, &req); err != nil { logger.Errorf("[AgentWS] 解析心跳消息失败: %v", err) return } - if req.LogID > 0 { - logger.Infof("[AgentWS] 收到任务心跳: LogID=%d, Duration=%dms", req.LogID, req.Duration) + if req.LogID != "" { + logger.Infof("[AgentWS] 收到任务心跳: LogID=%s, Duration=%dms", req.LogID, req.Duration) c.agentService.UpdateTaskDuration(req.LogID, req.Duration) } } @@ -565,7 +565,7 @@ func (c *AgentController) handleFetchTasks(agent *models.Agent) { c.wsManager.SendToAgent(agent.ID, services.WSTypeTasks, map[string]interface{}{ "tasks": tasks, }) - logger.Infof("[AgentWS] Agent #%d 请求任务列表,返回 %d 个任务", agent.ID, len(tasks)) + logger.Infof("[AgentWS] Agent #%s 请求任务列表,返回 %d 个任务", agent.ID, len(tasks)) } // handleHeartbeat 处理心跳 @@ -619,7 +619,7 @@ func (c *AgentController) handleTaskResult(agent *models.Agent, data json.RawMes // handleTaskLog 处理 Agent 发送的实时日志 func (c *AgentController) handleTaskLog(agent *models.Agent, data json.RawMessage) { var logMsg struct { - LogID uint `json:"log_id"` + LogID string `json:"log_id"` Content string `json:"content"` } if err := json.Unmarshal(data, &logMsg); err != nil { @@ -631,12 +631,12 @@ func (c *AgentController) handleTaskLog(agent *models.Agent, data json.RawMessag if tl != nil { tl.Write([]byte(logMsg.Content)) } else { - logger.Warnf("[AgentWS] 收到任务日志但未找到活跃 TinyLog: LogID=%d, ContentSize=%d", logMsg.LogID, len(logMsg.Content)) + logger.Warnf("[AgentWS] 收到任务日志 but could not find active TinyLog: LogID=%s, ContentSize=%d", logMsg.LogID, len(logMsg.Content)) } } // NotifyTaskUpdate 通知 Agent 任务更新 -func (c *AgentController) NotifyTaskUpdate(agentID uint) { +func (c *AgentController) NotifyTaskUpdate(agentID string) { c.wsManager.BroadcastTasks(agentID) } @@ -682,13 +682,13 @@ func (c *AgentController) CreateToken(ctx *gin.Context) { // DeleteToken 删除令牌 func (c *AgentController) DeleteToken(ctx *gin.Context) { - id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) - if err != nil { + id := ctx.Param("id") + if id == "" { utils.BadRequest(ctx, "无效的 ID") return } - if err := c.agentService.DeleteToken(uint(id)); err != nil { + if err := c.agentService.DeleteToken(id); err != nil { utils.ServerError(ctx, err.Error()) return } diff --git a/internal/controllers/dashboard_controller.go b/internal/controllers/dashboard_controller.go index cca7bf9..a757d3d 100644 --- a/internal/controllers/dashboard_controller.go +++ b/internal/controllers/dashboard_controller.go @@ -139,7 +139,7 @@ func (dc *DashboardController) GetSendStats(c *gin.Context) { // TaskStats 任务执行统计 type TaskStats struct { - TaskID uint `json:"task_id"` + TaskID string `json:"task_id"` TaskName string `json:"task_name"` Count int `json:"count"` } @@ -159,7 +159,7 @@ func (dc *DashboardController) GetTaskStats(c *gin.Context) { // 按 task_id 聚合统计 var results []struct { - TaskID uint + TaskID string Total int } database.DB.Model(&models.SendStats{}). @@ -170,7 +170,7 @@ func (dc *DashboardController) GetTaskStats(c *gin.Context) { Find(&results) // 获取任务名称 - taskIDs := make([]uint, 0, len(results)) + taskIDs := make([]string, 0, len(results)) for _, r := range results { taskIDs = append(taskIDs, r.TaskID) } @@ -179,7 +179,7 @@ func (dc *DashboardController) GetTaskStats(c *gin.Context) { if len(taskIDs) > 0 { database.DB.Where("id IN ?", taskIDs).Find(&tasks) } - taskNameMap := make(map[uint]string) + taskNameMap := make(map[string]string) for _, t := range tasks { taskNameMap[t.ID] = t.Name } diff --git a/internal/controllers/dependency_controller.go b/internal/controllers/dependency_controller.go index 644a733..9c5a495 100644 --- a/internal/controllers/dependency_controller.go +++ b/internal/controllers/dependency_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "strconv" "strings" "github.com/engigu/baihu-panel/internal/models" @@ -71,8 +70,8 @@ func (c *DependencyController) Create(ctx *gin.Context) { // Delete 删除依赖 func (c *DependencyController) Delete(ctx *gin.Context) { - id, err := strconv.Atoi(ctx.Param("id")) - if err != nil { + id := ctx.Param("id") + if id == "" { utils.BadRequest(ctx, "无效的 ID") return } @@ -186,8 +185,8 @@ func (c *DependencyController) GetReinstallAllCommand(ctx *gin.Context) { // Uninstall 卸载依赖 func (c *DependencyController) Uninstall(ctx *gin.Context) { - id, err := strconv.Atoi(ctx.Param("id")) - if err != nil { + id := ctx.Param("id") + if id == "" { utils.BadRequest(ctx, "无效的 ID") return } @@ -220,8 +219,8 @@ func (c *DependencyController) Uninstall(ctx *gin.Context) { // Reinstall 重新安装依赖 func (c *DependencyController) Reinstall(ctx *gin.Context) { - id, err := strconv.Atoi(ctx.Param("id")) - if err != nil { + id := ctx.Param("id") + if id == "" { utils.BadRequest(ctx, "无效的 ID") return } diff --git a/internal/controllers/env_controller.go b/internal/controllers/env_controller.go index 4ceaeeb..9ac375f 100644 --- a/internal/controllers/env_controller.go +++ b/internal/controllers/env_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "strconv" "github.com/engigu/baihu-panel/internal/models/vo" "github.com/engigu/baihu-panel/internal/services" @@ -19,7 +18,7 @@ func NewEnvController(envService *services.EnvService) *EnvController { } func (ec *EnvController) CreateEnvVar(c *gin.Context) { - userID := 1 + userID := c.GetString("userID") var req struct { Name string `json:"name" binding:"required"` @@ -43,7 +42,7 @@ func (ec *EnvController) CreateEnvVar(c *gin.Context) { } func (ec *EnvController) GetEnvVars(c *gin.Context) { - userID := 1 + userID := c.GetString("userID") p := utils.ParsePagination(c) name := c.DefaultQuery("name", "") envVars, total := ec.envService.GetEnvVarsWithPagination(userID, name, p.Page, p.PageSize) @@ -51,14 +50,14 @@ func (ec *EnvController) GetEnvVars(c *gin.Context) { } func (ec *EnvController) GetAllEnvVars(c *gin.Context) { - userID := 1 + userID := c.GetString("userID") envVars := ec.envService.GetEnvVarsByUserID(userID) utils.Success(c, vo.ToEnvVOListFromModels(envVars)) } func (ec *EnvController) GetEnvVar(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的环境变量ID") return } @@ -73,8 +72,8 @@ func (ec *EnvController) GetEnvVar(c *gin.Context) { } func (ec *EnvController) UpdateEnvVar(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的环境变量ID") return } @@ -112,8 +111,8 @@ func (ec *EnvController) UpdateEnvVar(c *gin.Context) { } func (ec *EnvController) DeleteEnvVar(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的环境变量ID") return } diff --git a/internal/controllers/executor_controller.go b/internal/controllers/executor_controller.go index 7756ff1..c73d32c 100644 --- a/internal/controllers/executor_controller.go +++ b/internal/controllers/executor_controller.go @@ -19,8 +19,8 @@ func NewExecutorController(executorService *tasks.ExecutorService) *ExecutorCont } func (ec *ExecutorController) ExecuteTask(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的任务ID") return } diff --git a/internal/controllers/log_controller.go b/internal/controllers/log_controller.go index a17a68c..9038f39 100644 --- a/internal/controllers/log_controller.go +++ b/internal/controllers/log_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "strconv" "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" @@ -19,7 +18,7 @@ func NewLogController() *LogController { func (lc *LogController) GetLogs(c *gin.Context) { p := utils.ParsePagination(c) - taskID, _ := strconv.Atoi(c.DefaultQuery("task_id", "0")) + taskID := c.DefaultQuery("task_id", "") taskName := c.DefaultQuery("task_name", "") status := c.DefaultQuery("status", "") @@ -27,7 +26,7 @@ func (lc *LogController) GetLogs(c *gin.Context) { var total int64 query := database.DB.Model(&models.TaskLog{}) - if taskID > 0 { + if taskID != "" { query = query.Where("task_id = ?", taskID) } if status != "" { @@ -36,7 +35,7 @@ func (lc *LogController) GetLogs(c *gin.Context) { // 按任务名称过滤 if taskName != "" { - var taskIDs []uint + var taskIDs []string database.DB.Model(&models.Task{}).Where("name LIKE ?", "%"+taskName+"%").Pluck("id", &taskIDs) if len(taskIDs) > 0 { query = query.Where("task_id IN ?", taskIDs) @@ -49,14 +48,14 @@ func (lc *LogController) GetLogs(c *gin.Context) { query.Count(&total) query.Order("id DESC").Offset(p.Offset()).Limit(p.PageSize).Find(&logs) - taskIDList := make([]uint, 0) + taskIDList := make([]string, 0) for _, log := range logs { taskIDList = append(taskIDList, log.TaskID) } var tasks []models.Task database.DB.Where("id IN ?", taskIDList).Find(&tasks) - taskMap := make(map[uint]models.Task) + taskMap := make(map[string]models.Task) for _, t := range tasks { taskMap[t.ID] = t } @@ -87,14 +86,14 @@ func (lc *LogController) GetLogs(c *gin.Context) { } func (lc *LogController) GetLogDetail(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的日志ID") return } var log models.TaskLog - if err := database.DB.First(&log, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).First(&log).Error; err != nil { utils.NotFound(c, "日志不存在") return } @@ -104,7 +103,7 @@ func (lc *LogController) GetLogDetail(c *gin.Context) { func (lc *LogController) ClearLogs(c *gin.Context) { var req struct { - TaskID *int `json:"task_id"` + TaskID *string `json:"task_id"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -113,7 +112,7 @@ func (lc *LogController) ClearLogs(c *gin.Context) { } query := database.DB.Model(&models.TaskLog{}) - if req.TaskID != nil && *req.TaskID > 0 { + if req.TaskID != nil && *req.TaskID != "" { query = query.Where("task_id = ?", *req.TaskID) } else { query = query.Where("1 = 1") // Allow delete all without GORM safety block @@ -128,13 +127,13 @@ func (lc *LogController) ClearLogs(c *gin.Context) { } func (lc *LogController) DeleteLog(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的日志ID") return } - if err := database.DB.Delete(&models.TaskLog{}, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).Delete(&models.TaskLog{}).Error; err != nil { utils.ServerError(c, "删除日志失败") return } diff --git a/internal/controllers/log_ws_controller.go b/internal/controllers/log_ws_controller.go index 92908d8..2376b15 100644 --- a/internal/controllers/log_ws_controller.go +++ b/internal/controllers/log_ws_controller.go @@ -2,7 +2,6 @@ package controllers import ( "fmt" - "strconv" "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" @@ -25,10 +24,7 @@ func (lc *LogWSController) StreamLog(c *gin.Context) { return } - logID, err := strconv.ParseUint(logIDStr, 10, 32) - if err != nil { - return - } + logID := logIDStr conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { @@ -38,7 +34,7 @@ func (lc *LogWSController) StreamLog(c *gin.Context) { // 1. 检查数据库中是否已结束 var taskLog models.TaskLog - if err := database.DB.First(&taskLog, uint(logID)).Error; err == nil { + if err := database.DB.Where("id = ?", logID).First(&taskLog).Error; err == nil { if taskLog.Status != "running" { // 已结束,读取库内日志 content, err := utils.DecompressFromBase64(taskLog.Output) @@ -52,14 +48,14 @@ func (lc *LogWSController) StreamLog(c *gin.Context) { } // 2. 未结束或未找到记录,尝试从 TinyLogManager 获取 - tl := tasks.GetActiveLog(uint(logID)) + tl := tasks.GetActiveLog(logID) if tl == nil { conn.WriteMessage(websocket.TextMessage, []byte("未找到正在运行的任务日志")) return } // 发送系统提示 - conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("[System] 连接成功,正在监听日志... (LogID: %d)\n", logID))) + conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("[System] 连接成功,正在监听日志... (LogID: %s)\n", logID))) // 发送最后 100 行 lastLines, err := tl.ReadLastLines(100) @@ -78,7 +74,7 @@ func (lc *LogWSController) StreamLog(c *gin.Context) { if !ok { // 任务结束,尝试刷新最后一次库内完整内容 var finalLog models.TaskLog - if err := database.DB.First(&finalLog, uint(logID)).Error; err == nil { + if err := database.DB.Where("id = ?", logID).First(&finalLog).Error; err == nil { content, _ := utils.DecompressFromBase64(finalLog.Output) if content != "" { conn.WriteMessage(websocket.TextMessage, []byte("\n--- 任务已结束 ---\n")) diff --git a/internal/controllers/script_controller.go b/internal/controllers/script_controller.go index 1a1bd25..4138e87 100644 --- a/internal/controllers/script_controller.go +++ b/internal/controllers/script_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "strconv" "github.com/engigu/baihu-panel/internal/models/vo" "github.com/engigu/baihu-panel/internal/services" @@ -19,7 +18,7 @@ func NewScriptController(scriptService *services.ScriptService) *ScriptControlle } func (sc *ScriptController) CreateScript(c *gin.Context) { - userID := 1 + userID := c.GetString("userID") var req struct { Name string `json:"name" binding:"required"` @@ -36,7 +35,7 @@ func (sc *ScriptController) CreateScript(c *gin.Context) { } func (sc *ScriptController) GetScripts(c *gin.Context) { - userID := 1 + userID := c.GetString("userID") scripts := sc.scriptService.GetScriptsByUserID(userID) vos := vo.ToScriptVOListFromModels(scripts) for i := range vos { @@ -46,8 +45,8 @@ func (sc *ScriptController) GetScripts(c *gin.Context) { } func (sc *ScriptController) GetScript(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的脚本ID") return } @@ -62,8 +61,8 @@ func (sc *ScriptController) GetScript(c *gin.Context) { } func (sc *ScriptController) UpdateScript(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的脚本ID") return } @@ -88,8 +87,8 @@ func (sc *ScriptController) UpdateScript(c *gin.Context) { } func (sc *ScriptController) DeleteScript(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的脚本ID") return } diff --git a/internal/controllers/settings_controller.go b/internal/controllers/settings_controller.go index 0cea173..f1093b7 100644 --- a/internal/controllers/settings_controller.go +++ b/internal/controllers/settings_controller.go @@ -59,9 +59,9 @@ func (sc *SettingsController) ChangePassword(c *gin.Context) { return } - // 暂时使用固定用户名 admin - user := sc.userService.GetUserByUsername("admin") - if user == nil { + userID := c.GetString("userID") + var user *models.User + if err := database.DB.Where("id = ?", userID).First(&user).Error; err != nil { utils.NotFound(c, "用户不存在") return } diff --git a/internal/controllers/task_controller.go b/internal/controllers/task_controller.go index bb16b96..feacd79 100644 --- a/internal/controllers/task_controller.go +++ b/internal/controllers/task_controller.go @@ -2,7 +2,6 @@ package controllers import ( "path/filepath" - "strconv" "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/models/vo" @@ -63,7 +62,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) { CleanConfig string `json:"clean_config"` Envs string `json:"envs"` Languages []map[string]string `json:"languages"` - AgentID *uint `json:"agent_id"` + AgentID *string `json:"agent_id"` TriggerType string `json:"trigger_type"` RetryCount int `json:"retry_count"` RetryInterval int `json:"retry_interval"` @@ -90,14 +89,14 @@ func (tc *TaskController) CreateTask(c *gin.Context) { // 转换为绝对路径(Agent 任务保持原样) workDir := req.WorkDir - if req.AgentID == nil || *req.AgentID == 0 { + if req.AgentID == nil || *req.AgentID == "" { workDir = resolveWorkDir(req.WorkDir) } task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, workDir, req.CleanConfig, req.Envs, req.Type, req.Config, req.AgentID, req.Languages, req.TriggerType, req.Tags, req.RetryCount, req.RetryInterval, req.RandomRange) // 如果是 Agent 任务,通知 Agent;否则添加到本地 cron - if task.AgentID != nil && *task.AgentID > 0 { + if task.AgentID != nil && *task.AgentID != "" { tc.agentWSManager.BroadcastTasks(*task.AgentID) } else { tc.executorService.AddCronTask(task) @@ -114,12 +113,9 @@ func (tc *TaskController) GetTasks(c *gin.Context) { tags := c.DefaultQuery("tags", "") taskType := c.DefaultQuery("type", "") - var agentID *uint + var agentID *string if agentIDStr != "" { - if id, err := strconv.ParseUint(agentIDStr, 10, 32); err == nil { - uid := uint(id) - agentID = &uid - } + agentID = &agentIDStr } tasks, total := tc.taskService.GetTasksWithPagination(p.Page, p.PageSize, name, agentID, tags, taskType) @@ -127,8 +123,8 @@ func (tc *TaskController) GetTasks(c *gin.Context) { } func (tc *TaskController) GetTask(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的任务ID") return } @@ -143,15 +139,15 @@ func (tc *TaskController) GetTask(c *gin.Context) { } func (tc *TaskController) UpdateTask(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的任务ID") return } // 获取旧任务信息(用于判断 agent 变更) oldTask := tc.taskService.GetTaskByID(id) - var oldAgentID *uint + var oldAgentID *string if oldTask != nil { oldAgentID = oldTask.AgentID } @@ -169,7 +165,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { Envs string `json:"envs"` Enabled bool `json:"enabled"` Languages []map[string]string `json:"languages"` - AgentID *uint `json:"agent_id"` + AgentID *string `json:"agent_id"` TriggerType string `json:"trigger_type"` RetryCount int `json:"retry_count"` RetryInterval int `json:"retry_interval"` @@ -190,7 +186,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { // 转换为绝对路径(Agent 任务保持原样) workDir := req.WorkDir - if req.AgentID == nil || *req.AgentID == 0 { + if req.AgentID == nil || *req.AgentID == "" { workDir = resolveWorkDir(req.WorkDir) } @@ -201,12 +197,12 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { } // 处理任务调度 - if task.AgentID != nil && *task.AgentID > 0 { + if task.AgentID != nil && *task.AgentID != "" { // Agent 任务:从本地 cron 移除,通知 Agent tc.executorService.RemoveCronTask(task.ID) tc.agentWSManager.BroadcastTasks(*task.AgentID) // 如果 agent 变更了,也通知旧 agent - if oldAgentID != nil && *oldAgentID > 0 && *oldAgentID != *task.AgentID { + if oldAgentID != nil && *oldAgentID != "" && *oldAgentID != *task.AgentID { tc.agentWSManager.BroadcastTasks(*oldAgentID) } } else { @@ -217,7 +213,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { tc.executorService.RemoveCronTask(task.ID) } // 如果之前是 agent 任务,通知旧 agent 移除 - if oldAgentID != nil && *oldAgentID > 0 { + if oldAgentID != nil && *oldAgentID != "" { tc.agentWSManager.BroadcastTasks(*oldAgentID) } } @@ -226,20 +222,20 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { } func (tc *TaskController) DeleteTask(c *gin.Context) { - id, err := strconv.Atoi(c.Param("id")) - if err != nil { + id := c.Param("id") + if id == "" { utils.BadRequest(c, "无效的任务ID") return } // 获取任务信息(用于通知 agent) task := tc.taskService.GetTaskByID(id) - var agentID *uint + var agentID *string if task != nil { agentID = task.AgentID } - tc.executorService.RemoveCronTask(uint(id)) + tc.executorService.RemoveCronTask(id) success := tc.taskService.DeleteTask(id) if !success { @@ -248,7 +244,7 @@ func (tc *TaskController) DeleteTask(c *gin.Context) { } // 如果是 agent 任务,通知 agent - if agentID != nil && *agentID > 0 { + if agentID != nil && *agentID != "" { tc.agentWSManager.BroadcastTasks(*agentID) } @@ -256,13 +252,13 @@ func (tc *TaskController) DeleteTask(c *gin.Context) { } func (tc *TaskController) StopTask(c *gin.Context) { - logID, err := strconv.ParseUint(c.Param("logID"), 10, 32) - if err != nil { + logID := c.Param("logID") + if logID == "" { utils.BadRequest(c, "无效的日志ID") return } - err = tc.executorService.StopTaskExecution(uint(logID)) + err := tc.executorService.StopTaskExecution(logID) if err != nil { utils.BadRequest(c, err.Error()) return diff --git a/internal/controllers/terminal_controller.go b/internal/controllers/terminal_controller.go index 83e3148..b214705 100644 --- a/internal/controllers/terminal_controller.go +++ b/internal/controllers/terminal_controller.go @@ -85,11 +85,9 @@ func (tc *TerminalController) HandleWebSocket(c *gin.Context) { } // Windows 使用 pipe 模式,Unix 使用 PTY 模式 - userID := 1 - if v, exists := c.Get("userID"); exists { - if id, ok := v.(uint); ok { - userID = int(id) - } + userID := c.GetString("userID") + if userID == "" { + userID = "1" // 兜底 } if runtime.GOOS == "windows" { @@ -100,7 +98,7 @@ func (tc *TerminalController) HandleWebSocket(c *gin.Context) { } // handlePtyMode 使用 PTY 处理终端(Unix/macOS) -func (tc *TerminalController) handlePtyMode(conn *websocket.Conn, userID int) { +func (tc *TerminalController) handlePtyMode(conn *websocket.Conn, userID string) { // 发送 PTY 模式标识 conn.WriteMessage(websocket.TextMessage, []byte("__PTY_MODE__")) @@ -174,7 +172,7 @@ func (tc *TerminalController) handlePtyMode(conn *websocket.Conn, userID int) { } // handlePipeMode 使用 pipe 处理终端(Windows) -func (tc *TerminalController) handlePipeMode(conn *websocket.Conn, userID int) { +func (tc *TerminalController) handlePipeMode(conn *websocket.Conn, userID string) { // 发送 pipe 模式标识 conn.WriteMessage(websocket.TextMessage, []byte("__PIPE_MODE__")) diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 6da81d9..faa7b72 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -6,7 +6,7 @@ import ( ) func Migrate() error { - // 先执行自定义迁移 + // 执行自定义迁移 if err := customMigrations(); err != nil { logger.Warnf("[Database] 自定义迁移警告: %v", err) } diff --git a/internal/executor/executor.go b/internal/executor/executor.go index b18a6d0..7bb8184 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -59,13 +59,13 @@ type Result struct { // Hooks 执行钩子接口 type Hooks interface { // PreExecute 执行前钩子,返回日志ID和错误 - PreExecute(ctx context.Context, req Request) (logID uint, err error) + PreExecute(ctx context.Context, req Request) (logID string, err error) // PostExecute 执行后钩子,处理日志压缩和记录更新 - PostExecute(ctx context.Context, logID uint, result *Result) error + PostExecute(ctx context.Context, logID string, result *Result) error // OnHeartbeat 执行中心跳钩子,用于更新实时状态 - OnHeartbeat(ctx context.Context, logID uint, duration int64) error + OnHeartbeat(ctx context.Context, logID string, duration int64) error } // Execute 执行命令(基础版本,不带钩子) @@ -85,7 +85,7 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer } // 仍然触发 PreExecute 以便流程完整 - var logID uint + var logID string if hooks != nil { logID, _ = hooks.PreExecute(ctx, req) } @@ -118,7 +118,7 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer } // 1. 执行前钩子 - var logID uint + var logID string if hooks != nil { id, err := hooks.PreExecute(ctx, req) if err != nil { @@ -171,7 +171,7 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer ) f, ptyErr := pty.Start(cmd) if ptyErr == nil { - logger.Infof("[Executor] 任务 #%d 启动于 PTY 模式", logID) + logger.Infof("[Executor] 任务 #%s 启动于 PTY 模式", logID) ptyFile = f started = true copyDone = make(chan struct{}) @@ -182,7 +182,7 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer f.Close() }() } else { - logger.Errorf("[Executor] 任务 #%d PTY 启动失败: %v", logID, ptyErr) + logger.Errorf("[Executor] 任务 #%s PTY 启动失败: %v", logID, ptyErr) } } @@ -192,7 +192,7 @@ func ExecuteWithHooks(ctx context.Context, req Request, stdout, stderr io.Writer if stdout != stderr && stdout != io.Discard { logger.Debugf("[Executor] 任务 #%d stdout (%p) 和 stderr (%p) 不同,回退到 Pipe 模式。", logID, stdout, stderr) } - logger.Infof("[Executor] 任务 #%d 启动于 Pipe 模式", logID) + logger.Infof("[Executor] 任务 #%s 启动于 Pipe 模式", logID) if stdout != nil && stdout == stderr { pr, pw, err := os.Pipe() if err == nil { diff --git a/internal/executor/scheduler.go b/internal/executor/scheduler.go index c9a2ef0..53a2e85 100644 --- a/internal/executor/scheduler.go +++ b/internal/executor/scheduler.go @@ -63,7 +63,7 @@ const ( // ExecutionRequest 执行请求(标准接口) type ExecutionRequest struct { TaskID string // 任务 ID - LogID uint // 日志 ID + LogID string // 日志 ID Name string // 任务名称 Type TaskType // 任务类型 Command string // 命令 @@ -84,7 +84,7 @@ type ExecutionMetadata struct { // ExecutionResult 执行结果(标准接口) type ExecutionResult struct { TaskID string // 任务 ID - LogID uint // 日志 ID + LogID string // 日志 ID Success bool // 是否成功 Output string // 输出内容 Error string // 错误信息 @@ -151,15 +151,15 @@ type schedulerHooksAdapter struct { req *ExecutionRequest } -func (h *schedulerHooksAdapter) PreExecute(ctx context.Context, req Request) (uint, error) { +func (h *schedulerHooksAdapter) PreExecute(ctx context.Context, req Request) (string, error) { return h.req.LogID, nil } -func (h *schedulerHooksAdapter) PostExecute(ctx context.Context, logID uint, result *Result) error { +func (h *schedulerHooksAdapter) PostExecute(ctx context.Context, logID string, result *Result) error { return nil } -func (h *schedulerHooksAdapter) OnHeartbeat(ctx context.Context, logID uint, duration int64) error { +func (h *schedulerHooksAdapter) OnHeartbeat(ctx context.Context, logID string, duration int64) error { if h.handler != nil { h.handler.OnTaskHeartbeat(h.req, duration) } @@ -182,7 +182,7 @@ type Scheduler struct { mu sync.RWMutex logger SchedulerLogger runningTasks map[string]context.CancelFunc // 记录运行中的任务,用于停止 (TaskID -> CancelFunc) - runningExecs map[uint]context.CancelFunc // 记录运行中的执行,用于停止 (LogID -> CancelFunc) + runningExecs map[string]context.CancelFunc // 记录运行中的执行,用于停止 (LogID -> CancelFunc) } // NewScheduler 创建调度器 @@ -216,7 +216,7 @@ func NewScheduler(config SchedulerConfig, handler SchedulerEventHandler) *Schedu stopCh: make(chan struct{}), logger: &DefaultLogger{}, runningTasks: make(map[string]context.CancelFunc), - runningExecs: make(map[uint]context.CancelFunc), + runningExecs: make(map[string]context.CancelFunc), } return s @@ -443,7 +443,7 @@ func (s *Scheduler) executeTask(req *ExecutionRequest) (*ExecutionResult, error) // 注册到运行中任务 s.mu.Lock() s.runningTasks[req.TaskID] = cancel - if req.LogID > 0 { + if req.LogID != "" { s.runningExecs[req.LogID] = cancel } s.mu.Unlock() @@ -451,7 +451,7 @@ func (s *Scheduler) executeTask(req *ExecutionRequest) (*ExecutionResult, error) defer func() { s.mu.Lock() delete(s.runningTasks, req.TaskID) - if req.LogID > 0 { + if req.LogID != "" { delete(s.runningExecs, req.LogID) } s.mu.Unlock() @@ -527,14 +527,14 @@ func (s *Scheduler) StopTask(taskID string) bool { } // StopLog 停止正在运行的任务(通过 LogID,精确停止单个执行副本) -func (s *Scheduler) StopLog(logID uint) bool { +func (s *Scheduler) StopLog(logID string) bool { s.mu.RLock() cancel, exists := s.runningExecs[logID] s.mu.RUnlock() if exists && cancel != nil { cancel() - s.logger.Infof("[Scheduler] 已尝试停止任务执行 #%d", logID) + s.logger.Infof("[Scheduler] 已尝试停止任务执行 #%s", logID) return true } return false diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index f7ab102..e8f6670 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -5,6 +5,8 @@ import ( "time" "github.com/engigu/baihu-panel/internal/constant" + "github.com/engigu/baihu-panel/internal/database" + "github.com/engigu/baihu-panel/internal/models" "github.com/engigu/baihu-panel/internal/services" "github.com/engigu/baihu-panel/internal/utils" @@ -35,9 +37,19 @@ func AuthRequired() gin.HandlerFunc { return } - // 将用户信息存入上下文 - c.Set("userID", userID) - c.Set("username", username) + // 安全增强:校验数据库中该用户的 ID 是否与 Token 一致 + // 防止迁移后旧 Token 中的数字 ID 污染新数据 + var user models.User + if err := database.DB.Where("username = ?", username).First(&user).Error; err != nil || user.ID != userID { + utils.Unauthorized(c, "会话失效,请重新登录") + ClearAuthCookie(c) + c.Abort() + return + } + + // 将用户信息存入上下文 (必须使用数据库中的最新 ID) + c.Set("userID", user.ID) + c.Set("username", user.Username) c.Next() } } @@ -79,8 +91,16 @@ func checkApiToken(c *gin.Context, settingsSvc *services.SettingsService) bool { } } - c.Set("userID", uint(1)) // 模拟 Admin 角色 - c.Set("username", "api_token_user") + // 模拟 Admin 角色,必须通过实际存在的 admin 用户 ID 来关联 + var adminUser models.User + if err := database.DB.Where("role = ?", "admin").First(&adminUser).Error; err != nil { + utils.Unauthorized(c, "未找到管理员账户,API Token 校验失败") + c.Abort() + return true // 返回 true 表示中间件已处理并截断了请求 + } + + c.Set("userID", adminUser.ID) + c.Set("username", adminUser.Username) c.Next() return true } diff --git a/internal/models/agent.go b/internal/models/agent.go index e878423..2b044d1 100644 --- a/internal/models/agent.go +++ b/internal/models/agent.go @@ -1,8 +1,6 @@ package models import ( - "strconv" - "github.com/engigu/baihu-panel/internal/constant" "gorm.io/gorm" @@ -10,7 +8,7 @@ import ( // Agent 远程执行代理 type Agent struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Name string `json:"name" gorm:"size:100;not null"` // Agent 名称 Token string `json:"token" gorm:"size:64;index"` // 认证 Token(可重复使用) MachineID string `json:"machine_id" gorm:"size:64;uniqueIndex"` // 机器识别码(唯一) @@ -36,7 +34,7 @@ func (Agent) TableName() string { // AgentToken Agent 令牌 type AgentToken struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Token string `json:"token" gorm:"size:64;uniqueIndex;not null"` // 令牌 Remark string `json:"remark" gorm:"size:255"` // 备注 MaxUses int `json:"max_uses" gorm:"default:0"` // 最大使用次数,0 表示无限制 @@ -54,7 +52,7 @@ func (AgentToken) TableName() string { // AgentTask Agent 任务配置(用于下发给 Agent) type AgentTask struct { - ID uint `json:"id"` + ID string `json:"id"` Name string `json:"name"` Command string `json:"command"` Schedule string `json:"schedule"` @@ -67,7 +65,7 @@ type AgentTask struct { } func (t AgentTask) GetID() string { - return strconv.FormatUint(uint64(t.ID), 10) + return t.ID } func (t AgentTask) GetName() string { @@ -88,9 +86,9 @@ func (t AgentTask) GetRandomRange() int { // AgentTaskResult Agent 上报的任务执行结果 type AgentTaskResult struct { - TaskID uint `json:"task_id"` - LogID uint `json:"log_id"` - AgentID uint `json:"agent_id"` + TaskID string `json:"task_id"` + LogID string `json:"log_id"` + AgentID string `json:"agent_id"` Command string `json:"command"` Output string `json:"output"` Error string `json:"error"` // 额外的系统错误信息 diff --git a/internal/models/dependency.go b/internal/models/dependency.go index 17b2bbc..450aab0 100644 --- a/internal/models/dependency.go +++ b/internal/models/dependency.go @@ -8,7 +8,7 @@ import ( // Dependency 依赖包模型 type Dependency struct { - ID int `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Name string `json:"name" gorm:"size:100;not null"` Version string `json:"version" gorm:"size:50"` Language string `json:"language" gorm:"size:100;index"` // 关联语言 (node, python...) diff --git a/internal/models/env.go b/internal/models/env.go index 0952c7f..20c3de3 100644 --- a/internal/models/env.go +++ b/internal/models/env.go @@ -8,12 +8,12 @@ import ( // EnvironmentVariable represents an environment variable type EnvironmentVariable struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Name string `json:"name" gorm:"size:255;not null"` Value string `json:"value" gorm:"type:text"` Remark string `json:"remark" gorm:"size:500"` Hidden bool `json:"hidden" gorm:"default:true"` - UserID uint `json:"user_id" gorm:"index"` + UserID string `json:"user_id" gorm:"size:20;index"` CreatedAt LocalTime `json:"created_at"` UpdatedAt LocalTime `json:"updated_at"` DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` @@ -25,10 +25,10 @@ func (EnvironmentVariable) TableName() string { // Script represents a script file type Script struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Name string `json:"name" gorm:"size:255;not null"` Content string `json:"content" gorm:"type:text"` - UserID uint `json:"user_id" gorm:"index"` + UserID string `json:"user_id" gorm:"size:20;index"` CreatedAt LocalTime `json:"created_at"` UpdatedAt LocalTime `json:"updated_at"` DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` diff --git a/internal/models/language.go b/internal/models/language.go index 614894b..1bf2941 100644 --- a/internal/models/language.go +++ b/internal/models/language.go @@ -6,7 +6,7 @@ import ( ) type Language struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Plugin string `json:"plugin" gorm:"size:100;not null;index"` Version string `json:"version" gorm:"size:100;not null;index"` InstallPath string `json:"install_path" gorm:"size:255"` diff --git a/internal/models/login_log.go b/internal/models/login_log.go index bdb73a8..ac89320 100644 --- a/internal/models/login_log.go +++ b/internal/models/login_log.go @@ -6,7 +6,7 @@ import ( // LoginLog 登录日志 type LoginLog struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Username string `json:"username" gorm:"size:100;index;not null"` IP string `json:"ip" gorm:"size:50"` UserAgent string `json:"user_agent" gorm:"size:500"` diff --git a/internal/models/send_stats.go b/internal/models/send_stats.go index b5bee13..1d518ba 100644 --- a/internal/models/send_stats.go +++ b/internal/models/send_stats.go @@ -6,8 +6,8 @@ import ( // SendStats 任务执行统计 type SendStats struct { - ID uint `json:"id" gorm:"primaryKey"` - TaskID uint `json:"task_id" gorm:"uniqueIndex:idx_task_day_status"` + ID string `json:"id" gorm:"primaryKey;size:20"` + TaskID string `json:"task_id" gorm:"size:20;uniqueIndex:idx_task_day_status"` Day string `json:"day" gorm:"size:10;uniqueIndex:idx_task_day_status"` // 格式: 2006-01-02 Status string `json:"status" gorm:"size:20;uniqueIndex:idx_task_day_status"` Num int `json:"num" gorm:"default:0"` diff --git a/internal/models/setting.go b/internal/models/setting.go index ddefa22..9326264 100644 --- a/internal/models/setting.go +++ b/internal/models/setting.go @@ -6,7 +6,7 @@ import ( // Setting 系统设置 type Setting struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Section string `json:"section" gorm:"size:50;not null;index:idx_section_key"` Key string `json:"key" gorm:"size:100;not null;index:idx_section_key"` Value string `json:"value" gorm:"type:text"` diff --git a/internal/models/task.go b/internal/models/task.go index e7d0143..90ef102 100644 --- a/internal/models/task.go +++ b/internal/models/task.go @@ -1,8 +1,6 @@ package models import ( - "fmt" - "github.com/engigu/baihu-panel/internal/constant" "gorm.io/gorm" @@ -34,7 +32,7 @@ type TaskConfig struct { // Task 代表一个计划任务 type Task struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Name string `json:"name" gorm:"size:255;not null"` Command string `json:"command" gorm:"type:text"` // 普通任务的命令 Tags string `json:"tags" gorm:"size:255;default:''"` // 标签,逗号分隔 @@ -45,9 +43,9 @@ type Task struct { Timeout int `json:"timeout" gorm:"default:30"` // 超时时间(分钟),默认30分钟 WorkDir string `json:"work_dir" gorm:"size:255;default:''"` // 工作目录,为空则使用 scripts 目录 CleanConfig string `json:"clean_config" gorm:"size:255;default:''"` // 清理配置 JSON - Envs string `json:"envs" gorm:"size:255;default:''"` // 环境变量ID列表,逗号分隔 + Envs string `json:"envs" gorm:"type:text"` // 环境变量ID列表,逗号分隔 Languages []map[string]string `json:"languages" gorm:"serializer:json;type:text"` // 针对本地任务的语言配置列表 - AgentID *uint `json:"agent_id" gorm:"index"` // Agent ID,为空表示本地执行 + AgentID *string `json:"agent_id" gorm:"size:20;index"` // Agent ID,为空表示本地执行 RetryCount int `json:"retry_count" gorm:"default:0"` // 失败重试次数 RetryInterval int `json:"retry_interval" gorm:"default:0"` // 失败重试间隔(秒) RandomRange int `json:"random_range" gorm:"default:0"` // 随机延迟范围(秒) @@ -65,7 +63,7 @@ func (Task) TableName() string { } func (t *Task) GetID() string { - return fmt.Sprintf("%d", t.ID) + return t.ID } func (t *Task) GetName() string { @@ -93,7 +91,7 @@ func (t *Task) GetLanguages() []map[string]string { } func (t *Task) GetUseMise() bool { - return t.AgentID == nil || *t.AgentID == 0 + return t.AgentID == nil || *t.AgentID == "" } func (t *Task) UseMise() bool { @@ -110,9 +108,9 @@ func (t *Task) GetRandomRange() int { // TaskLog 代表任务执行的日志记录 type TaskLog struct { - ID uint `json:"id" gorm:"primaryKey"` - TaskID uint `json:"task_id" gorm:"index"` - AgentID *uint `json:"agent_id" gorm:"index"` // Agent ID,为空表示本地执行 + ID string `json:"id" gorm:"primaryKey;size:20"` + TaskID string `json:"task_id" gorm:"size:20;index"` + AgentID *string `json:"agent_id" gorm:"size:20;index"` // Agent ID,为空表示本地执行 Command string `json:"command" gorm:"type:text"` Output string `json:"-" gorm:"type:longtext"` // gzip+base64 压缩后的日志 Error string `json:"error" gorm:"type:text"` // 额外的系统错误信息 diff --git a/internal/models/user.go b/internal/models/user.go index 2daf881..94d5809 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -8,7 +8,7 @@ import ( // User represents a system user type User struct { - ID uint `json:"id" gorm:"primaryKey"` + ID string `json:"id" gorm:"primaryKey;size:20"` Username string `json:"username" gorm:"size:100;uniqueIndex;not null"` Password string `json:"-" gorm:"size:255;not null"` Email string `json:"email" gorm:"size:255"` diff --git a/internal/models/vo/agent_vo.go b/internal/models/vo/agent_vo.go index 3836c8d..54f3d54 100644 --- a/internal/models/vo/agent_vo.go +++ b/internal/models/vo/agent_vo.go @@ -6,7 +6,7 @@ import ( // AgentVO 代理视图对象 type AgentVO struct { - ID uint `json:"id"` + ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Status string `json:"status"` @@ -71,7 +71,7 @@ func ToAgentVOListFromModels(agents []models.Agent) []*AgentVO { // AgentTokenVO 代理令牌视图对象 type AgentTokenVO struct { - ID uint `json:"id"` + ID string `json:"id"` Token string `json:"token"` Remark string `json:"remark"` MaxUses int `json:"max_uses"` diff --git a/internal/models/vo/dep_vo.go b/internal/models/vo/dep_vo.go index b55b73e..6cdc462 100644 --- a/internal/models/vo/dep_vo.go +++ b/internal/models/vo/dep_vo.go @@ -8,7 +8,7 @@ import ( // DependencyVO 依赖包视图对象 type DependencyVO struct { - ID int `json:"id"` + ID string `json:"id"` Name string `json:"name"` Version string `json:"version"` Language string `json:"language"` diff --git a/internal/models/vo/script_vo.go b/internal/models/vo/script_vo.go index a081bdf..fc0c84f 100644 --- a/internal/models/vo/script_vo.go +++ b/internal/models/vo/script_vo.go @@ -6,7 +6,7 @@ import ( // ScriptVO 脚本视图对象 type ScriptVO struct { - ID uint `json:"id"` + ID string `json:"id"` Name string `json:"name"` Content string `json:"content,omitempty"` // 仅在拉取详情时返回 CreatedAt models.LocalTime `json:"created_at"` diff --git a/internal/models/vo/sys_vo.go b/internal/models/vo/sys_vo.go index 1c9d23d..eb65421 100644 --- a/internal/models/vo/sys_vo.go +++ b/internal/models/vo/sys_vo.go @@ -6,7 +6,7 @@ import ( // UserVO 用户视图对象 type UserVO struct { - ID uint `json:"id"` + ID string `json:"id"` Username string `json:"username"` Email string `json:"email"` Role string `json:"role"` @@ -31,7 +31,7 @@ func ToUserVO(user *models.User) *UserVO { // EnvVO 环境变量视图对象 type EnvVO struct { - ID uint `json:"id"` + ID string `json:"id"` Name string `json:"name"` Value string `json:"value"` Remark string `json:"remark"` @@ -79,7 +79,7 @@ func ToEnvVOListFromModels(envs []models.EnvironmentVariable) []*EnvVO { // LoginLogVO 登录日志视图对象 type LoginLogVO struct { - ID uint `json:"id"` + ID string `json:"id"` Username string `json:"username"` IP string `json:"ip"` UserAgent string `json:"user_agent"` diff --git a/internal/models/vo/task_vo.go b/internal/models/vo/task_vo.go index a31e472..3ca9b55 100644 --- a/internal/models/vo/task_vo.go +++ b/internal/models/vo/task_vo.go @@ -7,7 +7,7 @@ import ( // TaskVO 任务视图对象 type TaskVO struct { - ID uint `json:"id"` + ID string `json:"id"` Name string `json:"name"` Command string `json:"command"` Tags string `json:"tags"` @@ -20,7 +20,7 @@ type TaskVO struct { CleanConfig string `json:"clean_config"` Envs string `json:"envs"` Languages []map[string]string `json:"languages"` - AgentID *uint `json:"agent_id"` + AgentID *string `json:"agent_id"` Enabled bool `json:"enabled"` RetryCount int `json:"retry_count"` RetryInterval int `json:"retry_interval"` @@ -85,11 +85,11 @@ func ToTaskVOListFromModels(tasks []models.Task) []*TaskVO { // TaskLogVO 任务历史视图对象 type TaskLogVO struct { - ID uint `json:"id"` - TaskID uint `json:"task_id"` + ID string `json:"id"` + TaskID string `json:"task_id"` TaskName string `json:"task_name"` TaskType string `json:"task_type"` - AgentID *uint `json:"agent_id"` + AgentID *string `json:"agent_id"` Command string `json:"command"` Error string `json:"error"` Status string `json:"status"` @@ -148,7 +148,7 @@ func ToTaskLogVOListFromModels(logs []models.TaskLog) []*TaskLogVO { // ExecutionResultVO 任务执行结果视图对象 type ExecutionResultVO struct { TaskID string `json:"task_id"` - LogID uint `json:"log_id,omitempty"` + LogID string `json:"log_id,omitempty"` Success bool `json:"success"` Status string `json:"status"` Output string `json:"output,omitempty"` diff --git a/internal/services/agent_service.go b/internal/services/agent_service.go index c657a14..f6a6799 100644 --- a/internal/services/agent_service.go +++ b/internal/services/agent_service.go @@ -14,6 +14,7 @@ import ( "github.com/engigu/baihu-panel/internal/logger" "github.com/engigu/baihu-panel/internal/models" "github.com/engigu/baihu-panel/internal/services/tasks" + "github.com/engigu/baihu-panel/internal/utils" "gorm.io/gorm" ) @@ -46,6 +47,7 @@ func (s *AgentService) CreateToken(remark string, maxUses int, expiresAt *time.T token := generateToken() agentToken := &models.AgentToken{ + ID: utils.GenerateID(), Token: token, Remark: remark, MaxUses: maxUses, @@ -69,8 +71,8 @@ func (s *AgentService) ListTokens() []models.AgentToken { } // DeleteToken 删除令牌 -func (s *AgentService) DeleteToken(id uint) error { - return database.DB.Delete(&models.AgentToken{}, id).Error +func (s *AgentService) DeleteToken(id string) error { + return database.DB.Where("id = ?", id).Delete(&models.AgentToken{}).Error } // ValidateToken 验证令牌 @@ -98,7 +100,7 @@ func (s *AgentService) ValidateToken(token string) (*models.AgentToken, error) { } // UseToken 使用令牌(增加使用计数) -func (s *AgentService) UseToken(id uint) { +func (s *AgentService) UseToken(id string) { database.DB.Model(&models.AgentToken{}).Where("id = ?", id).UpdateColumn("used_count", gorm.Expr("used_count + 1")) } @@ -126,7 +128,7 @@ func (s *AgentService) RegisterByToken(token string, machineID string, ip string "last_seen": now, }) s.UseToken(agentToken.ID) - logger.Infof("[Agent] Agent #%d 通过 machine_id 复用 (%s)", existing.ID, machineID[:8]+"...") + logger.Infof("[Agent] Agent #%s 通过 machine_id 复用 (%s)", existing.ID, machineID[:8]+"...") return &existing, false, nil } } @@ -134,6 +136,7 @@ func (s *AgentService) RegisterByToken(token string, machineID string, ip string // 创建 Agent,使用令牌作为认证 Token now := models.LocalTime(time.Now()) agent := &models.Agent{ + ID: utils.GenerateID(), Name: fmt.Sprintf("agent-%d", time.Now().Unix()), Token: token, MachineID: machineID, @@ -148,7 +151,7 @@ func (s *AgentService) RegisterByToken(token string, machineID string, ip string } s.UseToken(agentToken.ID) - logger.Infof("[Agent] Agent 通过令牌注册: #%d (%s)", agent.ID, ip) + logger.Infof("[Agent] Agent 通过令牌注册: #%s (%s)", agent.ID, ip) return agent, true, nil } @@ -173,6 +176,7 @@ func (s *AgentService) Register(req *models.AgentRegisterRequest, ip string) (*m // 创建新 Agent,使用令牌作为认证 Token now := models.LocalTime(time.Now()) agent := &models.Agent{ + ID: utils.GenerateID(), Name: req.Name, Token: req.Token, Hostname: req.Hostname, @@ -194,7 +198,7 @@ func (s *AgentService) Register(req *models.AgentRegisterRequest, ip string) (*m } // Update 更新 Agent -func (s *AgentService) Update(id uint, name, description string, enabled bool) error { +func (s *AgentService) Update(id string, name, description string, enabled bool) error { return database.DB.Model(&models.Agent{}).Where("id = ?", id).Updates(map[string]interface{}{ "name": name, "description": description, @@ -203,7 +207,7 @@ func (s *AgentService) Update(id uint, name, description string, enabled bool) e } // Delete 删除 Agent(物理删除) -func (s *AgentService) Delete(id uint) error { +func (s *AgentService) Delete(id string) error { // 检查是否有关联任务 var count int64 database.DB.Model(&models.Task{}).Where("agent_id = ?", id).Count(&count) @@ -211,13 +215,13 @@ func (s *AgentService) Delete(id uint) error { return &ServiceError{Message: "该 Agent 下还有关联任务,无法删除"} } - return database.DB.Unscoped().Delete(&models.Agent{}, id).Error + return database.DB.Unscoped().Where("id = ?", id).Delete(&models.Agent{}).Error } // GetByID 根据 ID 获取 Agent -func (s *AgentService) GetByID(id uint) *models.Agent { +func (s *AgentService) GetByID(id string) *models.Agent { var agent models.Agent - if err := database.DB.First(&agent, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).First(&agent).Error; err != nil { return nil } return &agent @@ -249,7 +253,7 @@ func (s *AgentService) List() []models.Agent { } // RegenerateToken 重新生成 Token - 已废弃,保留空实现避免路由错误 -func (s *AgentService) RegenerateToken(id uint) (string, error) { +func (s *AgentService) RegenerateToken(id string) (string, error) { return "", &ServiceError{Message: "此功能已禁用"} } @@ -301,7 +305,7 @@ func (s *AgentService) Heartbeat(token, ip, version, buildTime, hostname, osType } // GetTasks 获取 Agent 的任务列表 -func (s *AgentService) GetTasks(agentID uint) []models.AgentTask { +func (s *AgentService) GetTasks(agentID string) []models.AgentTask { var tasks []models.Task database.DB.Where("agent_id = ? AND enabled = ?", agentID, true).Find(&tasks) @@ -359,13 +363,13 @@ func (s *AgentService) ReportResult(result *models.AgentTaskResult) error { // 先尝试通知正在等待的 goroutine if agentWSManager.NotifyRemoteResult(result) { - logger.Infof("[Agent] 已通知正在等待任务 #%d 结果的 goroutine", result.TaskID) + logger.Infof("[Agent] 已通知正在等待任务 #%s 结果的 goroutine", result.TaskID) return nil } // 如果没有人在等待(例如服务重启后),则由本协程负责处理结果入库 // 如果没有人在等待(例如服务重启后),则由本协程负责处理结果入库(记录日志并清理) - logger.Infof("[Agent] 没有找到等待任务 #%d 结果的 goroutine,直接处理结果", result.TaskID) + logger.Infof("[Agent] 没有找到等待任务 #%s 结果的 goroutine,直接处理结果", result.TaskID) sendStatsService := NewSendStatsService() taskLogService := tasks.NewTaskLogService(sendStatsService) @@ -379,7 +383,7 @@ func (s *AgentService) ReportResult(result *models.AgentTaskResult) error { } // UpdateTaskDuration 更新任务耗时(心跳) -func (s *AgentService) UpdateTaskDuration(logID uint, duration int64) error { +func (s *AgentService) UpdateTaskDuration(logID string, duration int64) error { taskLogService := tasks.NewTaskLogService(nil) return taskLogService.UpdateTaskDuration(logID, duration) } @@ -504,12 +508,12 @@ func (s *AgentService) GetAgentBinary(osType, arch string) ([]byte, string, erro } // SetForceUpdate 设置强制更新标志 -func (s *AgentService) SetForceUpdate(id uint) error { +func (s *AgentService) SetForceUpdate(id string) error { return database.DB.Model(&models.Agent{}).Where("id = ?", id).Update("force_update", true).Error } // ClearForceUpdate 清除强制更新标志 -func (s *AgentService) ClearForceUpdate(id uint) error { +func (s *AgentService) ClearForceUpdate(id string) error { return database.DB.Model(&models.Agent{}).Where("id = ?", id).Update("force_update", false).Error } diff --git a/internal/services/agent_ws_service.go b/internal/services/agent_ws_service.go index de60132..5196ea9 100644 --- a/internal/services/agent_ws_service.go +++ b/internal/services/agent_ws_service.go @@ -15,11 +15,11 @@ import ( // AgentWSManager WebSocket 连接管理器 type AgentWSManager struct { - connections map[uint]*AgentConnection // Agent ID -> 连接对象 + connections map[string]*AgentConnection // Agent ID -> 连接对象 ipConnections map[string]int // IP -> 连接数 ipLastAttempt map[string]time.Time // IP -> 最后连接尝试时间 ipFailCount map[string]int // IP -> 连续失败次数 - remoteWaiters map[uint]chan *models.AgentTaskResult // 日志 ID -> 结果通道 + remoteWaiters map[string]chan *models.AgentTaskResult // 日志 ID -> 结果通道 mu sync.RWMutex } @@ -33,7 +33,7 @@ const ( // AgentConnection Agent WebSocket 连接 type AgentConnection struct { - AgentID uint + AgentID string IP string Conn *websocket.Conn Send chan []byte @@ -72,11 +72,11 @@ var agentWSOnce sync.Once func GetAgentWSManager() *AgentWSManager { agentWSOnce.Do(func() { agentWSManager = &AgentWSManager{ - connections: make(map[uint]*AgentConnection), + connections: make(map[string]*AgentConnection), ipConnections: make(map[string]int), ipLastAttempt: make(map[string]time.Time), ipFailCount: make(map[string]int), - remoteWaiters: make(map[uint]chan *models.AgentTaskResult), + remoteWaiters: make(map[string]chan *models.AgentTaskResult), } go agentWSManager.cleanupLoop() }) @@ -137,7 +137,7 @@ func (m *AgentWSManager) RecordConnectSuccess(ip string) { } // Register 注册连接 -func (m *AgentWSManager) Register(agentID uint, conn *websocket.Conn, ip string) *AgentConnection { +func (m *AgentWSManager) Register(agentID string, conn *websocket.Conn, ip string) *AgentConnection { m.mu.Lock() defer m.mu.Unlock() @@ -164,12 +164,12 @@ func (m *AgentWSManager) Register(agentID uint, conn *websocket.Conn, ip string) // 增加 IP 连接计数 m.ipConnections[ip]++ - logger.Infof("[AgentWS] Agent #%d 已连接 (%s)", agentID, ip) + logger.Infof("[AgentWS] Agent #%s 已连接 (%s)", agentID, ip) return ac } // Unregister 注销连接(只注销指定的连接实例) -func (m *AgentWSManager) Unregister(agentID uint, ac *AgentConnection) { +func (m *AgentWSManager) Unregister(agentID string, ac *AgentConnection) { m.mu.Lock() defer m.mu.Unlock() @@ -183,19 +183,19 @@ func (m *AgentWSManager) Unregister(agentID uint, ac *AgentConnection) { } conn.Close() delete(m.connections, agentID) - logger.Infof("[AgentWS] Agent #%d 已断开", agentID) + logger.Infof("[AgentWS] Agent #%s 已断开", agentID) } } // GetConnection 获取连接 -func (m *AgentWSManager) GetConnection(agentID uint) *AgentConnection { +func (m *AgentWSManager) GetConnection(agentID string) *AgentConnection { m.mu.RLock() defer m.mu.RUnlock() return m.connections[agentID] } // SendToAgent 发送消息给指定 Agent -func (m *AgentWSManager) SendToAgent(agentID uint, msgType string, data interface{}) error { +func (m *AgentWSManager) SendToAgent(agentID string, msgType string, data interface{}) error { conn := m.GetConnection(agentID) if conn == nil { return nil // Agent 不在线 @@ -214,7 +214,7 @@ func (m *AgentWSManager) SendToAgent(agentID uint, msgType string, data interfac } // BroadcastTasks 广播任务更新给指定 Agent -func (m *AgentWSManager) BroadcastTasks(agentID uint) { +func (m *AgentWSManager) BroadcastTasks(agentID string) { agentService := NewAgentService() tasks := agentService.GetTasks(agentID) m.SendToAgent(agentID, WSTypeTasks, map[string]interface{}{ @@ -223,7 +223,7 @@ func (m *AgentWSManager) BroadcastTasks(agentID uint) { } // RegisterRemoteWaiter 注册远程任务结果等待者 -func (m *AgentWSManager) RegisterRemoteWaiter(logID uint) chan *models.AgentTaskResult { +func (m *AgentWSManager) RegisterRemoteWaiter(logID string) chan *models.AgentTaskResult { m.mu.Lock() defer m.mu.Unlock() ch := make(chan *models.AgentTaskResult, 1) @@ -232,7 +232,7 @@ func (m *AgentWSManager) RegisterRemoteWaiter(logID uint) chan *models.AgentTask } // UnregisterRemoteWaiter 注销远程任务结果等待者 -func (m *AgentWSManager) UnregisterRemoteWaiter(logID uint) { +func (m *AgentWSManager) UnregisterRemoteWaiter(logID string) { m.mu.Lock() defer m.mu.Unlock() delete(m.remoteWaiters, logID) @@ -293,7 +293,7 @@ func (m *AgentWSManager) cleanupLoop() { delete(m.connections, agentID) // 更新数据库状态 database.DB.Model(&models.Agent{}).Where("id = ?", agentID).Update("status", constant.AgentStatusOffline) - logger.Infof("[AgentWS] Agent #%d 心跳超时,已断开", agentID) + logger.Infof("[AgentWS] Agent #%s 心跳超时,已断开", agentID) } } diff --git a/internal/services/backup_service.go b/internal/services/backup_service.go index be8c81e..7b6893c 100644 --- a/internal/services/backup_service.go +++ b/internal/services/backup_service.go @@ -158,7 +158,7 @@ func (s *BackupService) CreateBackup() (string, error) { // 写入元数据信息 sysInfo := map[string]interface{}{ - "version": "v2", + "version": "v3", "ts": time.Now().Format("2006-01-02 15:04:05"), } sysFile, err := zipWriter.Create("__sys__.json") @@ -197,6 +197,24 @@ func (s *BackupService) Restore(zipPath string) error { fileMap[f.Name] = f } + // 校验版本 + if f, ok := fileMap["__sys__.json"]; ok { + rc, err := f.Open() + if err == nil { + var sysInfo map[string]interface{} + json.NewDecoder(rc).Decode(&sysInfo) + rc.Close() + if v, ok := sysInfo["version"]; ok { + vs, _ := v.(string) + if vs < "v3" { + return fmt.Errorf("只能数据随版本升级上来,当前备份版本为 %s,限制 v3 以下的不能导入", vs) + } + } + } + // } else { + // return fmt.Errorf("非法备份包:缺失版本标记") + } + // 开启全局事务 return database.DB.Transaction(func(tx *gorm.DB) error { // 1. 清空现有数据(物理删除) diff --git a/internal/services/dependency_service.go b/internal/services/dependency_service.go index fc4e164..6820437 100644 --- a/internal/services/dependency_service.go +++ b/internal/services/dependency_service.go @@ -6,6 +6,7 @@ import ( "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" "github.com/engigu/baihu-panel/internal/services/deps" + "github.com/engigu/baihu-panel/internal/utils" ) type DependencyService struct{} @@ -36,12 +37,15 @@ func (s *DependencyService) Create(dep *models.Dependency) error { if err == nil { return errors.New("依赖已存在") } + if dep.ID == "" { + dep.ID = utils.GenerateID() + } return database.DB.Create(dep).Error } // Delete 删除依赖记录 -func (s *DependencyService) Delete(id int) error { - return database.DB.Delete(&models.Dependency{}, id).Error +func (s *DependencyService) Delete(id string) error { + return database.DB.Where("id = ?", id).Delete(&models.Dependency{}).Error } // Install 安装依赖 diff --git a/internal/services/env_service.go b/internal/services/env_service.go index a2d6ce3..f510957 100644 --- a/internal/services/env_service.go +++ b/internal/services/env_service.go @@ -1,11 +1,11 @@ package services import ( - "strconv" "strings" "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" + "github.com/engigu/baihu-panel/internal/utils" ) type EnvService struct{} @@ -14,42 +14,26 @@ func NewEnvService() *EnvService { return &EnvService{} } -func (es *EnvService) CreateEnvVar(name, value, remark string, hidden bool, userID int) *models.EnvironmentVariable { +func (es *EnvService) CreateEnvVar(name, value, remark string, hidden bool, userID string) *models.EnvironmentVariable { env := &models.EnvironmentVariable{ + ID: utils.GenerateID(), Name: name, Value: value, Remark: remark, Hidden: hidden, - UserID: uint(userID), - } - data := map[string]interface{}{ - "name": name, - "value": value, - "remark": remark, - "hidden": hidden, - "user_id": userID, - } - database.DB.Model(&models.EnvironmentVariable{}).Create(data) - - // 将自动生成的 ID 赋值回对象以便返回 - if id, ok := data["id"].(uint); ok { - env.ID = id - } else if id, ok := data["id"].(int64); ok { - env.ID = uint(id) - } else { - // 如果 ID 没有自动回填到 map,尝试通过刚才的数据查出来 - database.DB.Where("name = ? AND user_id = ?", name, userID).Order("id DESC").First(env) + UserID: userID, } + database.DB.Create(env) return env } -func (es *EnvService) GetEnvVarsByUserID(userID int) []models.EnvironmentVariable { +func (es *EnvService) GetEnvVarsByUserID(userID string) []models.EnvironmentVariable { var envs []models.EnvironmentVariable database.DB.Where("user_id = ?", userID).Find(&envs) return envs } -func (es *EnvService) GetEnvVarsWithPagination(userID int, name string, page, pageSize int) ([]models.EnvironmentVariable, int64) { +func (es *EnvService) GetEnvVarsWithPagination(userID string, name string, page, pageSize int) ([]models.EnvironmentVariable, int64) { var envs []models.EnvironmentVariable var total int64 @@ -63,17 +47,17 @@ func (es *EnvService) GetEnvVarsWithPagination(userID int, name string, page, pa return envs, total } -func (es *EnvService) GetEnvVarByID(id int) *models.EnvironmentVariable { +func (es *EnvService) GetEnvVarByID(id string) *models.EnvironmentVariable { var env models.EnvironmentVariable - if err := database.DB.First(&env, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).First(&env).Error; err != nil { return nil } return &env } -func (es *EnvService) UpdateEnvVar(id int, name, value, remark string, hidden bool) *models.EnvironmentVariable { +func (es *EnvService) UpdateEnvVar(id string, name, value, remark string, hidden bool) *models.EnvironmentVariable { var env models.EnvironmentVariable - if err := database.DB.First(&env, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).First(&env).Error; err != nil { return nil } env.Name = name @@ -84,8 +68,8 @@ func (es *EnvService) UpdateEnvVar(id int, name, value, remark string, hidden bo return &env } -func (es *EnvService) DeleteEnvVar(id int) bool { - result := database.DB.Delete(&models.EnvironmentVariable{}, id) +func (es *EnvService) DeleteEnvVar(id string) bool { + result := database.DB.Where("id = ?", id).Delete(&models.EnvironmentVariable{}) return result.RowsAffected > 0 } @@ -107,12 +91,12 @@ func (es *EnvService) GetEnvVarsByIDs(envIDs string) []string { } // splitEnvIDs 解析逗号分隔的ID字符串 -func splitEnvIDs(envIDs string) []int { - var ids []int +func splitEnvIDs(envIDs string) []string { + var ids []string for _, s := range strings.Split(envIDs, ",") { s = strings.TrimSpace(s) - if id, err := strconv.Atoi(s); err == nil { - ids = append(ids, id) + if s != "" { + ids = append(ids, s) } } return ids diff --git a/internal/services/login_log_service.go b/internal/services/login_log_service.go index 701f278..d50979f 100644 --- a/internal/services/login_log_service.go +++ b/internal/services/login_log_service.go @@ -3,6 +3,7 @@ package services import ( "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" + "github.com/engigu/baihu-panel/internal/utils" ) type LoginLogService struct{} @@ -14,6 +15,7 @@ func NewLoginLogService() *LoginLogService { // Create 创建登录日志 func (s *LoginLogService) Create(username, ip, userAgent, status, message string) error { log := &models.LoginLog{ + ID: utils.GenerateID(), Username: username, IP: ip, UserAgent: userAgent, diff --git a/internal/services/migration_v3.go b/internal/services/migration_v3.go new file mode 100644 index 0000000..de25994 --- /dev/null +++ b/internal/services/migration_v3.go @@ -0,0 +1,326 @@ +package services + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/engigu/baihu-panel/internal/database" + "github.com/engigu/baihu-panel/internal/logger" + "github.com/engigu/baihu-panel/internal/models" + "github.com/engigu/baihu-panel/internal/utils" + "gorm.io/gorm" +) + +// MigrationTable 定义迁移配置 +type MigrationTable struct { + Model any + EntityName string + FKs map[string]string // 单个外键映射: 字段名 -> 实体名 + MultiFKs map[string]string // 复合外键映射 (如逗号分隔): 字段名 -> 实体名 +} + +func getMigrationTables() []MigrationTable { + return []MigrationTable{ + {&models.User{}, "users", nil, nil}, + {&models.Agent{}, "agents", nil, nil}, + {&models.AgentToken{}, "tokens", nil, nil}, + {&models.EnvironmentVariable{}, "envs", map[string]string{"UserID": "users"}, nil}, + {&models.Task{}, "tasks", map[string]string{"AgentID": "agents"}, map[string]string{"Envs": "envs"}}, + {&models.TaskLog{}, "task_logs", map[string]string{"TaskID": "tasks", "AgentID": "agents"}, nil}, + {&models.Script{}, "scripts", map[string]string{"UserID": "users"}, nil}, + {&models.Setting{}, "settings", nil, nil}, + {&models.SendStats{}, "send_stats", map[string]string{"TaskID": "tasks"}, nil}, + {&models.LoginLog{}, "login_logs", nil, nil}, + {&models.Language{}, "languages", nil, nil}, + {&models.Dependency{}, "deps", nil, nil}, + } +} + +func getTableName(db *gorm.DB, model any) string { + stmt := &gorm.Statement{DB: db} + if err := stmt.Parse(model); err != nil { + return "" + } + return stmt.Schema.Table +} + +func isTableStringID(db *gorm.DB, model any) bool { + if !db.Migrator().HasTable(model) { + return true + } + columnTypes, err := db.Migrator().ColumnTypes(model) + if err != nil { + return true + } + for _, ct := range columnTypes { + if strings.ToLower(ct.Name()) == "id" { + typeName := strings.ToLower(ct.DatabaseTypeName()) + return strings.Contains(typeName, "char") || strings.Contains(typeName, "text") || strings.Contains(typeName, "string") + } + } + return false +} + +func getValFromMap(m map[string]interface{}, key string) (interface{}, bool) { + lowerKey := strings.ToLower(key) + for k, v := range m { + if strings.ToLower(k) == lowerKey { + return v, true + } + } + return nil, false +} + +func RunMigrationV3() error { + db := database.DB + if db == nil { + return fmt.Errorf("数据库未初始化") + } + + // 0. 检查迁移标记,防止重复迁移逻辑被误判触发 + if db.Migrator().HasTable(&models.Setting{}) { + var migrationFlag models.Setting + err := db.Where("section = ? AND `key` = ?", "system", "migration_v3_success").First(&migrationFlag).Error + if err == nil && migrationFlag.Value == "true" { + // 如果已经是字符串 ID 模式,双重确认 + return nil + } + } + + tables := getMigrationTables() + needMigration := false + for _, t := range tables { + if !isTableStringID(db, t.Model) { + needMigration = true + logger.Infof("[MigrationV3] 表 [%s] ID 为数字,需迁移", getTableName(db, t.Model)) + break + } + } + + if !needMigration { + // 没有数字 ID 表了,但也可能还没打标(比如之前手动修过表),此时补一个标 + return markMigrationSuccess(db) + } + + // 1. 备份过程 + backupDir := "./data" + os.MkdirAll(backupDir, 0755) + // 如果已经有还原过的记录,为了防止反复循环,我们可以检查还原标记 + backups, _ := filepath.Glob(filepath.Join(backupDir, "migration_v3_backup_*.zip")) + if len(backups) == 0 { + logger.Infof("[MigrationV3] 执行关键备份...") + backupService := NewBackupService() + zipPath, err := backupService.CreateBackup() + if err != nil { + return fmt.Errorf("自动备份失败,流程终止: %v", err) + } + newPath := filepath.Join(backupDir, fmt.Sprintf("migration_v3_backup_%s.zip", filepath.Base(zipPath))) + os.Rename(zipPath, newPath) + logger.Infof("[MigrationV3] 备份成功: %s", newPath) + } + + mappings := make(map[string]map[uint]string) + err := db.Transaction(func(tx *gorm.DB) error { + return performHardMigration(tx, mappings) + }) + + if err != nil { + return err + } + + // 3. 标记成功 + return markMigrationSuccess(db) +} + +func markMigrationSuccess(db *gorm.DB) error { + if !db.Migrator().HasTable(&models.Setting{}) { + return nil + } + var flag models.Setting + err := db.Where("section = ? AND `key` = ?", "system", "migration_v3_success").First(&flag).Error + if err != nil { + // 创建或更新 + flag = models.Setting{ + ID: utils.GenerateID(), + Section: "system", + Key: "migration_v3_success", + Value: "true", + } + return db.Create(&flag).Error + } + return db.Model(&flag).Update("value", "true").Error +} + +func performHardMigration(tx *gorm.DB, mappings map[string]map[uint]string) error { + allTables := getMigrationTables() + + // --------------------------------------------------------- + // 第一阶段:全量构建 ID 映射映射表 (Pass 1) + // --------------------------------------------------------- + for _, t := range allTables { + actualName := getTableName(tx, t.Model) + if actualName == "" || !tx.Migrator().HasTable(actualName) { + continue + } + mappings[t.EntityName] = make(map[uint]string) + oldTableName := actualName + "_v2_bak" + + // 如果还没有备份表,说明这是第一次处理该表,先重命名 + if !tx.Migrator().HasTable(oldTableName) { + if isTableStringID(tx, t.Model) { + continue // 已经是字符串 ID 且无备份,跳过 + } + if err := tx.Migrator().RenameTable(actualName, oldTableName); err != nil { + return fmt.Errorf("重命名表 %s 失败: %v", actualName, err) + } + } + + // 预先为该表所有记录生成新的 xid + var rows []map[string]interface{} + tx.Table(oldTableName).Select("id").Find(&rows) + for _, row := range rows { + if val, ok := getValFromMap(row, "id"); ok { + uid := parseUint(val) + if uid > 0 { + mappings[t.EntityName][uid] = utils.GenerateID() + } + } + } + logger.Infof("[MigrationV3] Pass 1: 构建关键表 %s 的 ID 映射, 共 %d 条", actualName, len(mappings[t.EntityName])) + } + + // --------------------------------------------------------- + // 第二、三阶段:正式转换数据并处理关联字段 (Pass 2 & 3) + // --------------------------------------------------------- + for _, t := range allTables { + actualName := getTableName(tx, t.Model) + oldTableName := actualName + "_v2_bak" + + if !tx.Migrator().HasTable(oldTableName) { + // 虽然可能已经改过格式,但为了安全还是 AutoMigrate 一下 + tx.AutoMigrate(t.Model) + continue + } + + logger.Infof("[MigrationV3] Pass 2&3: 正在转换数据并修复关联: %s", actualName) + tx.AutoMigrate(t.Model) + + // 获取新表的有效列名(小写) + columnTypes, _ := tx.Migrator().ColumnTypes(t.Model) + validColumns := make(map[string]bool) + for _, ct := range columnTypes { + validColumns[strings.ToLower(ct.Name())] = true + } + + var oldData []map[string]interface{} + if err := tx.Table(oldTableName).Find(&oldData).Error; err != nil { + return err + } + + for _, row := range oldData { + // 1. 处理主键 ID + if val, ok := getValFromMap(row, "id"); ok { + uid := parseUint(val) + if nid, exists := mappings[t.EntityName][uid]; exists { + row["id"] = nid + } else { + row["id"] = utils.GenerateID() + } + } + + // 2. 处理单外键关联 (Phase 3) + for field, parentEntity := range t.FKs { + columnName := getColumnName(field) + if val, ok := getValFromMap(row, columnName); ok && val != nil { + ufk := parseUint(val) + if ufk > 0 { + if nid, exists := mappings[parentEntity][ufk]; exists { + row[columnName] = nid + } else { + row[columnName] = nil + } + } else { + row[columnName] = nil + } + } + } + + // 3. 处理复合多外键字段 (envs) + for field, parentEntity := range t.MultiFKs { + columnName := getColumnName(field) + if val, ok := getValFromMap(row, columnName); ok && val != nil { + if strVal, ok := val.(string); ok && strVal != "" { + row[columnName] = transformMultiIDs(strVal, parentEntity, mappings) + } + } + } + + // 4. 过滤不存在的列并插入 + filteredRow := make(map[string]interface{}) + for k, v := range row { + if validColumns[strings.ToLower(k)] { + filteredRow[k] = v + } + } + if err := tx.Table(actualName).Create(filteredRow).Error; err != nil { + return err + } + } + + // 迁移完成,清理备份表 + tx.Migrator().DropTable(oldTableName) + } + return nil +} + +// 辅助函数:解析各种数字 ID +func parseUint(val interface{}) uint { + if val == nil { return 0 } + switch v := val.(type) { + case uint: return v + case int64: return uint(v) + case int: return uint(v) + case uint64: return uint(v) + case float64: return uint(v) + case string: + var u uint + fmt.Sscanf(v, "%d", &u) + return u + } + return 0 +} + +// 辅助函数:字段名转列名 +func getColumnName(field string) string { + switch field { + case "AgentID": return "agent_id" + case "TaskID": return "task_id" + case "UserID": return "user_id" + case "LogID": return "log_id" + case "Envs": return "envs" + default: return strings.ToLower(field) + } +} + +// 辅助函数:处理逗号分隔的 ID 列表 +func transformMultiIDs(oldStr string, parentEntity string, mappings map[string]map[uint]string) string { + parts := strings.Split(oldStr, ",") + var result []string + for _, p := range parts { + p = strings.TrimSpace(p) + if p == "" { continue } + if len(p) == 20 && !utils.IsNumeric(p) { + result = append(result, p) // 已经是 xid,保留 + continue + } + uid := parseUint(p) + if uid > 0 { + if nid, exists := mappings[parentEntity][uid]; exists { + result = append(result, nid) + } + } + } + return strings.Join(result, ",") +} diff --git a/internal/services/mise_service.go b/internal/services/mise_service.go index 463fe55..84719f4 100644 --- a/internal/services/mise_service.go +++ b/internal/services/mise_service.go @@ -283,7 +283,7 @@ func (s *MiseService) syncToDB(languages []MiseLanguage) { return } - var currentIds []uint + var currentIds []string for _, lang := range languages { var model models.Language // 以 plugin 和 version 作为联合唯一标识(业务逻辑上) @@ -308,6 +308,7 @@ func (s *MiseService) syncToDB(languages []MiseLanguage) { if err != nil { // 如果不存在,则创建 newLang := models.Language{ + ID: utils.GenerateID(), Plugin: lang.Plugin, Version: lang.Version, InstallPath: lang.InstallPath, diff --git a/internal/services/script_service.go b/internal/services/script_service.go index ca1921a..8f4e652 100644 --- a/internal/services/script_service.go +++ b/internal/services/script_service.go @@ -3,6 +3,7 @@ package services import ( "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" + "github.com/engigu/baihu-panel/internal/utils" ) type ScriptService struct{} @@ -11,33 +12,34 @@ func NewScriptService() *ScriptService { return &ScriptService{} } -func (ss *ScriptService) CreateScript(name, content string, userID int) *models.Script { +func (ss *ScriptService) CreateScript(name, content string, userID string) *models.Script { script := &models.Script{ + ID: utils.GenerateID(), Name: name, Content: content, - UserID: uint(userID), + UserID: userID, } database.DB.Create(script) return script } -func (ss *ScriptService) GetScriptsByUserID(userID int) []models.Script { +func (ss *ScriptService) GetScriptsByUserID(userID string) []models.Script { var scripts []models.Script database.DB.Where("user_id = ?", userID).Find(&scripts) return scripts } -func (ss *ScriptService) GetScriptByID(id int) *models.Script { +func (ss *ScriptService) GetScriptByID(id string) *models.Script { var script models.Script - if err := database.DB.First(&script, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).First(&script).Error; err != nil { return nil } return &script } -func (ss *ScriptService) UpdateScript(id int, name, content string) *models.Script { +func (ss *ScriptService) UpdateScript(id string, name, content string) *models.Script { var script models.Script - if err := database.DB.First(&script, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).First(&script).Error; err != nil { return nil } script.Name = name @@ -46,7 +48,7 @@ func (ss *ScriptService) UpdateScript(id int, name, content string) *models.Scri return &script } -func (ss *ScriptService) DeleteScript(id int) bool { - result := database.DB.Delete(&models.Script{}, id) +func (ss *ScriptService) DeleteScript(id string) bool { + result := database.DB.Where("id = ?", id).Delete(&models.Script{}) return result.RowsAffected > 0 } diff --git a/internal/services/send_stats_service.go b/internal/services/send_stats_service.go index c132e7b..e10abba 100644 --- a/internal/services/send_stats_service.go +++ b/internal/services/send_stats_service.go @@ -6,6 +6,7 @@ import ( "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" "github.com/engigu/baihu-panel/internal/systime" + "github.com/engigu/baihu-panel/internal/utils" ) type SendStatsService struct{} @@ -15,7 +16,7 @@ func NewSendStatsService() *SendStatsService { } // IncrementStats 增加任务执行统计 -func (s *SendStatsService) IncrementStats(taskID uint, status string) error { +func (s *SendStatsService) IncrementStats(taskID string, status string) error { day := systime.FormatDate(time.Now()) var stats models.SendStats @@ -24,6 +25,7 @@ func (s *SendStatsService) IncrementStats(taskID uint, status string) error { if result.Error != nil { // 不存在则创建 stats = models.SendStats{ + ID: utils.GenerateID(), TaskID: taskID, Day: day, Status: status, @@ -37,7 +39,7 @@ func (s *SendStatsService) IncrementStats(taskID uint, status string) error { } // GetStatsByTaskID 获取任务的统计数据 -func (s *SendStatsService) GetStatsByTaskID(taskID uint) []models.SendStats { +func (s *SendStatsService) GetStatsByTaskID(taskID string) []models.SendStats { var stats []models.SendStats database.DB.Where("task_id = ?", taskID).Order("day DESC").Find(&stats) return stats diff --git a/internal/services/settings_service.go b/internal/services/settings_service.go index 7c07ab9..9ec8cad 100644 --- a/internal/services/settings_service.go +++ b/internal/services/settings_service.go @@ -21,7 +21,12 @@ func (s *SettingsService) InitSettings() error { var count int64 database.DB.Model(&models.Setting{}).Where("section = ? AND `key` = ?", section, key).Count(&count) if count == 0 { - if err := database.DB.Create(&models.Setting{Section: section, Key: key, Value: value}).Error; err != nil { + if err := database.DB.Create(&models.Setting{ + ID: utils.GenerateID(), + Section: section, + Key: key, + Value: value, + }).Error; err != nil { return err } } @@ -38,7 +43,12 @@ func (s *SettingsService) InitSettings() error { } else { secretValue = utils.RandomString(32) } - if err := database.DB.Create(&models.Setting{Section: constant.SectionSecurity, Key: constant.KeySecret, Value: secretValue}).Error; err != nil { + if err := database.DB.Create(&models.Setting{ + ID: utils.GenerateID(), + Section: constant.SectionSecurity, + Key: constant.KeySecret, + Value: secretValue, + }).Error; err != nil { return err } } else { @@ -69,7 +79,12 @@ func (s *SettingsService) Get(section, key string) string { func (s *SettingsService) Set(section, key, value string) error { var setting models.Setting if database.DB.Where("section = ? AND `key` = ?", section, key).First(&setting).Error != nil { - return database.DB.Create(&models.Setting{Section: section, Key: key, Value: value}).Error + return database.DB.Create(&models.Setting{ + ID: utils.GenerateID(), + Section: section, + Key: key, + Value: value, + }).Error } return database.DB.Model(&setting).Update("value", value).Error } diff --git a/internal/services/tasks/executor_service.go b/internal/services/tasks/executor_service.go index d2b5a8d..0d7321d 100644 --- a/internal/services/tasks/executor_service.go +++ b/internal/services/tasks/executor_service.go @@ -24,9 +24,9 @@ import ( // AgentWSManager 接口定义(避免循环依赖) type AgentWSManager interface { - RegisterRemoteWaiter(logID uint) chan *models.AgentTaskResult - UnregisterRemoteWaiter(logID uint) - SendToAgent(agentID uint, msgType string, data interface{}) error + RegisterRemoteWaiter(logID string) chan *models.AgentTaskResult + UnregisterRemoteWaiter(logID string) + SendToAgent(agentID string, msgType string, data interface{}) error } // SettingsService 接口定义(避免循环依赖) @@ -115,10 +115,9 @@ func (h *ServerSchedulerHandler) OnTaskScheduled(req *executor.ExecutionRequest) } func (h *ServerSchedulerHandler) OnTaskExecuting(req *executor.ExecutionRequest) (io.Writer, io.Writer, error) { - var taskID uint - fmt.Sscanf(req.TaskID, "%d", &taskID) + taskID := req.TaskID - task := h.es.taskService.GetTaskByID(int(taskID)) + task := h.es.taskService.GetTaskByID(taskID) // 系统任务(无 taskID)不记录数据库日志,直接返回空写入器 if task == nil { return nil, nil, nil @@ -168,7 +167,7 @@ func (h *ServerSchedulerHandler) OnTaskExecuting(req *executor.ExecutionRequest) } func (h *ServerSchedulerHandler) OnTaskHeartbeat(req *executor.ExecutionRequest, duration int64) { - if req.LogID > 0 { + if req.LogID != "" { h.es.taskLogService.UpdateTaskDuration(req.LogID, duration) } @@ -184,14 +183,13 @@ func (h *ServerSchedulerHandler) OnTaskStarted(req *executor.ExecutionRequest) { } func (h *ServerSchedulerHandler) OnTaskCompleted(req *executor.ExecutionRequest, result *executor.ExecutionResult) { - if req.LogID == 0 { + if req.LogID == "" { return } - var taskID uint - fmt.Sscanf(req.TaskID, "%d", &taskID) + taskID := req.TaskID - task := h.es.taskService.GetTaskByID(int(taskID)) + task := h.es.taskService.GetTaskByID(taskID) if task == nil { return } @@ -204,7 +202,7 @@ func (h *ServerSchedulerHandler) OnTaskCompleted(req *executor.ExecutionRequest, var err error output, err = tl.CompressAndCleanup() if err != nil { - logger.Errorf("[Executor] 压缩任务 #%d 日志失败: %v", task.ID, err) + logger.Errorf("[Executor] 压缩任务 #%s 日志失败: %v", task.ID, err) output = "[System Error] 日志处理失败: " + err.Error() } } else { @@ -230,7 +228,7 @@ func (h *ServerSchedulerHandler) OnTaskCompleted(req *executor.ExecutionRequest, } // 如果有 AgentID,也记录下来 - if task.AgentID != nil && *task.AgentID > 0 { + if task.AgentID != nil && *task.AgentID != "" { agentID := *task.AgentID taskLog.AgentID = &agentID } @@ -251,12 +249,11 @@ func (h *ServerSchedulerHandler) OnTaskCompleted(req *executor.ExecutionRequest, } func (h *ServerSchedulerHandler) OnTaskFailed(req *executor.ExecutionRequest, err error) { - if req.LogID == 0 { + if req.LogID == "" { return } - var taskID uint - fmt.Sscanf(req.TaskID, "%d", &taskID) + taskID := req.TaskID // 移除运行记录 if req.Metadata.GoID != 0 { @@ -288,8 +285,8 @@ func (h *ServerSchedulerHandler) OnTaskFailed(req *executor.ExecutionRequest, er } // 补充 AgentID - task := h.es.taskService.GetTaskByID(int(taskID)) - if task != nil && task.AgentID != nil && *task.AgentID > 0 { + task := h.es.taskService.GetTaskByID(taskID) + if task != nil && task.AgentID != nil && *task.AgentID != "" { agentID := *task.AgentID taskLog.AgentID = &agentID } @@ -321,10 +318,10 @@ func (es *ExecutorService) HandleTaskRetry(task *models.Task, req *executor.Exec if retryIndex < task.RetryCount { retryIndex++ - logger.Infof("[Executor] 任务 #%d 执行失败/出错,将在 %d 秒后进行第 %d/%d 次重试...", task.ID, task.RetryInterval, retryIndex, task.RetryCount) + logger.Infof("[Executor] 任务 #%s 执行失败/出错,将在 %d 秒后进行第 %d/%d 次重试...", task.ID, task.RetryInterval, retryIndex, task.RetryCount) es.scheduler.EnqueueDelayed(time.Duration(task.RetryInterval)*time.Second, func() *executor.ExecutionRequest { - latestTask := es.taskService.GetTaskByID(int(task.ID)) + latestTask := es.taskService.GetTaskByID(task.ID) if latestTask == nil || !latestTask.Enabled { return nil } @@ -350,8 +347,7 @@ func (es *ExecutorService) HandleTaskRetry(task *models.Task, req *executor.Exec } func (h *ServerSchedulerHandler) OnCronNextRun(req *executor.ExecutionRequest, nextRun time.Time) { - var taskID uint - fmt.Sscanf(req.TaskID, "%d", &taskID) + taskID := req.TaskID // 更新数据库中的下次运行时间 database.DB.Model(&models.Task{}).Where("id = ?", taskID).Update("next_run", nextRun) } @@ -359,19 +355,19 @@ func (h *ServerSchedulerHandler) OnCronNextRun(req *executor.ExecutionRequest, n // LocalTaskHooks 本地任务钩子适配器 type LocalTaskHooks struct { es *ExecutorService - logID uint + logID string } -func (h *LocalTaskHooks) PreExecute(ctx context.Context, req executor.Request) (uint, error) { +func (h *LocalTaskHooks) PreExecute(ctx context.Context, req executor.Request) (string, error) { return h.logID, nil } -func (h *LocalTaskHooks) PostExecute(ctx context.Context, logID uint, result *executor.Result) error { +func (h *LocalTaskHooks) PostExecute(ctx context.Context, logID string, result *executor.Result) error { return nil } -func (h *LocalTaskHooks) OnHeartbeat(ctx context.Context, logID uint, duration int64) error { - if logID > 0 { +func (h *LocalTaskHooks) OnHeartbeat(ctx context.Context, logID string, duration int64) error { + if logID != "" { return h.es.taskLogService.UpdateTaskDuration(logID, duration) } return nil @@ -379,10 +375,9 @@ func (h *LocalTaskHooks) OnHeartbeat(ctx context.Context, logID uint, duration i // ExecuteDispatcher 实现任务分发逻辑 func (es *ExecutorService) ExecuteDispatcher(ctx context.Context, req *executor.ExecutionRequest, stdout, stderr io.Writer) (*executor.Result, error) { - var taskID uint - fmt.Sscanf(req.TaskID, "%d", &taskID) + taskID := req.TaskID - task := es.taskService.GetTaskByID(int(taskID)) + task := es.taskService.GetTaskByID(taskID) // 系统任务(无 taskID)直接本地执行 if task == nil { return executor.Execute(ctx, executor.Request{ @@ -409,7 +404,7 @@ func (es *ExecutorService) ExecuteDispatcher(ctx context.Context, req *executor. } // 远程任务 - if task.AgentID != nil && *task.AgentID > 0 { + if task.AgentID != nil && *task.AgentID != "" { return es.ExecuteRemoteForScheduler(task, req.LogID) } @@ -467,8 +462,8 @@ func (es *ExecutorService) AddCronTask(task *models.Task) error { } // RemoveCronTask 移除计划任务 -func (es *ExecutorService) RemoveCronTask(taskID uint) { - es.cronManager.RemoveTask(fmt.Sprintf("%d", taskID)) +func (es *ExecutorService) RemoveCronTask(taskID string) { + es.cronManager.RemoveTask(taskID) } // ValidateCron 验证 Cron 表达式 @@ -494,10 +489,10 @@ func (es *ExecutorService) loadCronTasks() { go func(t models.Task) { // 延迟一点时间再触发,确保系统完全启动 time.Sleep(3 * time.Second) - logger.Infof("[Executor] 触发开机服务启动任务 #%d: %s", t.ID, t.Name) - es.ExecuteTask(int(t.ID), nil) + logger.Infof("[Executor] 触发开机服务启动任务 #%s: %s", t.ID, t.Name) + es.ExecuteTask(t.ID, nil) }(task) - } else if task.TriggerType == constant.TriggerTypeCron && task.Schedule != "" && (task.AgentID == nil || *task.AgentID == 0) { + } else if task.TriggerType == constant.TriggerTypeCron && task.Schedule != "" && (task.AgentID == nil || *task.AgentID == "") { // 只调度本地任务(agent_id 为空或 0)的定时任务 err := es.cronManager.AddTask(&task) if err != nil { @@ -519,11 +514,11 @@ func (es *ExecutorService) Reload() { } // ExecuteTask executes a task by ID(同步执行,供 API 调用) -func (es *ExecutorService) ExecuteTask(taskID int, extraEnvs []string) *executor.ExecutionResult { +func (es *ExecutorService) ExecuteTask(taskID string, extraEnvs []string) *executor.ExecutionResult { task := es.taskService.GetTaskByID(taskID) if task == nil { return &executor.ExecutionResult{ - TaskID: fmt.Sprintf("%d", taskID), + TaskID: taskID, Success: false, Error: "任务不存在", StartTime: time.Now(), @@ -532,9 +527,9 @@ func (es *ExecutorService) ExecuteTask(taskID int, extraEnvs []string) *executor } // 1. 检查并发 - if err := es.CheckConcurrency(uint(taskID)); err != nil { + if err := es.CheckConcurrency(taskID); err != nil { return &executor.ExecutionResult{ - TaskID: fmt.Sprintf("%d", taskID), + TaskID: taskID, Success: false, Error: err.Error(), // 这里会返回 "任务正在运行中,拒绝并行执行" StartTime: time.Now(), @@ -548,7 +543,7 @@ func (es *ExecutorService) ExecuteTask(taskID int, extraEnvs []string) *executor } req := &executor.ExecutionRequest{ - TaskID: fmt.Sprintf("%d", task.ID), + TaskID: task.ID, Name: task.Name, Command: task.Command, WorkDir: task.WorkDir, @@ -562,7 +557,7 @@ func (es *ExecutorService) ExecuteTask(taskID int, extraEnvs []string) *executor es.scheduler.EnqueueOrExecute(req) return &executor.ExecutionResult{ - TaskID: fmt.Sprintf("%d", task.ID), + TaskID: task.ID, Success: true, Status: constant.TaskStatusQueued, StartTime: time.Now(), @@ -570,7 +565,7 @@ func (es *ExecutorService) ExecuteTask(taskID int, extraEnvs []string) *executor } // StopTaskExecution stops a running task execution by LogID -func (es *ExecutorService) StopTaskExecution(logID uint) error { +func (es *ExecutorService) StopTaskExecution(logID string) error { var taskLog models.TaskLog if err := database.DB.First(&taskLog, logID).Error; err != nil { return fmt.Errorf("日志不存在") @@ -580,21 +575,21 @@ func (es *ExecutorService) StopTaskExecution(logID uint) error { return fmt.Errorf("任务已结束") } - task := es.taskService.GetTaskByID(int(taskLog.TaskID)) + task := es.taskService.GetTaskByID(taskLog.TaskID) if task == nil { return fmt.Errorf("任务不存在") } // 远程任务:发送停止指令到 Agent - if task.AgentID != nil && *task.AgentID > 0 { - logger.Infof("[Executor] 请求停止远程任务 #%d (Agent #%d, LogID: %d)", task.ID, *task.AgentID, logID) + if task.AgentID != nil && *task.AgentID != "" { + logger.Infof("[Executor] 请求停止远程任务 #%s (Agent #%s, LogID: %s)", task.ID, *task.AgentID, logID) return es.agentWSManager.SendToAgent(*task.AgentID, constant.WSTypeStop, map[string]interface{}{ "log_id": logID, }) } // 本地任务:直接停止调度器中的执行实例 - logger.Infof("[Executor] 请求停止本地任务 #%d (LogID: %d)", task.ID, logID) + logger.Infof("[Executor] 请求停止本地任务 #%s (LogID: %s)", task.ID, logID) if es.scheduler.StopLog(logID) { return nil } @@ -658,7 +653,7 @@ func (es *ExecutorService) UpdateResult(res executor.ExecutionResult) { // 查找是否已存在(通过 LogID) for i := range es.results { - if es.results[i].LogID == res.LogID && res.LogID != 0 { + if es.results[i].LogID == res.LogID && res.LogID != "" { es.results[i] = res return } @@ -703,9 +698,9 @@ func (es *ExecutorService) CleanupRunningTasks() error { } // CheckConcurrency 检查任务并发限制(只读检查) -func (es *ExecutorService) CheckConcurrency(taskID uint) error { +func (es *ExecutorService) CheckConcurrency(taskID string) error { var task models.Task - if err := database.DB.Select("config, running_go").First(&task, taskID).Error; err != nil { + if err := database.DB.Select("config, running_go").Where("id = ?", taskID).First(&task).Error; err != nil { return err } var goids []int64 @@ -725,11 +720,11 @@ func (es *ExecutorService) CheckConcurrency(taskID uint) error { } // AddRunningGo 添加当前 goroutine ID 到任务的 running_go 字段 -func (es *ExecutorService) AddRunningGo(taskID uint) (int64, error) { +func (es *ExecutorService) AddRunningGo(taskID string) (int64, error) { goid := utils.GetGoroutineID() err := database.DB.Transaction(func(tx *gorm.DB) error { var task models.Task - if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&task, taskID).Error; err != nil { + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", taskID).First(&task).Error; err != nil { return err } var goids []int64 @@ -756,10 +751,10 @@ func (es *ExecutorService) AddRunningGo(taskID uint) (int64, error) { } // RemoveRunningGo 从任务的 running_go 字段移除指定 goroutine ID -func (es *ExecutorService) RemoveRunningGo(taskID uint, goid int64) { +func (es *ExecutorService) RemoveRunningGo(taskID string, goid int64) { database.DB.Transaction(func(tx *gorm.DB) error { var task models.Task - if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&task, taskID).Error; err != nil { + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", taskID).First(&task).Error; err != nil { return err } var goids []int64 @@ -778,17 +773,17 @@ func (es *ExecutorService) RemoveRunningGo(taskID uint, goid int64) { } // ExecuteRemoteForScheduler 供 Scheduler 调用,执行远程任务并等待结果 -func (es *ExecutorService) ExecuteRemoteForScheduler(task *models.Task, logID uint) (*executor.Result, error) { +func (es *ExecutorService) ExecuteRemoteForScheduler(task *models.Task, logID string) (*executor.Result, error) { agentID := *task.AgentID - logger.Infof("[Executor] 远程执行任务 #%d: %s (Agent #%d, LogID: %d)", task.ID, task.Name, agentID, logID) + logger.Infof("[Executor] 远程执行任务 #%s: %s (Agent #%s, LogID: %s)", task.ID, task.Name, agentID, logID) // 1. 检查 Agent 状态 var agent models.Agent - if err := database.DB.First(&agent, agentID).Error; err != nil { - return nil, fmt.Errorf("Agent #%d 不存在", agentID) + if err := database.DB.Where("id = ?", agentID).First(&agent).Error; err != nil { + return nil, fmt.Errorf("Agent #%s 不存在", agentID) } if !agent.Enabled { - return nil, fmt.Errorf("Agent #%d 已禁用", agentID) + return nil, fmt.Errorf("Agent #%s 已禁用", agentID) } if es.agentWSManager == nil { return nil, fmt.Errorf("AgentWSManager 未初始化") diff --git a/internal/services/tasks/task_log_service.go b/internal/services/tasks/task_log_service.go index a4da34f..85a543c 100644 --- a/internal/services/tasks/task_log_service.go +++ b/internal/services/tasks/task_log_service.go @@ -13,7 +13,7 @@ import ( // SendStatsService 接口定义(避免循环依赖) type SendStatsService interface { - IncrementStats(taskID uint, status string) error + IncrementStats(taskID string, status string) error } // TaskLogService 任务日志服务 @@ -35,9 +35,10 @@ type CleanConfig struct { } // CreateEmptyLog 创建一个空的日志记录(任务开始时调用) -func (s *TaskLogService) CreateEmptyLog(taskID uint, command string) (*models.TaskLog, error) { +func (s *TaskLogService) CreateEmptyLog(taskID string, command string) (*models.TaskLog, error) { startTime := models.Now() taskLog := &models.TaskLog{ + ID: utils.GenerateID(), TaskID: taskID, Command: command, Status: "running", @@ -52,9 +53,10 @@ func (s *TaskLogService) CreateEmptyLog(taskID uint, command string) (*models.Ta // SaveTaskLog 保存或更新任务日志 func (s *TaskLogService) SaveTaskLog(taskLog *models.TaskLog) error { var err error - if taskLog.ID > 0 { - err = database.DB.Model(taskLog).Updates(taskLog).Error + if taskLog.ID != "" { + err = database.DB.Model(taskLog).Where("id = ?", taskLog.ID).Updates(taskLog).Error } else { + taskLog.ID = utils.GenerateID() err = database.DB.Create(taskLog).Error } @@ -69,12 +71,12 @@ func (s *TaskLogService) SaveTaskLog(taskLog *models.TaskLog) error { } // UpdateTaskDuration 更新任务耗时(心跳) -func (s *TaskLogService) UpdateTaskDuration(logID uint, duration int64) error { +func (s *TaskLogService) UpdateTaskDuration(logID string, duration int64) error { return database.DB.Model(&models.TaskLog{}).Where("id = ?", logID).Update("duration", duration).Error } // UpdateTaskStats 更新任务统计 -func (s *TaskLogService) UpdateTaskStats(taskID uint, status string) { +func (s *TaskLogService) UpdateTaskStats(taskID string, status string) { if s.sendStatsService == nil { logger.Error("[TaskLog] SendStatsService 未初始化") return @@ -87,9 +89,9 @@ func (s *TaskLogService) UpdateTaskStats(taskID uint, status string) { } // CleanTaskLogs 清理任务日志 -func (s *TaskLogService) CleanTaskLogs(taskID uint) { +func (s *TaskLogService) CleanTaskLogs(taskID string) { var task models.Task - if err := database.DB.First(&task, taskID).Error; err != nil { + if err := database.DB.Where("id = ?", taskID).First(&task).Error; err != nil { return } @@ -123,7 +125,7 @@ func (s *TaskLogService) CleanTaskLogs(taskID uint) { } if deleted > 0 { - logger.Infof("[TaskLog] 清理任务 #%d 的 %d 条日志", taskID, deleted) + logger.Infof("[TaskLog] 清理任务 #%s 的 %d 条日志", taskID, deleted) } } @@ -153,6 +155,7 @@ func (s *TaskLogService) CreateTaskLogFromAgentResult(result *models.AgentTaskRe } taskLog := &models.TaskLog{ + ID: utils.GenerateID(), TaskID: result.TaskID, AgentID: &result.AgentID, Command: result.Command, @@ -177,7 +180,7 @@ func (s *TaskLogService) CreateTaskLogFromAgentResult(result *models.AgentTaskRe } // CreateTaskLogFromLocalExecution 从本地执行结果创建任务日志 -func (s *TaskLogService) CreateTaskLogFromLocalExecution(taskID uint, command, output, systemErr, status string, duration int64, exitCode int, start, end time.Time, isCompressed bool) (*models.TaskLog, error) { +func (s *TaskLogService) CreateTaskLogFromLocalExecution(taskID string, command, output, systemErr, status string, duration int64, exitCode int, start, end time.Time, isCompressed bool) (*models.TaskLog, error) { var compressed string var err error @@ -196,6 +199,7 @@ func (s *TaskLogService) CreateTaskLogFromLocalExecution(taskID uint, command, o endTime := models.LocalTime(end) taskLog := &models.TaskLog{ + ID: utils.GenerateID(), TaskID: taskID, Command: command, Output: compressed, diff --git a/internal/services/tasks/task_service.go b/internal/services/tasks/task_service.go index 055e5b3..4dab32b 100644 --- a/internal/services/tasks/task_service.go +++ b/internal/services/tasks/task_service.go @@ -4,6 +4,7 @@ import ( "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" + "github.com/engigu/baihu-panel/internal/utils" ) type TaskService struct{} @@ -12,7 +13,7 @@ func NewTaskService() *TaskService { return &TaskService{} } -func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, workDir, cleanConfig, envs, taskType, config string, agentID *uint, languages []map[string]string, triggerType string, tags string, retryCount int, retryInterval int, randomRange int) *models.Task { +func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, workDir, cleanConfig, envs, taskType, config string, agentID *string, languages []map[string]string, triggerType string, tags string, retryCount int, retryInterval int, randomRange int) *models.Task { if taskType == "" { taskType = "task" } @@ -20,6 +21,7 @@ func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, w triggerType = constant.TriggerTypeCron } task := &models.Task{ + ID: utils.GenerateID(), Name: name, Command: command, Tags: tags, @@ -52,7 +54,7 @@ func (ts *TaskService) GetTasks() []models.Task { } // GetTasksWithPagination 分页获取任务列表 -func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, agentID *uint, tags string, taskType string) ([]models.Task, int64) { +func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, agentID *string, tags string, taskType string) ([]models.Task, int64) { var tasks []models.Task var total int64 @@ -76,17 +78,17 @@ func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, a return tasks, total } -func (ts *TaskService) GetTaskByID(id int) *models.Task { +func (ts *TaskService) GetTaskByID(id string) *models.Task { var task models.Task - if err := database.DB.First(&task, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).First(&task).Error; err != nil { return nil } return &task } -func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeout int, workDir, cleanConfig, envs string, enabled bool, taskType, config string, agentID *uint, languages []map[string]string, triggerType string, tags string, retryCount int, retryInterval int, randomRange int) *models.Task { +func (ts *TaskService) UpdateTask(id string, name, command, schedule string, timeout int, workDir, cleanConfig, envs string, enabled bool, taskType, config string, agentID *string, languages []map[string]string, triggerType string, tags string, retryCount int, retryInterval int, randomRange int) *models.Task { var task models.Task - if err := database.DB.First(&task, id).Error; err != nil { + if err := database.DB.Where("id = ?", id).First(&task).Error; err != nil { return nil } task.Name = name @@ -117,7 +119,7 @@ func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeou return &task } -func (ts *TaskService) DeleteTask(id int) bool { - result := database.DB.Delete(&models.Task{}, id) +func (ts *TaskService) DeleteTask(id string) bool { + result := database.DB.Where("id = ?", id).Delete(&models.Task{}) return result.RowsAffected > 0 } diff --git a/internal/services/tasks/tiny_log.go b/internal/services/tasks/tiny_log.go index 1e1dd8e..f30af34 100644 --- a/internal/services/tasks/tiny_log.go +++ b/internal/services/tasks/tiny_log.go @@ -16,13 +16,13 @@ import ( var ( // globalTinyLogManager 跟踪所有活跃的 TinyLog 实例 globalTinyLogManager = &TinyLogManager{ - logs: make(map[uint]*TinyLog), + logs: make(map[string]*TinyLog), } ) type TinyLogManager struct { mu sync.RWMutex - logs map[uint]*TinyLog + logs map[string]*TinyLog } func (m *TinyLogManager) Register(log *TinyLog) { @@ -31,26 +31,26 @@ func (m *TinyLogManager) Register(log *TinyLog) { m.logs[log.LogID] = log } -func (m *TinyLogManager) Unregister(logID uint) { +func (m *TinyLogManager) Unregister(logID string) { m.mu.Lock() defer m.mu.Unlock() delete(m.logs, logID) } -func (m *TinyLogManager) Get(logID uint) *TinyLog { +func (m *TinyLogManager) Get(logID string) *TinyLog { m.mu.RLock() defer m.mu.RUnlock() return m.logs[logID] } // GetActiveLog 通过 ID 获取活跃的 TinyLog 实例 -func GetActiveLog(logID uint) *TinyLog { +func GetActiveLog(logID string) *TinyLog { return globalTinyLogManager.Get(logID) } // TinyLog 是一个高性能、低内存占用的日志收集器 type TinyLog struct { - LogID uint + LogID string mu sync.RWMutex file *os.File path string @@ -61,7 +61,7 @@ type TinyLog struct { } // NewTinyLog 创建一个新的 TinyLog 实例(基于临时文件存储)并注册它 -func NewTinyLog(logID uint) (*TinyLog, error) { +func NewTinyLog(logID string) (*TinyLog, error) { f, err := os.CreateTemp("", "task_log_*.log") if err != nil { return nil, err diff --git a/internal/services/user_service.go b/internal/services/user_service.go index d941960..008a030 100644 --- a/internal/services/user_service.go +++ b/internal/services/user_service.go @@ -7,6 +7,7 @@ import ( "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/database" "github.com/engigu/baihu-panel/internal/models" + "github.com/engigu/baihu-panel/internal/utils" ) type UserService struct{} @@ -22,6 +23,7 @@ func (us *UserService) hashPassword(password string) string { func (us *UserService) CreateUser(username, password, email, role string) *models.User { user := &models.User{ + ID: utils.GenerateID(), Username: username, Password: us.hashPassword(password), Email: email, @@ -59,6 +61,6 @@ func (us *UserService) AuthenticateUser(username, password string) bool { return us.ValidatePassword(user, password) } -func (us *UserService) UpdatePassword(userID uint, newPassword string) error { +func (us *UserService) UpdatePassword(userID string, newPassword string) error { return database.DB.Model(&models.User{}).Where("id = ?", userID).Update("password", us.hashPassword(newPassword)).Error } diff --git a/internal/utils/id.go b/internal/utils/id.go new file mode 100644 index 0000000..8cf9ab4 --- /dev/null +++ b/internal/utils/id.go @@ -0,0 +1,19 @@ +package utils + +import ( + "github.com/rs/xid" +) + +// GenerateID 生成一个新的 ID (使用 xid,20位字符) +func GenerateID() string { + return xid.New().String() +} +// IsNumeric 检查字符串是否全为数字 +func IsNumeric(s string) bool { + for _, c := range s { + if c < '0' || c > '9' { + return false + } + } + return s != "" +} diff --git a/internal/utils/token.go b/internal/utils/token.go index d125b65..679e91c 100644 --- a/internal/utils/token.go +++ b/internal/utils/token.go @@ -8,13 +8,13 @@ import ( ) type Claims struct { - UserID uint `json:"user_id"` + UserID string `json:"user_id"` Username string `json:"username"` jwt.RegisteredClaims } // GenerateToken 生成 JWT token -func GenerateToken(userID uint, username string, expireDays int, secret string) (string, error) { +func GenerateToken(userID string, username string, expireDays int, secret string) (string, error) { claims := Claims{ UserID: userID, Username: username, @@ -29,18 +29,18 @@ func GenerateToken(userID uint, username string, expireDays int, secret string) } // ParseToken 解析 JWT token -func ParseToken(tokenString string, secret string) (uint, string, error) { +func ParseToken(tokenString string, secret string) (string, string, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (any, error) { return []byte(secret), nil }) if err != nil { - return 0, "", err + return "", "", err } if claims, ok := token.Claims.(*Claims); ok && token.Valid { return claims.UserID, claims.Username, nil } - return 0, "", errors.New("invalid token") + return "", "", errors.New("invalid token") } diff --git a/web/src/api/index.ts b/web/src/api/index.ts index 04fb84e..ca365aa 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -58,27 +58,27 @@ export const api = { request('/auth/register', { method: 'POST', body: JSON.stringify(data) }) }, tasks: { - list: (params?: { page?: number; page_size?: number; name?: string; agent_id?: number; tags?: string; type?: string }) => { + list: (params?: { page?: number; page_size?: number; name?: string; agent_id?: string; tags?: string; type?: string }) => { const query = new URLSearchParams() if (params?.page) query.set('page', String(params.page)) if (params?.page_size) query.set('page_size', String(params.page_size)) if (params?.name) query.set('name', params.name) if (params?.tags) query.set('tags', params.tags) - if (params?.agent_id) query.set('agent_id', String(params.agent_id)) + if (params?.agent_id) query.set('agent_id', params.agent_id) if (params?.type) query.set('type', params.type) return request(`/tasks?${query}`) }, create: (data: Partial) => request('/tasks', { method: 'POST', body: JSON.stringify(data) }), - update: (id: number, data: Partial) => request(`/tasks/${id}`, { method: 'PUT', body: JSON.stringify(data) }), - delete: (id: number) => request(`/tasks/${id}`, { method: 'DELETE' }), - execute: (id: number) => request(`/execute/task/${id}`, { method: 'POST' }), - stop: (logID: number) => request(`/tasks/stop/${logID}`, { method: 'POST' }) + update: (id: string, data: Partial) => request(`/tasks/${id}`, { method: 'PUT', body: JSON.stringify(data) }), + delete: (id: string) => request(`/tasks/${id}`, { method: 'DELETE' }), + execute: (id: string) => request(`/execute/task/${id}`, { method: 'POST' }), + stop: (logID: string) => request(`/tasks/stop/${logID}`, { method: 'POST' }) }, scripts: { list: () => request('/scripts'), create: (data: Partial