feat: add qlrepo sync crontab
This commit is contained in:
@@ -241,6 +241,10 @@ func (h *ServerSchedulerHandler) OnTaskCompleted(req *executor.ExecutionRequest,
|
||||
// 处理任务完成(更新统计、清理旧日志等)
|
||||
h.es.taskLogService.ProcessTaskCompletion(taskLog)
|
||||
|
||||
if task.Type == constant.TaskTypeRepo && result.Status == constant.TaskStatusSuccess {
|
||||
go ParseRepoScriptsAndAddCron(h.es, task)
|
||||
}
|
||||
|
||||
// 更新内存缓冲
|
||||
h.es.UpdateResult(*result)
|
||||
|
||||
@@ -418,6 +422,10 @@ func (h *LocalTaskHooks) OnHeartbeat(ctx context.Context, logID string, duration
|
||||
// ExecuteDispatcher 实现任务分发逻辑
|
||||
func (es *ExecutorService) ExecuteDispatcher(ctx context.Context, req *executor.ExecutionRequest, stdout, stderr io.Writer) (*executor.Result, error) {
|
||||
taskID := req.TaskID
|
||||
|
||||
// 解析路径变量 (如 $SCRIPTS_DIR$)
|
||||
req.Command = es.ResolvePath(req.Command)
|
||||
req.WorkDir = es.ResolvePath(req.WorkDir)
|
||||
|
||||
task := es.taskService.GetTaskByID(taskID)
|
||||
// 系统任务(无 taskID)直接本地执行
|
||||
@@ -479,7 +487,7 @@ func (es *ExecutorService) Stop() {
|
||||
|
||||
// StartCron 启动计划任务
|
||||
func (es *ExecutorService) StartCron() {
|
||||
es.loadCronTasks()
|
||||
go es.loadCronTasks()
|
||||
es.cronManager.Start()
|
||||
// logger.Info("[Executor] 计划任务管理器已启动")
|
||||
}
|
||||
@@ -949,8 +957,24 @@ func (es *ExecutorService) BuildRepoCommand(task *models.Task) (string, string)
|
||||
if config.WhitelistPaths != "" {
|
||||
args = append(args, "--whitelist-paths", config.WhitelistPaths)
|
||||
}
|
||||
if config.Blacklist != "" {
|
||||
args = append(args, "--blacklist", config.Blacklist)
|
||||
}
|
||||
if config.Dependence != "" {
|
||||
args = append(args, "--dependence", config.Dependence)
|
||||
}
|
||||
if config.Extensions != "" {
|
||||
args = append(args, "--extensions", config.Extensions)
|
||||
}
|
||||
|
||||
return exePath + " " + strings.Join(args, " "), filepath.Dir(exePath)
|
||||
// 为了防止 shell 解释特殊字符(如 |),对每个参数进行转义/加引号
|
||||
quotedArgs := make([]string, len(args))
|
||||
for i, arg := range args {
|
||||
// 使用单引号包裹参数,并转义已有的单引号
|
||||
quotedArgs[i] = "'" + strings.ReplaceAll(arg, "'", "'\\''") + "'"
|
||||
}
|
||||
|
||||
return "'" + strings.ReplaceAll(exePath, "'", "'\\''") + "' " + strings.Join(quotedArgs, " "), filepath.Dir(exePath)
|
||||
}
|
||||
|
||||
// loadEnvVars 加载环境变量,支持全局注入及重名合并
|
||||
@@ -981,3 +1005,8 @@ func (es *ExecutorService) loadEnvVars(taskID string, envIDs string) []string {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *ExecutorService) ResolvePath(path string) string {
|
||||
absScriptsDir, _ := filepath.Abs(constant.ScriptsWorkDir)
|
||||
return strings.ReplaceAll(path, "$SCRIPTS_DIR$", absScriptsDir)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"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/utils"
|
||||
)
|
||||
|
||||
// regex patterns for script comment parsing
|
||||
var (
|
||||
envRegex = regexp.MustCompile(`(?i)[new ]*Env\(['"]?([^'"]+)['"]?\)[;]?`)
|
||||
cronRegex = regexp.MustCompile(`(?i)(?:cron[ \t]*[:=]?[ \t]*['"]([^'"]+)['"])|(?:(?:^|[ \t\*\/])([0-9\*\/\-,L?]+[ \t]+[0-9\*\/\-,L?#]+[ \t]+[0-9\*\/\-,L?#]+[ \t]+[0-9\*\/\-,L?#]+[ \t]+[0-9\*\/\-,L?#]+(?:[ \t]+[0-9\*\/\-,L?#]+)?))`)
|
||||
)
|
||||
|
||||
// ParseRepoScriptsAndAddCron scans the repo dir for scripts, parses cron and env comments, and registers tasks
|
||||
func ParseRepoScriptsAndAddCron(es *ExecutorService, repoTask *models.Task) {
|
||||
if repoTask == nil || repoTask.Type != constant.TaskTypeRepo {
|
||||
return
|
||||
}
|
||||
|
||||
var repoCfg models.RepoConfig
|
||||
if err := json.Unmarshal([]byte(repoTask.Config), &repoCfg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if repoCfg.RepoSource != "ql" || !repoCfg.AutoAddCron {
|
||||
return
|
||||
}
|
||||
|
||||
// target path
|
||||
targetPath := repoCfg.TargetPath
|
||||
if targetPath == "" {
|
||||
targetPath = repoTask.WorkDir
|
||||
}
|
||||
if targetPath == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// We might have appended a repo id to targetPath
|
||||
repoId := utils.GetRepoIdentifier(repoCfg.SourceURL, repoCfg.Branch)
|
||||
|
||||
gitDir := filepath.Join(targetPath, ".git")
|
||||
if !isDir(targetPath) || !pathExists(gitDir) {
|
||||
repoPath := filepath.Join(targetPath, repoId)
|
||||
if pathExists(repoPath) {
|
||||
targetPath = repoPath
|
||||
}
|
||||
}
|
||||
|
||||
if !pathExists(targetPath) {
|
||||
return
|
||||
}
|
||||
|
||||
// tag used during sync
|
||||
tag := fmt.Sprintf("%s", repoId)
|
||||
|
||||
exts := []string{".js", ".py", ".ts", ".sh", ".php"}
|
||||
if repoCfg.Extensions != "" {
|
||||
customExts := splitKeywords(repoCfg.Extensions)
|
||||
if len(customExts) > 0 {
|
||||
exts = nil
|
||||
for _, e := range customExts {
|
||||
e = strings.TrimSpace(e)
|
||||
if e != "" {
|
||||
if !strings.HasPrefix(e, ".") {
|
||||
e = "." + e
|
||||
}
|
||||
exts = append(exts, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundSourceIDs := make(map[string]bool)
|
||||
|
||||
filepath.WalkDir(targetPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.Contains(path, ".git") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ext := filepath.Ext(path)
|
||||
validExt := false
|
||||
for _, e := range exts {
|
||||
if ext == e {
|
||||
validExt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validExt {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var taskName string
|
||||
var taskCron string
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
var firstCommentLine string
|
||||
inBlockComment := false
|
||||
|
||||
// 特殊处理:针对当前文件名的 Cron 关联正则表达式 (对标青龙 perl 逻辑)
|
||||
// 寻找类似 "// 0 0 * * * jd_task.js" 的行
|
||||
fileNameEscaped := regexp.QuoteMeta(filepath.Base(path))
|
||||
associatedCronRegex := regexp.MustCompile(fmt.Sprintf(`(?i)(?:^|[ \t\*\//])(([0-9\*\/\-,L?#]+[ \t]+){4,5}[0-9\*\/\-,L?#]+)[ \t,"]+.*%s`, fileNameEscaped))
|
||||
|
||||
for i := 0; i < 150 && scanner.Scan(); i++ { // QL 扫描范围较大
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 处理块注释开始/结束
|
||||
if strings.HasPrefix(line, "/*") {
|
||||
inBlockComment = true
|
||||
line = strings.TrimPrefix(line, "/*")
|
||||
line = strings.TrimPrefix(line, "*")
|
||||
line = strings.TrimSpace(line)
|
||||
}
|
||||
if strings.HasSuffix(line, "*/") {
|
||||
inBlockComment = false
|
||||
line = strings.TrimSuffix(line, "*/")
|
||||
line = strings.TrimSpace(line)
|
||||
}
|
||||
|
||||
// 1. 尝试提取任务名称 (优先使用 Env)
|
||||
if taskName == "" {
|
||||
if envMatch := envRegex.FindStringSubmatch(line); len(envMatch) > 1 {
|
||||
taskName = strings.TrimSpace(envMatch[1])
|
||||
} else if strings.Contains(line, "name:") {
|
||||
// 兼容 name: "xxx" 格式
|
||||
nameRegex := regexp.MustCompile(`(?i)name:[ \t]*['"]([^'"]+)['"]`)
|
||||
if nameMatch := nameRegex.FindStringSubmatch(line); len(nameMatch) > 1 {
|
||||
taskName = strings.TrimSpace(nameMatch[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果还没找到名称,且在注释中,记录第一行非空注释作为备选名称
|
||||
if taskName == "" && (inBlockComment || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "*") || strings.HasPrefix(line, "#")) {
|
||||
cleanLine := line
|
||||
if strings.HasPrefix(line, "//") {
|
||||
cleanLine = strings.TrimPrefix(line, "//")
|
||||
} else if strings.HasPrefix(line, "#") {
|
||||
cleanLine = strings.TrimPrefix(line, "#")
|
||||
} else if strings.HasPrefix(line, "*") {
|
||||
cleanLine = strings.TrimPrefix(line, "*")
|
||||
}
|
||||
cleanLine = strings.TrimSpace(cleanLine)
|
||||
|
||||
// 排除掉包含 "Env" 或 "cron" 的行, 且排除掉可能是路径或URL的行
|
||||
if cleanLine != "" && !strings.Contains(strings.ToLower(cleanLine), "env") &&
|
||||
!strings.Contains(strings.ToLower(cleanLine), "cron") &&
|
||||
!strings.Contains(cleanLine, "http") &&
|
||||
!strings.Contains(cleanLine, "/") &&
|
||||
firstCommentLine == "" {
|
||||
// 且排除掉纯 cron 表达式
|
||||
if !cronRegex.MatchString(cleanLine) {
|
||||
firstCommentLine = cleanLine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 提取 Cron
|
||||
if taskCron == "" {
|
||||
// A. 优先查找关联了当前文件名的 Cron (对标 QL)
|
||||
if assocMatch := associatedCronRegex.FindStringSubmatch(line); len(assocMatch) > 1 {
|
||||
taskCron = strings.TrimSpace(assocMatch[1])
|
||||
}
|
||||
|
||||
// B. 如果没找到,尝试普通的 cron: "..." 或 cron 表达式
|
||||
if taskCron == "" {
|
||||
if cronMatch := cronRegex.FindStringSubmatch(line); len(cronMatch) > 0 {
|
||||
for _, m := range cronMatch[1:] {
|
||||
if m != "" {
|
||||
taskCron = strings.TrimSpace(m)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if taskName != "" && taskCron != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 如果最后还是没找到 taskName,使用备选名称或文件名
|
||||
if taskName == "" {
|
||||
if firstCommentLine != "" {
|
||||
taskName = firstCommentLine
|
||||
} else {
|
||||
taskName = strings.TrimSuffix(filepath.Base(path), ext)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 应用白名单 / 黑名单过滤 (逻辑对标青龙)
|
||||
// relRepoPath 是相对于仓库根目录的路径,filename 是文件名
|
||||
relRepoPath, _ := filepath.Rel(targetPath, path)
|
||||
filename := filepath.Base(path)
|
||||
|
||||
// 只有在显式设置了白名单时才进行白名单校验 (青龙行为)
|
||||
if repoCfg.WhitelistPaths != "" {
|
||||
if !matchesQLPattern(relRepoPath, filename, repoCfg.WhitelistPaths) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 校验黑名单
|
||||
if repoCfg.Blacklist != "" {
|
||||
if matchesQLPattern(relRepoPath, filename, repoCfg.Blacklist) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if taskName != "" && taskCron != "" {
|
||||
// 获取脚本相对于数据目录的路径
|
||||
absScriptsDir, _ := filepath.Abs(constant.ScriptsWorkDir)
|
||||
absPath, _ := filepath.Abs(path)
|
||||
|
||||
// 计算 SourceID: 相对于脚本目录的完整路径,并清洗特殊符号
|
||||
relPath, _ := filepath.Rel(absScriptsDir, absPath)
|
||||
sourceID := sanitizeIdentifier(relPath)
|
||||
|
||||
// 替换绝对路径为代号 $SCRIPTS_DIR$
|
||||
displayPath := path
|
||||
displayWorkDir := targetPath
|
||||
if strings.HasPrefix(absPath, absScriptsDir) {
|
||||
displayPath = filepath.Join("$SCRIPTS_DIR$", relPath)
|
||||
// 获取目录路径
|
||||
relDir, _ := filepath.Rel(absScriptsDir, targetPath)
|
||||
displayWorkDir = filepath.Join("$SCRIPTS_DIR$", relDir)
|
||||
}
|
||||
|
||||
// Found task, save it
|
||||
command := getCommandByExt(ext, displayPath)
|
||||
|
||||
// 注册任务默认开启“全量环境变量注入”,以适配大多数脚本
|
||||
defaultTaskConfig := `{"$task_all_envs":true}`
|
||||
|
||||
// See if task exists (优先通过 SourceID 匹配)
|
||||
var existing models.Task
|
||||
err := database.DB.Where("source_id = ?", sourceID).First(&existing).Error
|
||||
if err != nil {
|
||||
// 降级使用 command + tag 匹配 (兼容旧数据)
|
||||
err = database.DB.Where("command = ? AND tags LIKE ?", command, "%"+tag+"%").First(&existing).Error
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// update
|
||||
existing.Name = taskName
|
||||
existing.Schedule = normalizeCron(taskCron)
|
||||
existing.Languages = repoTask.Languages
|
||||
existing.SourceID = sourceID
|
||||
existing.RepoTaskID = repoTask.ID
|
||||
existing.WorkDir = displayWorkDir
|
||||
// 如果原配置为空或者是 {},则应用默认配置
|
||||
if string(existing.Config) == "" || string(existing.Config) == "{}" {
|
||||
existing.Config = models.BigText(defaultTaskConfig)
|
||||
}
|
||||
database.DB.Save(&existing)
|
||||
|
||||
if existing.Enabled && es != nil {
|
||||
es.AddCronTask(&existing)
|
||||
}
|
||||
foundSourceIDs[sourceID] = true
|
||||
} else {
|
||||
// create new
|
||||
newTask := &models.Task{
|
||||
Name: taskName,
|
||||
Command: models.BigText(command),
|
||||
Schedule: normalizeCron(taskCron),
|
||||
Type: "task",
|
||||
TriggerType: constant.TriggerTypeCron,
|
||||
Tags: tag,
|
||||
Languages: repoTask.Languages,
|
||||
Timeout: repoTask.Timeout,
|
||||
Config: models.BigText(defaultTaskConfig),
|
||||
Enabled: true,
|
||||
WorkDir: displayWorkDir,
|
||||
SourceID: sourceID,
|
||||
RepoTaskID: repoTask.ID,
|
||||
}
|
||||
newTask.ID = utils.GenerateID()
|
||||
database.DB.Create(newTask)
|
||||
if es != nil {
|
||||
es.AddCronTask(newTask)
|
||||
}
|
||||
foundSourceIDs[sourceID] = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// 清理该仓库任务下不再存在的旧脚本任务
|
||||
var oldTasks []models.Task
|
||||
if err := database.DB.Where("repo_task_id = ?", repoTask.ID).Find(&oldTasks).Error; err == nil {
|
||||
for _, ot := range oldTasks {
|
||||
if !foundSourceIDs[ot.SourceID] {
|
||||
if es != nil {
|
||||
if es.taskService != nil {
|
||||
es.taskService.DeleteTask(ot.ID)
|
||||
}
|
||||
es.RemoveCronTask(ot.ID)
|
||||
} else {
|
||||
// Fallback if es is nil (which shouldn't happen, but just in case)
|
||||
database.DB.Unscoped().Where("id = ?", ot.ID).Delete(&models.Task{})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeIdentifier(s string) string {
|
||||
// 将所有非字母数字替换为下划线
|
||||
reg := regexp.MustCompile(`[^a-zA-Z0-9]+`)
|
||||
res := reg.ReplaceAllString(s, "_")
|
||||
return strings.ToLower(strings.Trim(res, "_"))
|
||||
}
|
||||
|
||||
func normalizeCron(cron string) string {
|
||||
fields := strings.Fields(cron)
|
||||
if len(fields) == 5 {
|
||||
return "0 " + cron
|
||||
}
|
||||
return cron
|
||||
}
|
||||
|
||||
func pathExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
func getCommandByExt(ext, path string) string {
|
||||
switch ext {
|
||||
case ".js", ".ts":
|
||||
return fmt.Sprintf("node %s", path)
|
||||
case ".py":
|
||||
return fmt.Sprintf("python %s", path)
|
||||
case ".sh":
|
||||
return fmt.Sprintf("bash %s", path)
|
||||
case ".php":
|
||||
return fmt.Sprintf("php %s", path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func matchesQLPattern(rel, filename string, keywordsStr string) bool {
|
||||
if keywordsStr == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
keywords := splitKeywords(keywordsStr)
|
||||
for _, k := range keywords {
|
||||
// 1. 尝试作为正则整体进行匹配,默认不区分大小写 (?i)
|
||||
pattern := k
|
||||
if !strings.HasPrefix(pattern, "(?i)") {
|
||||
pattern = "(?i)" + pattern
|
||||
}
|
||||
|
||||
reg, err := regexp.Compile(pattern)
|
||||
if err == nil {
|
||||
// 优先匹配文件名(解决 ^jd[^_] 这种锚点在相对路径下失效的问题)
|
||||
if reg.MatchString(filename) || reg.MatchString(rel) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// 回退逻辑:全小写包含判断
|
||||
kLower := strings.ToLower(k)
|
||||
if strings.Contains(strings.ToLower(rel), kLower) || strings.Contains(strings.ToLower(filename), kLower) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func splitKeywords(s string) []string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
var parts []string
|
||||
if strings.Contains(s, "|") {
|
||||
parts = strings.Split(s, "|")
|
||||
} else if strings.Contains(s, ",") {
|
||||
parts = strings.Split(s, ",")
|
||||
} else {
|
||||
parts = []string{s}
|
||||
}
|
||||
|
||||
var res []string
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
res = append(res, p)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -13,7 +13,15 @@ func NewTaskService() *TaskService {
|
||||
return &TaskService{}
|
||||
}
|
||||
|
||||
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 {
|
||||
func (ts *TaskService) GetTaskBySourceID(sourceID string) *models.Task {
|
||||
var task models.Task
|
||||
if err := database.DB.Where("source_id = ?", sourceID).First(&task).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
return &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, sourceID string) *models.Task {
|
||||
if taskType == "" {
|
||||
taskType = "task"
|
||||
}
|
||||
@@ -39,6 +47,7 @@ func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, w
|
||||
RetryCount: retryCount,
|
||||
RetryInterval: retryInterval,
|
||||
RandomRange: randomRange,
|
||||
SourceID: sourceID,
|
||||
CreatedAt: models.Now(),
|
||||
UpdatedAt: models.Now(),
|
||||
}
|
||||
@@ -88,7 +97,7 @@ func (ts *TaskService) GetTaskByID(id string) *models.Task {
|
||||
return &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 {
|
||||
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, sourceID string) *models.Task {
|
||||
var task models.Task
|
||||
if err := database.DB.Where("id = ?", id).First(&task).Error; err != nil {
|
||||
return nil
|
||||
@@ -114,12 +123,15 @@ func (ts *TaskService) UpdateTask(id string, name, command, schedule string, tim
|
||||
if triggerType != "" {
|
||||
task.TriggerType = triggerType
|
||||
}
|
||||
if sourceID != "" {
|
||||
task.SourceID = sourceID
|
||||
}
|
||||
|
||||
database.DB.Model(&task).Select(
|
||||
"Name", "Command", "Tags", "Schedule", "Timeout", "WorkDir",
|
||||
"CleanConfig", "Envs", "Enabled", "AgentID", "Languages",
|
||||
"RetryCount", "RetryInterval", "RandomRange", "Type",
|
||||
"TriggerType", "Config",
|
||||
"TriggerType", "Config", "SourceID",
|
||||
).Updates(&task)
|
||||
return &task
|
||||
}
|
||||
@@ -131,3 +143,11 @@ func (ts *TaskService) DeleteTask(id string) bool {
|
||||
result := database.DB.Unscoped().Where("id = ?", id).Delete(&models.Task{})
|
||||
return result.RowsAffected > 0
|
||||
}
|
||||
|
||||
func (ts *TaskService) BatchDeleteTasks(ids []string) int64 {
|
||||
// 同时删除关联的通知推送设置
|
||||
database.DB.Where("type = ? AND data_id IN ?", constant.BindingTypeTask, ids).Delete(&models.NotifyBinding{})
|
||||
|
||||
result := database.DB.Unscoped().Where("id IN ?", ids).Delete(&models.Task{})
|
||||
return result.RowsAffected
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user