Files
TaskPool/cmd/task/task.go
T
2026-05-14 09:55:35 +08:00

342 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package task
import (
"flag"
"fmt"
"os"
"strings"
"github.com/engigu/baihu-panel/cmd/clibase"
"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"
)
// 打印主帮助
func printMainHelp() {
fmt.Fprintf(os.Stderr, "\n白虎面板任务命令行管理工具 (Task CLI)\n\n")
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu task <子命令> [参数]\n\n")
fmt.Fprintf(os.Stderr, "可用子命令:\n")
fmt.Fprintf(os.Stderr, " list 查询并输出任务列表\n")
fmt.Fprintf(os.Stderr, " run 手动立即触发执行指定的任务\n")
fmt.Fprintf(os.Stderr, " enable 启用指定的任务(同步加入后台调度队列)\n")
fmt.Fprintf(os.Stderr, " disable 禁用指定的任务(同步从后台调度队列摘除)\n")
fmt.Fprintf(os.Stderr, " status 查看指定任务最近一次执行的完整输出与状态\n")
fmt.Fprintf(os.Stderr, " history 查看指定任务近期的多次执行流水记录\n\n")
fmt.Fprintf(os.Stderr, "使用 'baihu task <子命令> --help' 查看具体子命令的参数说明和示例。\n\n")
}
// Run 任务命令行入口
func Run(args []string) {
if len(args) == 0 || args[0] == "-h" || args[0] == "--help" {
printMainHelp()
return
}
subCommand := args[0]
subArgs := args[1:]
switch subCommand {
case "list":
runList(subArgs)
case "run":
runExecute(subArgs)
case "enable", "disable":
runToggle(subCommand, subArgs)
case "status":
runStatus(subArgs)
case "history":
runHistory(subArgs)
default:
fmt.Fprintf(os.Stderr, "未知子命令: %s\n", subCommand)
printMainHelp()
}
}
func runList(args []string) {
fs := flag.NewFlagSet("list", flag.ExitOnError)
namePtr := fs.String("name", "", "按任务名称或备注进行模糊筛选")
typePtr := fs.String("type", "", "按任务类型筛选 (例如: task, repo)")
pagePtr := fs.Int("page", 1, "查询页码")
sizePtr := fs.Int("size", 20, "每页展示条数")
fs.Usage = func() {
clibase.PrintSubCommandUsage("白虎面板任务列表查询工具", "baihu task list [参数]", " baihu task list\n baihu task list -page 2 -size 10\n baihu task list -name \"签到\"", fs)
}
if err := fs.Parse(args); err != nil {
return
}
clibase.InitContext(false)
var total int64
query := database.DB.Model(&models.Task{})
if *namePtr != "" {
query = query.Where("name LIKE ? OR remark LIKE ?", "%"+*namePtr+"%", "%"+*namePtr+"%")
}
if *typePtr != "" {
query = query.Where("type = ?", *typePtr)
}
query.Count(&total)
offset := (*pagePtr - 1) * *sizePtr
if offset < 0 {
offset = 0
}
var tasks []models.Task
query.Order("created_at DESC").Limit(*sizePtr).Offset(offset).Find(&tasks)
fmt.Println("====================================================================================================")
fmt.Printf("%-12s | %-25s | %-18s | %-8s | %-6s\n", "任务ID", "任务名称", "Cron规则", "类型", "状态")
fmt.Println("----------------------------------------------------------------------------------------------------")
for _, t := range tasks {
name := t.Name
// 截断过长名称以对齐
if len(name) > 22 {
name = string([]rune(name)[:20]) + ".."
}
cron := t.Schedule
if cron == "" {
cron = "-"
}
status := "启用"
if !utils.DerefBool(t.Enabled, true) {
status = "禁用"
}
fmt.Printf("%-12s | %-25s | %-18s | %-8s | %-6s\n", t.ID, name, cron, t.Type, status)
}
fmt.Println("====================================================================================================")
totalPages := (total + int64(*sizePtr) - 1) / int64(*sizePtr)
if totalPages == 0 {
totalPages = 1
}
fmt.Printf("共查询到 %d 个任务记录,当前展示第 %d/%d 页 (每页 %d 条)。\n", total, *pagePtr, totalPages, *sizePtr)
fmt.Printf("提示: 追加参数 (例如 '-page 2 -size 50') 即可灵活查看指定页码或调整展示数量。\n")
}
func runExecute(args []string) {
fs := flag.NewFlagSet("run", flag.ExitOnError)
fs.Usage = func() {
clibase.PrintSubCommandUsage("白虎面板手动任务触发工具", "baihu task run <任务ID>", " baihu task run a1b2c3d4", nil)
}
if err := fs.Parse(args); err != nil {
return
}
parsedArgs := fs.Args()
if len(parsedArgs) < 1 {
fmt.Fprintf(os.Stderr, "错误: 缺少目标任务ID。\n")
fs.Usage()
return
}
taskID := parsedArgs[0]
clibase.InitContext(false)
_, err := clibase.CallInternalAPI("POST", "/internal/tasks/execute/"+taskID, map[string]interface{}{})
if err != nil {
fmt.Printf(">> 任务触发失败: %v\n", err)
return
}
fmt.Printf(">> 任务 [%s] 触发指令下发成功!已进入后台调度队列排队或执行。\n", taskID)
fmt.Printf(">> 提示: 可以使用 'baihu task status %s' 查看近期执行输出。\n", taskID)
}
func runToggle(action string, args []string) {
fs := flag.NewFlagSet(action, flag.ExitOnError)
actionName := "启用"
targetEnabled := true
if action == "disable" {
actionName = "禁用"
targetEnabled = false
}
fs.Usage = func() {
clibase.PrintSubCommandUsage(fmt.Sprintf("白虎面板任务%s工具", actionName), fmt.Sprintf("baihu task %s <任务ID>", action), fmt.Sprintf(" baihu task %s a1b2c3d4", action), nil)
}
if err := fs.Parse(args); err != nil {
return
}
parsedArgs := fs.Args()
if len(parsedArgs) < 1 {
fmt.Fprintf(os.Stderr, "错误: 缺少目标任务ID。\n")
fs.Usage()
return
}
taskID := parsedArgs[0]
clibase.InitContext(false)
_, err := clibase.CallInternalAPI("POST", "/internal/tasks/toggle/"+taskID, map[string]interface{}{
"enabled": targetEnabled,
})
if err != nil {
fmt.Printf(">> 切换状态操作失败: %v\n", err)
return
}
fmt.Printf(">> 任务 [%s] 已成功%s\n", taskID, actionName)
}
func runStatus(args []string) {
fs := flag.NewFlagSet("status", flag.ExitOnError)
fs.Usage = func() {
clibase.PrintSubCommandUsage("白虎面板任务执行状态与日志查看工具", "baihu task status <任务ID> [日志ID]", " baihu task status a1b2c3d4\n baihu task status a1b2c3d4 log_123456", nil)
}
if err := fs.Parse(args); err != nil {
return
}
parsedArgs := fs.Args()
if len(parsedArgs) < 1 {
fmt.Fprintf(os.Stderr, "错误: 缺少目标任务ID。\n")
fs.Usage()
return
}
taskID := parsedArgs[0]
var specificLogID string
if len(parsedArgs) > 1 {
specificLogID = parsedArgs[1]
}
clibase.InitContext(false)
var taskLog models.TaskLog
query := database.DB.Where("task_id = ?", taskID)
if specificLogID != "" {
query = query.Where("id = ?", specificLogID)
}
res := query.Order("created_at DESC").Limit(1).Find(&taskLog)
if res.Error != nil || res.RowsAffected == 0 {
if specificLogID != "" {
fmt.Printf("找不到任务 [%s] 指定日志ID [%s] 的记录。\n", taskID, specificLogID)
} else {
fmt.Printf("找不到任务 [%s] 的任何执行记录。\n", taskID)
}
return
}
var task models.Task
database.DB.Where("id = ?", taskID).Limit(1).Find(&task)
taskName := taskID
if task.Name != "" {
taskName = task.Name
}
statusText := "运行中"
switch taskLog.Status {
case constant.TaskStatusSuccess:
statusText = "成功"
case constant.TaskStatusFailed:
statusText = "失败"
case constant.TaskStatusTimeout:
statusText = "超时"
case constant.TaskStatusCancelled:
statusText = "已取消"
}
fmt.Println("====================================================================================================")
fmt.Printf("任务名称: %s (ID: %s)\n", taskName, taskID)
fmt.Printf("日志记录: %s\n", taskLog.ID)
fmt.Printf("执行命令: %s\n", string(taskLog.Command))
fmt.Printf("最终状态: %s (耗时: %d 毫秒, 退出码: %d)\n", statusText, taskLog.Duration, taskLog.ExitCode)
if taskLog.StartTime != nil {
fmt.Printf("开始时间: %s\n", taskLog.StartTime.Time().Format("2006-01-02 15:04:05"))
}
fmt.Println("----------------------------------------------------------------------------------------------------")
fmt.Println("[日志输出内容]")
// 解压
decompressed, err := utils.DecompressFromBase64(string(taskLog.Output))
if err != nil {
fmt.Printf("[无法解压日志输出: %v]\n", err)
} else {
// 清理多余回车和终端 ANSI 转义字符
cleanText := strings.ReplaceAll(decompressed, "\r\n", "\n")
cleanText = clibase.AnsiRegex.ReplaceAllString(cleanText, "")
fmt.Println(strings.TrimSpace(cleanText))
}
if string(taskLog.Error) != "" {
fmt.Println("\n[系统捕获异常]")
fmt.Println(string(taskLog.Error))
}
fmt.Println("====================================================================================================")
}
func runHistory(args []string) {
fs := flag.NewFlagSet("history", flag.ExitOnError)
limitPtr := fs.Int("limit", 10, "展示的最近历史记录条数")
fs.Usage = func() {
clibase.PrintSubCommandUsage("白虎面板任务执行历史查看工具", "baihu task history <任务ID> [参数]", " baihu task history a1b2c3d4\n baihu task history a1b2c3d4 -limit 20", fs)
}
if err := fs.Parse(args); err != nil {
return
}
parsedArgs := fs.Args()
if len(parsedArgs) < 1 {
fmt.Fprintf(os.Stderr, "错误: 缺少目标任务ID。\n")
fs.Usage()
return
}
taskID := parsedArgs[0]
clibase.InitContext(false)
var task models.Task
database.DB.Where("id = ?", taskID).Limit(1).Find(&task)
taskName := taskID
if task.Name != "" {
taskName = task.Name
}
var logs []models.TaskLog
database.DB.Where("task_id = ?", taskID).Order("created_at DESC").Limit(*limitPtr).Find(&logs)
fmt.Println("====================================================================================================")
fmt.Printf("任务流水: %s (ID: %s) 的近期执行记录 (最多展示 %d 条)\n", taskName, taskID, *limitPtr)
fmt.Println("----------------------------------------------------------------------------------------------------")
fmt.Printf("%-20s | %-8s | %-6s | %-12s | %-20s\n", "日志ID", "状态", "退出码", "耗时", "开始时间")
fmt.Println("----------------------------------------------------------------------------------------------------")
if len(logs) == 0 {
fmt.Println("未查询到任何历史执行记录。")
} else {
for _, l := range logs {
statusText := "运行中"
switch l.Status {
case constant.TaskStatusSuccess:
statusText = "成功"
case constant.TaskStatusFailed:
statusText = "失败"
case constant.TaskStatusTimeout:
statusText = "超时"
case constant.TaskStatusCancelled:
statusText = "已取消"
}
startStr := "-"
if l.StartTime != nil {
startStr = l.StartTime.Time().Format("2006-01-02 15:04:05")
}
durationStr := fmt.Sprintf("%d ms", l.Duration)
fmt.Printf("%-20s | %-8s | %-6d | %-12s | %-20s\n", l.ID, statusText, l.ExitCode, durationStr, startStr)
}
}
fmt.Println("====================================================================================================")
fmt.Printf("提示: 结合命令 'baihu task status %s <日志ID>' 查看特定历史日志内容。\n", taskID)
}