diff --git a/internal/constant/constant.go b/internal/constant/constant.go index d1f4506..bafdc49 100644 --- a/internal/constant/constant.go +++ b/internal/constant/constant.go @@ -159,6 +159,11 @@ const ( EnvTypeNormal = "normal" EnvTypeSecret = "secret" + // Relation Types + RelationTypeTaskTag = "task_tag" + RelationTypeTaskEnv = "task_env" + RelationTypeEnvTag = "env_tag" + // WebSocket 安全常量 // PongWait 收到 pong 的超时时间 PongWait = 60 * time.Second diff --git a/internal/controllers/env_controller.go b/internal/controllers/env_controller.go index 2ba4cd1..7c192f5 100644 --- a/internal/controllers/env_controller.go +++ b/internal/controllers/env_controller.go @@ -1,8 +1,10 @@ package controllers import ( + "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/models/vo" "github.com/engigu/baihu-panel/internal/services" + "github.com/engigu/baihu-panel/internal/services/relation" "github.com/engigu/baihu-panel/internal/utils" "github.com/gin-gonic/gin" @@ -72,7 +74,7 @@ func (ec *EnvController) CreateEnvVar(c *gin.Context) { envVar := ec.envService.CreateEnvVar(req.Name, req.Value, req.Remark, req.Type, hidden, enabled, userID) if envVar != nil { - ec.envService.SaveEnvTags(envVar.ID, req.Tags) + relation.DataRelation.SaveTags(envVar.ID, constant.RelationTypeEnvTag, req.Tags) envVar.Tags = req.Tags } utils.Success(c, vo.ToEnvVO(envVar)) @@ -205,7 +207,7 @@ func (ec *EnvController) UpdateEnvVar(c *gin.Context) { return } - ec.envService.SaveEnvTags(envVar.ID, req.Tags) + relation.DataRelation.SaveTags(envVar.ID, constant.RelationTypeEnvTag, req.Tags) envVar.Tags = req.Tags utils.Success(c, vo.ToEnvVO(envVar)) diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 8a8cdb4..48b1302 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -9,6 +9,7 @@ import ( "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/logger" "github.com/engigu/baihu-panel/internal/models" + "github.com/rs/xid" ) var allModels = []interface{}{ @@ -97,25 +98,188 @@ func getModelSignature(models []interface{}) string { return hex.EncodeToString(hash[:]) } -// customMigrations 自定义迁移(处理 AutoMigrate 无法自动完成的变更) +// customMigrations 自定义迁移,处理 AutoMigrate 无法自动解决的变更 func customMigrations() error { // 检查 ql_tokens 表是否存在 if DB.Migrator().HasTable("ql_tokens") { - // 将 code 列重命名为 token(如果 code 列存在) + // 如果 code 列存在,且 token 列不存在,则重命名 if DB.Migrator().HasColumn(&models.AgentToken{}, "code") { if err := DB.Migrator().RenameColumn(&models.AgentToken{}, "code", "token"); err != nil { - logger.Debugf("[Database] 重命名 ql_tokens.code 列: %v", err) + logger.Debugf("[Database] 重命名 ql_tokens.code 失败: %v", err) } } } // 移除 deps 表中的 type 字段(如果存在) if DB.Migrator().HasColumn(&models.Dependency{}, "type") { if err := DB.Migrator().DropColumn(&models.Dependency{}, "type"); err != nil { - logger.Debugf("[Database] 移除 deps.type 列失败: %v", err) + logger.Debugf("[Database] 移除 deps.type 字段失败: %v", err) } else { - logger.Infof("[Database] 已成功移除 deps 表中的冗余 type 列") + logger.Infof("[Database] 已成功移除 deps 表中的 type 字段") } } + migrateTaskTags() + migrateTaskEnvs() + return nil } + +// migrateTaskEnvs 迁移旧的任务绑定环境变量到通用数据关联表 +func migrateTaskEnvs() { + // 检查 settings 表中是否已经记录了迁移状态 + if DB.Migrator().HasTable(&models.Setting{}) { + var setting models.Setting + res := DB.Where(&models.Setting{Section: "system", Key: "task_envs_migrated"}).Limit(1).Find(&setting) + if res.RowsAffected > 0 && string(setting.Value) == "true" { + return + } + } + + if !DB.Migrator().HasColumn(&models.Task{}, "envs") { + markTaskEnvsMigrated() + return + } + logger.Infof("[Database] 正在迁移旧任务环境变量绑定...") + + type TaskMigration struct { + ID string + Envs models.BigText + } + var tasks []TaskMigration + DB.Table((&models.Task{}).TableName()).Select("id, envs").Where("envs IS NOT NULL AND envs != ?", "").Find(&tasks) + + for _, task := range tasks { + envs := strings.Split(string(task.Envs), ",") + for _, envID := range envs { + envID = strings.TrimSpace(envID) + if envID == "" { + continue + } + var count int64 + DB.Model(&models.DataRelation{}).Where("data_id = ? AND relate_id = ? AND type = ?", task.ID, envID, constant.RelationTypeTaskEnv).Count(&count) + if count == 0 { + relation := models.DataRelation{ + ID: xid.New().String(), + DataID: task.ID, + RelateID: envID, + Type: constant.RelationTypeTaskEnv, + CreatedAt: models.Now(), + UpdatedAt: models.Now(), + } + DB.Create(&relation) + } + } + } + + // if err := DB.Migrator().DropColumn(&models.Task{}, "envs"); err != nil { + // logger.Debugf("[Database] 移除 bh_tasks.envs 字段失败: %v", err) + // } else { + // logger.Infof("[Database] 成功迁移 %d 个环境变量绑定的任务,并删除了旧 envs 字段", len(tasks)) + // } + logger.Infof("[Database] 成功迁移 %d 个环境变量绑定的任务", len(tasks)) + markTaskEnvsMigrated() +} + +func markTaskEnvsMigrated() { + if !DB.Migrator().HasTable(&models.Setting{}) { + return + } + var setting models.Setting + res := DB.Where(&models.Setting{Section: "system", Key: "task_envs_migrated"}).Limit(1).Find(&setting) + if res.RowsAffected > 0 { + DB.Model(&setting).Update("value", models.BigText("true")) + } else { + DB.Create(&models.Setting{ + ID: xid.New().String(), + Section: "system", + Key: "task_envs_migrated", + Value: models.BigText("true"), + }) + } +} + +// migrateTaskTags 迁移旧的任务标签到通用数据关联表 +func migrateTaskTags() { + // 检查 settings 表中是否已经记录了迁移状态 + if DB.Migrator().HasTable(&models.Setting{}) { + var setting models.Setting + res := DB.Where(&models.Setting{Section: "system", Key: "task_tags_migrated"}).Limit(1).Find(&setting) + if res.RowsAffected > 0 && string(setting.Value) == "true" { + return + } + } + + if !DB.Migrator().HasColumn(&models.Task{}, "tags") { + markTaskTagsMigrated() + return + } + logger.Infof("[Database] 正在迁移旧任务标签...") + + type TaskMigration struct { + ID string + Tags string + } + var tasks []TaskMigration + DB.Table((&models.Task{}).TableName()).Select("id, tags").Where("tags != ?", "").Find(&tasks) + + for _, task := range tasks { + tags := strings.Split(task.Tags, ",") + for _, tag := range tags { + tag = strings.TrimSpace(tag) + if tag == "" { + continue + } + var storage models.DataStorage + res := DB.Where("type = ? AND name = ?", constant.RelationTypeTaskTag, tag).Limit(1).Find(&storage) + if res.RowsAffected == 0 { + storage = models.DataStorage{ + ID: xid.New().String(), + Type: constant.RelationTypeTaskTag, + Name: tag, + CreatedAt: models.Now(), + UpdatedAt: models.Now(), + } + DB.Create(&storage) + } + var count int64 + DB.Model(&models.DataRelation{}).Where("data_id = ? AND relate_id = ? AND type = ?", task.ID, storage.ID, constant.RelationTypeTaskTag).Count(&count) + if count == 0 { + relation := models.DataRelation{ + ID: xid.New().String(), + DataID: task.ID, + RelateID: storage.ID, + Type: constant.RelationTypeTaskTag, + CreatedAt: models.Now(), + UpdatedAt: models.Now(), + } + DB.Create(&relation) + } + } + } + + // if err := DB.Migrator().DropColumn(&models.Task{}, "tags"); err != nil { + // logger.Debugf("[Database] 移除 bh_tasks.tags 字段失败: %v", err) + // } else { + // logger.Infof("[Database] 成功迁移 %d 个带有标签的任务,并删除了旧 tags 字段", len(tasks)) + // } + logger.Infof("[Database] 成功迁移 %d 个带有标签的任务", len(tasks)) + markTaskTagsMigrated() +} + +func markTaskTagsMigrated() { + if !DB.Migrator().HasTable(&models.Setting{}) { + return + } + var setting models.Setting + res := DB.Where(&models.Setting{Section: "system", Key: "task_tags_migrated"}).Limit(1).Find(&setting) + if res.RowsAffected > 0 { + DB.Model(&setting).Update("value", models.BigText("true")) + } else { + DB.Create(&models.Setting{ + ID: xid.New().String(), + Section: "system", + Key: "task_tags_migrated", + Value: models.BigText("true"), + }) + } +} diff --git a/internal/models/task.go b/internal/models/task.go index a83d465..1389384 100644 --- a/internal/models/task.go +++ b/internal/models/task.go @@ -76,7 +76,7 @@ type Task struct { Command BigText `json:"command"` // 普通任务的命令 PreCommand BigText `json:"pre_command"` // 执行前的命令 PostCommand BigText `json:"post_command"` // 执行后的命令 - Tags string `json:"tags" gorm:"size:255;default:''"` // 标签,逗号分隔 + Tags string `json:"tags" gorm:"-"` // 标签,逗号分隔 Type string `json:"type" gorm:"size:20;default:'task'"` // 任务类型: constant.TaskTypeNormal, constant.TaskTypeRepo TriggerType string `json:"trigger_type" gorm:"size:25;default:'cron'"` // 触发类型: constant.TriggerTypeCron, constant.TriggerTypeBaihuStartup Config BigText `json:"config"` // 配置 JSON(仓库同步配置等) @@ -84,7 +84,7 @@ 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 BigText `json:"envs"` // 环境变量ID列表,逗号分隔 + Envs BigText `json:"envs" gorm:"-"` // 环境变量ID列表,逗号分隔 Languages TaskLanguages `json:"languages" gorm:"type:text"` // 针对本地任务的语言配置列表 AgentID *string `json:"agent_id" gorm:"size:20;index"` // Agent ID,为空表示本地执行 RetryCount int `json:"retry_count" gorm:"default:0"` // 失败重试次数 diff --git a/internal/services/env_service.go b/internal/services/env_service.go index 7d8783d..0991bac 100644 --- a/internal/services/env_service.go +++ b/internal/services/env_service.go @@ -6,6 +6,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/services/relation" "github.com/engigu/baihu-panel/internal/utils" "gorm.io/gorm" @@ -76,11 +77,11 @@ func (es *EnvService) GetEnvVarsWithPagination(userID string, name string, envTy } if len(validTags) > 0 { var storageIDs []string - database.DB.Model(&models.DataStorage{}).Where("type = ? AND name IN ?", "env_tag", validTags).Pluck("id", &storageIDs) + database.DB.Model(&models.DataStorage{}).Where("type = ? AND name IN ?", constant.RelationTypeEnvTag, validTags).Pluck("id", &storageIDs) var envIDs []string if len(storageIDs) > 0 { - database.DB.Model(&models.DataRelation{}).Where("type = ? AND relate_id IN ?", "env_tag", storageIDs).Pluck("data_id", &envIDs) + database.DB.Model(&models.DataRelation{}).Where("type = ? AND relate_id IN ?", constant.RelationTypeEnvTag, storageIDs).Pluck("data_id", &envIDs) } if len(envIDs) > 0 { @@ -138,8 +139,11 @@ func (es *EnvService) UpdateEnvVar(id string, name, value, remark, envType strin func (es *EnvService) GetAssociatedTasks(id string) []models.Task { var associatedTasks []models.Task - query := "envs = ? OR envs LIKE ? OR envs LIKE ? OR envs LIKE ?" - database.DB.Where(query, id, id+",%", "%,"+id, "%,"+id+",%").Find(&associatedTasks) + var taskIDs []string + database.DB.Model(&models.DataRelation{}).Where("type = ? AND relate_id = ?", "task_env", id).Pluck("data_id", &taskIDs) + if len(taskIDs) > 0 { + database.DB.Where("id IN ?", taskIDs).Find(&associatedTasks) + } return associatedTasks } @@ -152,19 +156,9 @@ func (es *EnvService) DeleteEnvVar(id string, force bool) (bool, []models.Task) if force { err := database.DB.Transaction(func(tx *gorm.DB) error { - // Update tasks to remove this env ID - for _, task := range associatedTasks { - ids := splitEnvIDs(string(task.Envs)) - var newIDs []string - for _, eid := range ids { - if eid != id { - newIDs = append(newIDs, eid) - } - } - newEnvs := strings.Join(newIDs, ",") - if err := tx.Model(&task).Update("envs", newEnvs).Error; err != nil { - return err - } + // Delete the relations mapping this env to any tasks + if err := tx.Where("type = ? AND relate_id = ?", "task_env", id).Delete(&models.DataRelation{}).Error; err != nil { + return err } // Delete the env var if err := tx.Where("id = ?", id).Delete(&models.EnvironmentVariable{}).Error; err != nil { @@ -173,7 +167,7 @@ func (es *EnvService) DeleteEnvVar(id string, force bool) (bool, []models.Task) return nil }) if err == nil { - es.CleanEnvTags(id) + relation.DataRelation.CleanRelations(id, constant.RelationTypeEnvTag) return true, nil } return false, nil @@ -181,7 +175,7 @@ func (es *EnvService) DeleteEnvVar(id string, force bool) (bool, []models.Task) result := database.DB.Where("id = ?", id).Delete(&models.EnvironmentVariable{}) if result.RowsAffected > 0 { - es.CleanEnvTags(id) + relation.DataRelation.CleanRelations(id, constant.RelationTypeEnvTag) return true, nil } return false, nil @@ -348,7 +342,7 @@ func splitEnvIDs(envIDs string) []string { // SaveEnvTags 保存环境变量标签 func (es *EnvService) SaveEnvTags(envID string, tagsStr string) { - database.DB.Where("data_id = ? AND type = ?", envID, "env_tag").Delete(&models.DataRelation{}) + database.DB.Where("data_id = ? AND type = ?", envID, constant.RelationTypeEnvTag).Delete(&models.DataRelation{}) if tagsStr == "" { return } @@ -359,11 +353,11 @@ func (es *EnvService) SaveEnvTags(envID string, tagsStr string) { continue } var storage models.DataStorage - res := database.DB.Where("type = ? AND name = ?", "env_tag", tag).Limit(1).Find(&storage) + res := database.DB.Where("type = ? AND name = ?", constant.RelationTypeEnvTag, tag).Limit(1).Find(&storage) if res.RowsAffected == 0 { storage = models.DataStorage{ ID: utils.GenerateID(), - Type: "env_tag", + Type: constant.RelationTypeEnvTag, Name: tag, CreatedAt: models.Now(), UpdatedAt: models.Now(), @@ -374,7 +368,7 @@ func (es *EnvService) SaveEnvTags(envID string, tagsStr string) { ID: utils.GenerateID(), DataID: envID, RelateID: storage.ID, - Type: "env_tag", + Type: constant.RelationTypeEnvTag, CreatedAt: models.Now(), UpdatedAt: models.Now(), } @@ -391,46 +385,25 @@ func (es *EnvService) LoadEnvTags(envs []models.EnvironmentVariable) { for i, e := range envs { envIDs[i] = e.ID } - var relations []models.DataRelation - database.DB.Where("data_id IN ? AND type = ?", envIDs, "env_tag").Find(&relations) - if len(relations) == 0 { - return - } - relateIDs := make([]string, len(relations)) - for i, r := range relations { - relateIDs[i] = r.RelateID - } - var storages []models.DataStorage - database.DB.Where("id IN ? AND type = ?", relateIDs, "env_tag").Find(&storages) - storageMap := make(map[string]string) - for _, s := range storages { - storageMap[s.ID] = s.Name - } - - envTagsMap := make(map[string][]string) - for _, r := range relations { - if name, ok := storageMap[r.RelateID]; ok { - envTagsMap[r.DataID] = append(envTagsMap[r.DataID], name) - } - } + tagsMap := relation.DataRelation.LoadTags(envIDs, constant.RelationTypeEnvTag) for i, e := range envs { - if tags, ok := envTagsMap[e.ID]; ok { + if tags, ok := tagsMap[e.ID]; ok { envs[i].Tags = strings.Join(tags, ",") + } else { + envs[i].Tags = "" } } } -// GetAllEnvTags 获取所有环境变量标签 +// GetAllEnvTags 获取全局环境变量标签 func (es *EnvService) GetAllEnvTags() ([]string, error) { - var tags []string - err := database.DB.Model(&models.DataStorage{}).Where("type = ?", "env_tag").Pluck("name", &tags).Error - return tags, err + return relation.DataRelation.GetAllTags(constant.RelationTypeEnvTag) } // CleanEnvTags 删除环境变量时清理关联标签记录 func (es *EnvService) CleanEnvTags(id string) { - database.DB.Where("data_id = ? AND type = ?", id, "env_tag").Delete(&models.DataRelation{}) + database.DB.Where("data_id = ? AND type = ?", id, constant.RelationTypeEnvTag).Delete(&models.DataRelation{}) } diff --git a/internal/services/relation/relation_service.go b/internal/services/relation/relation_service.go new file mode 100644 index 0000000..50afe61 --- /dev/null +++ b/internal/services/relation/relation_service.go @@ -0,0 +1,141 @@ +package relation + +import ( + "strings" + + "github.com/engigu/baihu-panel/internal/database" + "github.com/engigu/baihu-panel/internal/models" + "github.com/rs/xid" +) + +type DataRelationService struct{} + +var DataRelation = &DataRelationService{} + +// SaveTags 保存带有 Storage (文本标签) 的关系映射 +func (s *DataRelationService) SaveTags(dataID string, relType string, tagsStr string) { + database.DB.Where("data_id = ? AND type = ?", dataID, relType).Delete(&models.DataRelation{}) + if tagsStr == "" { + return + } + tags := strings.Split(tagsStr, ",") + for _, tag := range tags { + tag = strings.TrimSpace(tag) + if tag == "" { + continue + } + var storage models.DataStorage + res := database.DB.Where("type = ? AND name = ?", relType, tag).Limit(1).Find(&storage) + if res.RowsAffected == 0 { + storage = models.DataStorage{ + ID: xid.New().String(), + Type: relType, + Name: tag, + CreatedAt: models.Now(), + UpdatedAt: models.Now(), + } + database.DB.Create(&storage) + } + relation := models.DataRelation{ + ID: xid.New().String(), + DataID: dataID, + RelateID: storage.ID, + Type: relType, + CreatedAt: models.Now(), + UpdatedAt: models.Now(), + } + database.DB.Create(&relation) + } +} + +// LoadTags 加载带有 Storage (文本标签) 的映射,返回 map[DataID][]TagName +func (s *DataRelationService) LoadTags(dataIDs []string, relType string) map[string][]string { + if len(dataIDs) == 0 { + return nil + } + var relations []models.DataRelation + database.DB.Where("data_id IN ? AND type = ?", dataIDs, relType).Find(&relations) + + if len(relations) == 0 { + return nil + } + + var relateIDs []string + for _, r := range relations { + relateIDs = append(relateIDs, r.RelateID) + } + + var storages []models.DataStorage + database.DB.Where("id IN ?", relateIDs).Find(&storages) + + storageMap := make(map[string]string) + for _, storage := range storages { + storageMap[storage.ID] = storage.Name + } + + resultMap := make(map[string][]string) + for _, r := range relations { + if name, ok := storageMap[r.RelateID]; ok { + resultMap[r.DataID] = append(resultMap[r.DataID], name) + } + } + return resultMap +} + +// SaveRelations 保存单纯的关系映射 (例如 ID关联) +func (s *DataRelationService) SaveRelations(dataID string, relType string, relateIDsStr string) { + database.DB.Where("data_id = ? AND type = ?", dataID, relType).Delete(&models.DataRelation{}) + if relateIDsStr == "" { + return + } + ids := strings.Split(relateIDsStr, ",") + for _, relateID := range ids { + relateID = strings.TrimSpace(relateID) + if relateID == "" { + continue + } + relation := models.DataRelation{ + ID: xid.New().String(), + DataID: dataID, + RelateID: relateID, + Type: relType, + CreatedAt: models.Now(), + UpdatedAt: models.Now(), + } + database.DB.Create(&relation) + } +} + +// LoadRelations 加载单纯的关系映射,返回 map[DataID][]RelateID +func (s *DataRelationService) LoadRelations(dataIDs []string, relType string) map[string][]string { + if len(dataIDs) == 0 { + return nil + } + var relations []models.DataRelation + database.DB.Where("data_id IN ? AND type = ?", dataIDs, relType).Find(&relations) + + resultMap := make(map[string][]string) + for _, r := range relations { + resultMap[r.DataID] = append(resultMap[r.DataID], r.RelateID) + } + return resultMap +} + +// CleanRelations 删除某种类型的所有关联映射 +func (s *DataRelationService) CleanRelations(dataID string, relType string) { + database.DB.Where("data_id = ? AND type = ?", dataID, relType).Delete(&models.DataRelation{}) +} + +// GetAllTags 获取全局范围内某种类型的所有的 Tag Name +func (s *DataRelationService) GetAllTags(relType string) ([]string, error) { + var storages []models.DataStorage + err := database.DB.Where("type = ?", relType).Find(&storages).Error + if err != nil { + return nil, err + } + var tags []string + for _, s := range storages { + tags = append(tags, s.Name) + } + return tags, nil +} diff --git a/internal/services/tasks/task_service.go b/internal/services/tasks/task_service.go index c03779c..ca67124 100644 --- a/internal/services/tasks/task_service.go +++ b/internal/services/tasks/task_service.go @@ -1,11 +1,13 @@ package tasks import ( + "strings" + "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/relation" "github.com/engigu/baihu-panel/internal/utils" - "strings" ) // TaskParam 任务创建与更新参数传输对象 @@ -47,6 +49,8 @@ func (ts *TaskService) GetTaskBySourceID(sourceID string) *models.Task { if res.Error != nil || res.RowsAffected == 0 { return nil } + ts.loadTagsAndEnvs([]models.Task{task}) + return &task } @@ -91,6 +95,10 @@ func (ts *TaskService) CreateTask(p *TaskParam) *models.Task { task.NextRun = nil } database.DB.Select("*").Create(task) + relation.DataRelation.SaveTags(task.ID, constant.RelationTypeTaskTag, p.Tags) + task.Tags = p.Tags + relation.DataRelation.SaveRelations(task.ID, constant.RelationTypeTaskEnv, p.Envs) + task.Envs = models.BigText(p.Envs) return task } @@ -98,6 +106,7 @@ func (ts *TaskService) CreateTask(p *TaskParam) *models.Task { func (ts *TaskService) GetTasks() []models.Task { var tasks []models.Task database.DB.Find(&tasks) + ts.loadTagsAndEnvs(tasks) return tasks } @@ -111,20 +120,30 @@ func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, a query = query.Where("name LIKE ? OR remark LIKE ?", "%"+name+"%", "%"+name+"%") } - // 标签筛选 (并集) + // 标签筛选 (交集或并集均可,这里保留原本的逻辑为并集,但是利用数据关联表) if tags != "" { tagList := strings.Split(tags, ",") - var orConditions []string - var orValues []interface{} + var validTags []string for _, tag := range tagList { tag = strings.TrimSpace(tag) if tag != "" { - orConditions = append(orConditions, "tags LIKE ?") - orValues = append(orValues, "%"+tag+"%") + validTags = append(validTags, tag) } } - if len(orConditions) > 0 { - query = query.Where(strings.Join(orConditions, " OR "), orValues...) + if len(validTags) > 0 { + var storageIDs []string + database.DB.Model(&models.DataStorage{}).Where("type = ? AND name IN ?", constant.RelationTypeTaskTag, validTags).Pluck("id", &storageIDs) + + var taskIDs []string + if len(storageIDs) > 0 { + database.DB.Model(&models.DataRelation{}).Where("type = ? AND relate_id IN ?", constant.RelationTypeTaskTag, storageIDs).Pluck("data_id", &taskIDs) + } + + if len(taskIDs) > 0 { + query = query.Where("id IN ?", taskIDs) + } else { + query = query.Where("1 = 0") + } } } @@ -137,6 +156,7 @@ func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, a query.Count(&total) query.Order("pin_type DESC, created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&tasks) + ts.loadTagsAndEnvs(tasks) return tasks, total } @@ -147,6 +167,8 @@ func (ts *TaskService) GetTaskByID(id string) *models.Task { if res.Error != nil || res.RowsAffected == 0 { return nil } + ts.loadTagsAndEnvs([]models.Task{task}) + return &task } @@ -162,12 +184,10 @@ func (ts *TaskService) UpdateTask(id string, p *TaskParam) *models.Task { task.PreCommand = models.BigText(p.PreCommand) task.PostCommand = models.BigText(p.PostCommand) task.PinType = p.PinType - task.Tags = p.Tags task.Schedule = p.Schedule task.Timeout = p.Timeout task.WorkDir = p.WorkDir task.CleanConfig = p.CleanConfig - task.Envs = models.BigText(p.Envs) task.Enabled = &p.Enabled task.AgentID = p.AgentID task.Languages = p.Languages @@ -187,18 +207,25 @@ func (ts *TaskService) UpdateTask(id string, p *TaskParam) *models.Task { database.DB.Model(&task).Select( "Name", "Remark", "Command", "Tags", "Schedule", "Timeout", "WorkDir", - "CleanConfig", "Envs", "Enabled", "AgentID", "Languages", + "CleanConfig", "Enabled", "AgentID", "Languages", "RetryCount", "RetryInterval", "RandomRange", "Type", "TriggerType", "Config", "SourceID", "PinType", "PreCommand", "PostCommand", ).Updates(&task) + relation.DataRelation.SaveTags(task.ID, constant.RelationTypeTaskTag, p.Tags) + task.Tags = p.Tags + relation.DataRelation.SaveRelations(task.ID, constant.RelationTypeTaskEnv, p.Envs) + task.Envs = models.BigText(p.Envs) + return &task } func (ts *TaskService) DeleteTask(id string) bool { // 同时删除关联的通知推送设置 database.DB.Where("type = ? AND data_id = ?", constant.BindingTypeTask, id).Delete(&models.NotifyBinding{}) + relation.DataRelation.CleanRelations(id, constant.RelationTypeTaskTag) + relation.DataRelation.CleanRelations(id, constant.RelationTypeTaskEnv) result := database.DB.Where("id = ?", id).Delete(&models.Task{}) return result.RowsAffected > 0 @@ -207,6 +234,8 @@ func (ts *TaskService) DeleteTask(id string) bool { func (ts *TaskService) BatchDeleteTasks(ids []string) int64 { // 同时删除关联的通知推送设置 database.DB.Where("type = ? AND data_id IN ?", constant.BindingTypeTask, ids).Delete(&models.NotifyBinding{}) + database.DB.Where("type = ? AND data_id IN ?", constant.RelationTypeTaskTag, ids).Delete(&models.DataRelation{}) + database.DB.Where("type = ? AND data_id IN ?", constant.RelationTypeTaskEnv, ids).Delete(&models.DataRelation{}) result := database.DB.Where("id IN ?", ids).Delete(&models.Task{}) return result.RowsAffected @@ -214,23 +243,32 @@ func (ts *TaskService) BatchDeleteTasks(ids []string) int64 { // GetAllTags 获取所有任务标签 func (ts *TaskService) GetAllTags() ([]string, error) { - var tasks []models.Task - database.DB.Select("tags").Where("tags != ?", "").Find(&tasks) + return relation.DataRelation.GetAllTags(constant.RelationTypeTaskTag) +} - tagMap := make(map[string]bool) - for _, task := range tasks { - tags := strings.Split(task.Tags, ",") - for _, tag := range tags { - tag = strings.TrimSpace(tag) - if tag != "" { - tagMap[tag] = true - } +func (ts *TaskService) loadTagsAndEnvs(tasks []models.Task) { + if len(tasks) == 0 { + return + } + taskIDs := make([]string, len(tasks)) + for i, t := range tasks { + taskIDs[i] = t.ID + } + + tagsMap := relation.DataRelation.LoadTags(taskIDs, constant.RelationTypeTaskTag) + envsMap := relation.DataRelation.LoadRelations(taskIDs, constant.RelationTypeTaskEnv) + + for i, t := range tasks { + if tags, ok := tagsMap[t.ID]; ok { + tasks[i].Tags = strings.Join(tags, ",") + } else { + tasks[i].Tags = "" + } + + if envs, ok := envsMap[t.ID]; ok { + tasks[i].Envs = models.BigText(strings.Join(envs, ",")) + } else { + tasks[i].Envs = models.BigText("") } } - - result := make([]string, 0, len(tagMap)) - for tag := range tagMap { - result = append(result, tag) - } - return result, nil }