定时任务添加http模式
This commit is contained in:
+38
-18
@@ -83,7 +83,10 @@ func autoInitDB(ctx context.Context) {
|
||||
group_id INTEGER NOT NULL DEFAULT 0, server_ids TEXT NOT NULL DEFAULT '',
|
||||
server_type INTEGER NOT NULL DEFAULT 0, task_name TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT '', cron_spec TEXT NOT NULL DEFAULT '',
|
||||
concurrent INTEGER NOT NULL DEFAULT 0, command TEXT NOT NULL DEFAULT '',
|
||||
concurrent INTEGER NOT NULL DEFAULT 0, task_type TEXT NOT NULL DEFAULT 'shell',
|
||||
command TEXT NOT NULL DEFAULT '', url TEXT NOT NULL DEFAULT '',
|
||||
method TEXT NOT NULL DEFAULT 'GET', headers TEXT NOT NULL DEFAULT '',
|
||||
body TEXT NOT NULL DEFAULT '',
|
||||
timeout INTEGER NOT NULL DEFAULT 0, execute_times INTEGER NOT NULL DEFAULT 0,
|
||||
prev_time INTEGER NOT NULL DEFAULT 0, status INTEGER NOT NULL DEFAULT 2,
|
||||
is_notify INTEGER NOT NULL DEFAULT 0, notify_type INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -119,6 +122,20 @@ func autoInitDB(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移:为旧表添加新列(忽略重复添加的错误)
|
||||
migrations := []string{
|
||||
"ALTER TABLE pp_task ADD COLUMN task_type TEXT NOT NULL DEFAULT 'shell'",
|
||||
"ALTER TABLE pp_task ADD COLUMN url TEXT NOT NULL DEFAULT ''",
|
||||
"ALTER TABLE pp_task ADD COLUMN method TEXT NOT NULL DEFAULT 'GET'",
|
||||
"ALTER TABLE pp_task ADD COLUMN headers TEXT NOT NULL DEFAULT ''",
|
||||
"ALTER TABLE pp_task ADD COLUMN body TEXT NOT NULL DEFAULT ''",
|
||||
}
|
||||
for _, sql := range migrations {
|
||||
if _, err := g.DB().Exec(ctx, sql); err != nil {
|
||||
// 列已存在则忽略错误
|
||||
}
|
||||
}
|
||||
|
||||
var adminCount int
|
||||
_ = g.DB().GetScan(ctx, &adminCount, "SELECT COUNT(*) FROM pp_uc_admin WHERE id=?", 1)
|
||||
if adminCount == 0 {
|
||||
@@ -138,26 +155,29 @@ func autoInitDB(ctx context.Context) {
|
||||
|
||||
func seedAuth(ctx context.Context) {
|
||||
now := time.Now().Unix()
|
||||
type row struct{ id, pid, sort, isShow int; name, url, icon string }
|
||||
type row struct {
|
||||
id, pid, sort, isShow int
|
||||
name, url, icon string
|
||||
}
|
||||
rows := []row{
|
||||
// pid=0 = 一级菜单
|
||||
{1, 0, 1, 1, "任务管理", " ", "fa-tasks"},
|
||||
{2, 0, 2, 1, "服务器管理", " ", "fa-server"},
|
||||
{3, 0, 3, 1, "系统设置", " ", "fa-cog"},
|
||||
{4, 0, 4, 1, "日志管理", " ", "fa-file-text"},
|
||||
{5, 0, 5, 1, "权限管理", " ", "fa-lock"},
|
||||
{1, 0, 1, 1, "任务管理", " ", "fa-tasks"},
|
||||
{2, 0, 2, 1, "服务器管理", " ", "fa-server"},
|
||||
{3, 0, 3, 1, "系统设置", " ", "fa-cog"},
|
||||
{4, 0, 4, 1, "日志管理", " ", "fa-file-text"},
|
||||
{5, 0, 5, 1, "权限管理", " ", "fa-lock"},
|
||||
// 二级菜单(pid=父菜单的id)
|
||||
{14, 1, 1, 1, "任务列表", "/task/list", "fa-tasks"},
|
||||
{15, 1, 2, 1, "任务审核", "/task/audit_list", "fa-check"},
|
||||
{16, 2, 1, 1, "服务器列表", "/server/list", "fa-server"},
|
||||
{6, 3, 1, 1, "任务分组", "/group/list", "fa-folder"},
|
||||
{7, 3, 2, 1, "资源分组", "/server_group/list","fa-sitemap"},
|
||||
{8, 3, 3, 1, "禁用命令", "/ban/list", "fa-ban"},
|
||||
{9, 3, 4, 1, "通知模板", "/notify_tpl/list", "fa-bullhorn"},
|
||||
{10, 4, 1, 1, "执行日志", "/task_log/list", "fa-file"},
|
||||
{11, 5, 1, 1, "权限因子", "/auth/index", "fa-key"},
|
||||
{12, 5, 2, 1, "角色管理", "/role/list", "fa-group"},
|
||||
{13, 5, 3, 1, "管理员管理","/admin/list", "fa-user"},
|
||||
{14, 1, 1, 1, "任务列表", "/task/list", "fa-tasks"},
|
||||
{15, 1, 2, 1, "任务审核", "/task/audit_list", "fa-check"},
|
||||
{16, 2, 1, 1, "服务器列表", "/server/list", "fa-server"},
|
||||
{6, 3, 1, 1, "任务分组", "/group/list", "fa-folder"},
|
||||
{7, 3, 2, 1, "资源分组", "/server_group/list", "fa-sitemap"},
|
||||
{8, 3, 3, 1, "禁用命令", "/ban/list", "fa-ban"},
|
||||
{9, 3, 4, 1, "通知模板", "/notify_tpl/list", "fa-bullhorn"},
|
||||
{10, 4, 1, 1, "执行日志", "/task_log/list", "fa-file"},
|
||||
{11, 5, 1, 1, "权限因子", "/auth/index", "fa-key"},
|
||||
{12, 5, 2, 1, "角色管理", "/role/list", "fa-group"},
|
||||
{13, 5, 3, 1, "管理员管理", "/admin/list", "fa-user"},
|
||||
}
|
||||
for _, r := range rows {
|
||||
g.DB().Exec(ctx,
|
||||
|
||||
+44
-10
@@ -84,7 +84,7 @@ func TaskTable(r *ghttp.Request) {
|
||||
if t.UpdateTime > 0 {
|
||||
ut = time.Unix(t.UpdateTime, 0).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
rows = append(rows, g.Map{"id": t.Id, "task_name": t.TaskName, "group_id": t.GroupId, "cron_spec": t.CronSpec, "command": t.Command, "status": t.Status, "status_text": st, "prev_time": t.PrevTime, "prev_time_str": pt, "next_time": nt, "execute_times": t.ExecuteTimes, "server_ids": t.ServerIds, "create_time_str": ct, "update_time_str": ut})
|
||||
rows = append(rows, g.Map{"id": t.Id, "task_name": t.TaskName, "group_id": t.GroupId, "cron_spec": t.CronSpec, "task_type": t.TaskType, "command": t.Command, "url": t.Url, "method": t.Method, "headers": t.Headers, "body": t.Body, "status": t.Status, "status_text": st, "prev_time": t.PrevTime, "prev_time_str": pt, "next_time": nt, "execute_times": t.ExecuteTimes, "server_ids": t.ServerIds, "create_time_str": ct, "update_time_str": ut})
|
||||
}
|
||||
ajaxList(r, "成功", 0, total, rows)
|
||||
}
|
||||
@@ -94,7 +94,12 @@ func TaskAjaxSave(r *ghttp.Request) {
|
||||
id := r.Get("id", 0).Int()
|
||||
taskName := r.Get("task_name").String()
|
||||
cronSpec := r.Get("cron_spec").String()
|
||||
taskType := r.Get("task_type", "shell").String()
|
||||
command := r.Get("command").String()
|
||||
url := r.Get("url").String()
|
||||
method := r.Get("method", "GET").String()
|
||||
headers := r.Get("headers").String()
|
||||
body := r.Get("body").String()
|
||||
groupId := r.Get("group_id", 0).Int()
|
||||
serverIds := r.Get("server_ids").String()
|
||||
serverType := r.Get("server_type", 0).Int()
|
||||
@@ -105,20 +110,31 @@ func TaskAjaxSave(r *ghttp.Request) {
|
||||
notifyTplId := r.Get("notify_tpl_id", 0).Int()
|
||||
notifyUserIds := r.Get("notify_user_ids").String()
|
||||
description := r.Get("description").String()
|
||||
if taskName == "" || cronSpec == "" || command == "" {
|
||||
ajaxMsg(r, "任务名称、Cron表达式、命令不能为空", consts.MsgErr)
|
||||
if taskName == "" || cronSpec == "" {
|
||||
ajaxMsg(r, "任务名称、Cron表达式不能为空", consts.MsgErr)
|
||||
return
|
||||
}
|
||||
if taskType == "http" {
|
||||
if url == "" {
|
||||
ajaxMsg(r, "URL不能为空", consts.MsgErr)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if command == "" {
|
||||
ajaxMsg(r, "命令不能为空", consts.MsgErr)
|
||||
return
|
||||
}
|
||||
if err := scheduler.Scheduler.CheckCommand(ctx, command); err != nil {
|
||||
ajaxMsg(r, err.Error(), consts.MsgErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 验证 Cron 表达式(临时注册再移除,仅用于校验格式)
|
||||
if _, err := gcron.AddSingleton(ctx, cronSpec, func(ctx context.Context) {}, "cron_validator"); err != nil {
|
||||
ajaxMsg(r, "Cron表达式格式错误: "+err.Error(), consts.MsgErr)
|
||||
return
|
||||
}
|
||||
gcron.Remove("cron_validator")
|
||||
if err := scheduler.Scheduler.CheckCommand(ctx, command); err != nil {
|
||||
ajaxMsg(r, err.Error(), consts.MsgErr)
|
||||
return
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
userId := r.GetCtxVar("userId", 0).Int()
|
||||
if id > 0 {
|
||||
@@ -134,7 +150,12 @@ func TaskAjaxSave(r *ghttp.Request) {
|
||||
task.ServerType = serverType
|
||||
task.CronSpec = cronSpec
|
||||
task.Concurrent = concurrent
|
||||
task.TaskType = taskType
|
||||
task.Command = command
|
||||
task.Url = url
|
||||
task.Method = method
|
||||
task.Headers = headers
|
||||
task.Body = body
|
||||
task.Timeout = timeout
|
||||
task.IsNotify = isNotify
|
||||
task.NotifyType = notifyType
|
||||
@@ -158,7 +179,9 @@ func TaskAjaxSave(r *ghttp.Request) {
|
||||
t := &entity.Task{
|
||||
TaskName: taskName, Description: description, GroupId: groupId,
|
||||
ServerIds: serverIds, ServerType: serverType, CronSpec: cronSpec,
|
||||
Concurrent: concurrent, Command: command, Timeout: timeout,
|
||||
Concurrent: concurrent, TaskType: taskType,
|
||||
Command: command, Url: url, Method: method,
|
||||
Headers: headers, Body: body, Timeout: timeout,
|
||||
Status: status, IsNotify: isNotify, NotifyType: notifyType,
|
||||
NotifyTplId: notifyTplId, NotifyUserIds: notifyUserIds,
|
||||
CreateId: userId, UpdateId: userId, CreateTime: now, UpdateTime: now,
|
||||
@@ -493,6 +516,16 @@ func TaskLogTable(r *ghttp.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// 构建任务ID→名称映射
|
||||
taskNameMap := make(map[int]string)
|
||||
for _, l := range list {
|
||||
if _, ok := taskNameMap[l.TaskId]; !ok {
|
||||
if t, _ := dao.Task.GetById(ctx, l.TaskId); t != nil {
|
||||
taskNameMap[l.TaskId] = t.TaskName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rows := make([]g.Map, 0)
|
||||
for _, l := range list {
|
||||
st := "成功"
|
||||
@@ -511,8 +544,9 @@ func TaskLogTable(r *ghttp.Request) {
|
||||
sizeText = fmt.Sprintf("%.1f KB", float64(outputSize)/1024)
|
||||
}
|
||||
rows = append(rows, g.Map{
|
||||
"id": l.Id, "task_id": l.TaskId, "server_name": l.ServerName,
|
||||
"status": l.Status, "statusText": st,
|
||||
"id": l.Id, "task_id": l.TaskId, "task_name": taskNameMap[l.TaskId],
|
||||
"server_name": l.ServerName,
|
||||
"status": l.Status, "statusText": st,
|
||||
"process_time": l.ProcessTime,
|
||||
"start_time": startTime,
|
||||
"output_size": sizeText,
|
||||
|
||||
+155
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -5,8 +5,8 @@
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>定时任务管理器</title>
|
||||
<script type="module" crossorigin src="/app/assets/index-LEpjxP6Q.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/app/assets/index-CDatsf8P.css">
|
||||
<script type="module" crossorigin src="/app/assets/index-9ppaOJEB.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/app/assets/index-PhAJ6uiJ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -40,9 +40,38 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="命令脚本" prop="command">
|
||||
<el-form-item label="任务类型">
|
||||
<el-radio-group v-model="form.task_type">
|
||||
<el-radio value="shell">Shell 命令</el-radio>
|
||||
<el-radio value="http">HTTP 请求</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.task_type === 'shell'" label="命令脚本" prop="command">
|
||||
<el-input v-model="form.command" type="textarea" :rows="4" />
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="form.task_type === 'http'">
|
||||
<el-form-item label="请求 URL" prop="url">
|
||||
<el-input v-model="form.url" placeholder="https://example.com/api" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求方法">
|
||||
<el-select v-model="form.method" style="width: 140px">
|
||||
<el-option label="GET" value="GET" />
|
||||
<el-option label="POST" value="POST" />
|
||||
<el-option label="PUT" value="PUT" />
|
||||
<el-option label="DELETE" value="DELETE" />
|
||||
<el-option label="PATCH" value="PATCH" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求头">
|
||||
<el-input v-model="form.headers" type="textarea" :rows="3" placeholder='{"Content-Type": "application/json"}' />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求体">
|
||||
<el-input v-model="form.body" type="textarea" :rows="4" placeholder='{"key": "value"}' />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="超时设置">
|
||||
<el-input-number v-model="form.timeout" :min="0" :max="86400" /> 秒
|
||||
</el-form-item>
|
||||
@@ -96,7 +125,8 @@ const submitting = ref(false)
|
||||
|
||||
const form = reactive<any>({
|
||||
id: 0, task_name: '', group_id: '', server_ids: [], server_type: 0,
|
||||
description: '', concurrent: 0, cron_spec: '', command: '',
|
||||
description: '', concurrent: 0, cron_spec: '', task_type: 'shell',
|
||||
command: '', url: '', method: 'GET', headers: '', body: '',
|
||||
timeout: 0, is_notify: 0, notify_type: 0, notify_tpl_id: 0,
|
||||
notify_user_ids: [],
|
||||
})
|
||||
@@ -108,7 +138,6 @@ const formData = reactive<any>({
|
||||
const rules = {
|
||||
task_name: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
|
||||
cron_spec: [{ required: true, message: '请输入Cron表达式', trigger: 'blur' }],
|
||||
command: [{ required: true, message: '请输入命令', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
async function loadFormData() {
|
||||
@@ -124,7 +153,10 @@ async function loadFormData() {
|
||||
server_ids: d.task.serverIds ? String(d.task.serverIds).split(',') : [],
|
||||
server_type: d.task.serverType, description: d.task.description,
|
||||
concurrent: d.task.concurrent, cron_spec: d.task.cronSpec,
|
||||
command: d.task.command, timeout: d.task.timeout,
|
||||
task_type: d.task.taskType || 'shell',
|
||||
command: d.task.command, url: d.task.url || '',
|
||||
method: d.task.method || 'GET', headers: d.task.headers || '',
|
||||
body: d.task.body || '', timeout: d.task.timeout,
|
||||
is_notify: d.task.isNotify, notify_type: d.task.notifyType,
|
||||
notify_tpl_id: d.task.notifyTplId,
|
||||
notify_user_ids: d.task.notifyUserIds ? String(d.task.notifyUserIds).split(',') : [],
|
||||
@@ -140,6 +172,14 @@ async function loadFormData() {
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (form.task_type === 'shell' && !form.command) {
|
||||
ElMessage.error('请输入命令')
|
||||
return
|
||||
}
|
||||
if (form.task_type === 'http' && !form.url) {
|
||||
ElMessage.error('请输入URL')
|
||||
return
|
||||
}
|
||||
const valid = await formRef.value.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
submitting.value = true
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<el-table-column prop="id" label="ID" width="60" sortable="custom" />
|
||||
<el-table-column prop="task_name" label="任务名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="cron_spec" label="时间表达式" width="110" />
|
||||
<el-table-column prop="task_type" label="类型" width="80" />
|
||||
<el-table-column label="下次执行" width="150"><template #default="{ row }"><span v-if="row.next_time">{{ row.next_time }}</span><span v-else class="text-muted">-</span></template></el-table-column>
|
||||
<el-table-column label="上次执行" width="150"><template #default="{ row }">{{ row.prev_time_str || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="status_text" label="状态" width="80" />
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<el-table :data="list" v-loading="loading" stripe size="small" @selection-change="rows => selectedIds = rows.map(r => r.id)">
|
||||
<el-table-column type="selection" width="45" />
|
||||
<el-table-column prop="task_id" label="任务ID" width="70" />
|
||||
<el-table-column prop="task_name" label="任务名称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="server_name" label="服务器" />
|
||||
<el-table-column prop="start_time" label="开始时间" width="160" />
|
||||
<el-table-column prop="process_time" label="执行耗时" width="80" />
|
||||
|
||||
+36
-25
@@ -10,7 +10,12 @@ type Task struct {
|
||||
Description string `orm:"description" json:"description"`
|
||||
CronSpec string `orm:"cron_spec" json:"cronSpec"`
|
||||
Concurrent int `orm:"concurrent" json:"concurrent"`
|
||||
TaskType string `orm:"task_type" json:"taskType"`
|
||||
Command string `orm:"command" json:"command"`
|
||||
Url string `orm:"url" json:"url"`
|
||||
Method string `orm:"method" json:"method"`
|
||||
Headers string `orm:"headers" json:"headers"`
|
||||
Body string `orm:"body" json:"body"`
|
||||
Timeout int `orm:"timeout" json:"timeout"`
|
||||
ExecuteTimes int `orm:"execute_times" json:"executeTimes"`
|
||||
PrevTime int64 `orm:"prev_time" json:"prevTime"`
|
||||
@@ -27,31 +32,37 @@ type Task struct {
|
||||
|
||||
// TaskCols 字段常量
|
||||
var TaskCols = struct {
|
||||
Id, GroupId, ServerIds, ServerType, TaskName string
|
||||
Description, CronSpec, Concurrent, Command string
|
||||
Timeout, ExecuteTimes, PrevTime, Status string
|
||||
Id, GroupId, ServerIds, ServerType, TaskName string
|
||||
Description, CronSpec, Concurrent, TaskType string
|
||||
Command, Url, Method, Headers, Body string
|
||||
Timeout, ExecuteTimes, PrevTime, Status string
|
||||
IsNotify, NotifyType, NotifyTplId, NotifyUserIds string
|
||||
CreateId, UpdateId, CreateTime, UpdateTime string
|
||||
CreateId, UpdateId, CreateTime, UpdateTime string
|
||||
}{
|
||||
Id: "id",
|
||||
GroupId: "group_id",
|
||||
ServerIds: "server_ids",
|
||||
ServerType: "server_type",
|
||||
TaskName: "task_name",
|
||||
Description: "description",
|
||||
CronSpec: "cron_spec",
|
||||
Concurrent: "concurrent",
|
||||
Command: "command",
|
||||
Timeout: "timeout",
|
||||
ExecuteTimes: "execute_times",
|
||||
PrevTime: "prev_time",
|
||||
Status: "status",
|
||||
IsNotify: "is_notify",
|
||||
NotifyType: "notify_type",
|
||||
NotifyTplId: "notify_tpl_id",
|
||||
NotifyUserIds: "notify_user_ids",
|
||||
CreateId: "create_id",
|
||||
UpdateId: "update_id",
|
||||
CreateTime: "create_time",
|
||||
UpdateTime: "update_time",
|
||||
Id: "id",
|
||||
GroupId: "group_id",
|
||||
ServerIds: "server_ids",
|
||||
ServerType: "server_type",
|
||||
TaskName: "task_name",
|
||||
Description: "description",
|
||||
CronSpec: "cron_spec",
|
||||
Concurrent: "concurrent",
|
||||
TaskType: "task_type",
|
||||
Command: "command",
|
||||
Url: "url",
|
||||
Method: "method",
|
||||
Headers: "headers",
|
||||
Body: "body",
|
||||
Timeout: "timeout",
|
||||
ExecuteTimes: "execute_times",
|
||||
PrevTime: "prev_time",
|
||||
Status: "status",
|
||||
IsNotify: "is_notify",
|
||||
NotifyType: "notify_type",
|
||||
NotifyTplId: "notify_tpl_id",
|
||||
NotifyUserIds: "notify_user_ids",
|
||||
CreateId: "create_id",
|
||||
UpdateId: "update_id",
|
||||
CreateTime: "create_time",
|
||||
UpdateTime: "update_time",
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -31,28 +35,35 @@ func (s *schedulerService) executeTask(ctx context.Context, task *entity.Task) {
|
||||
serverName := "本地服务器"
|
||||
var serverId int
|
||||
|
||||
serverIds := strings.Split(task.ServerIds, ",")
|
||||
for _, sid := range serverIds {
|
||||
sid = strings.TrimSpace(sid)
|
||||
if sid == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var result *TaskResult
|
||||
if sid == "0" {
|
||||
serverName = "本地服务器"
|
||||
serverId = 0
|
||||
result = s.execLocal(ctx, task.Command, task.Timeout)
|
||||
} else {
|
||||
if srv, _ := dao.TaskServer.GetById(ctx, parseInt(sid)); srv != nil {
|
||||
serverName = srv.ServerName
|
||||
serverId = srv.Id
|
||||
if task.TaskType == "http" {
|
||||
// HTTP 任务:直接从调度器所在机器发起请求
|
||||
result := s.execHTTP(ctx, task)
|
||||
finalResult = result
|
||||
} else {
|
||||
// Shell 任务:按原有逻辑执行(本地/远程)
|
||||
serverIds := strings.Split(task.ServerIds, ",")
|
||||
for _, sid := range serverIds {
|
||||
sid = strings.TrimSpace(sid)
|
||||
if sid == "" {
|
||||
continue
|
||||
}
|
||||
result = s.execRemote(ctx, task.Command, task.Timeout, parseInt(sid))
|
||||
}
|
||||
|
||||
if finalResult == nil || !result.IsOk {
|
||||
finalResult = result
|
||||
var result *TaskResult
|
||||
if sid == "0" {
|
||||
serverName = "本地服务器"
|
||||
serverId = 0
|
||||
result = s.execLocal(ctx, task.Command, task.Timeout)
|
||||
} else {
|
||||
if srv, _ := dao.TaskServer.GetById(ctx, parseInt(sid)); srv != nil {
|
||||
serverName = srv.ServerName
|
||||
serverId = srv.Id
|
||||
}
|
||||
result = s.execRemote(ctx, task.Command, task.Timeout, parseInt(sid))
|
||||
}
|
||||
|
||||
if finalResult == nil || !result.IsOk {
|
||||
finalResult = result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +185,76 @@ func (s *schedulerService) execRemote(ctx context.Context, command string, timeo
|
||||
return result
|
||||
}
|
||||
|
||||
// execHTTP 执行 HTTP 请求任务
|
||||
func (s *schedulerService) execHTTP(ctx context.Context, task *entity.Task) *TaskResult {
|
||||
result := &TaskResult{}
|
||||
client := &http.Client{Timeout: time.Duration(task.Timeout) * time.Second}
|
||||
if task.Timeout <= 0 {
|
||||
client.Timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
method := strings.ToUpper(task.Method)
|
||||
if method == "" {
|
||||
method = "GET"
|
||||
}
|
||||
|
||||
var bodyReader io.Reader
|
||||
if task.Body != "" && (method == "POST" || method == "PUT" || method == "PATCH") {
|
||||
bodyReader = bytes.NewBufferString(task.Body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, task.Url, bodyReader)
|
||||
if err != nil {
|
||||
result.Error = "创建请求失败: " + err.Error()
|
||||
return result
|
||||
}
|
||||
|
||||
// 解析并设置自定义 Header
|
||||
if task.Headers != "" {
|
||||
var headers map[string]string
|
||||
if err := json.Unmarshal([]byte(task.Headers), &headers); err == nil {
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
} else {
|
||||
result.Error = "Headers 格式错误(需为 JSON 对象)"
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// 默认 Content-Type
|
||||
if req.Header.Get("Content-Type") == "" && bodyReader != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "HTTP请求 -> %s %s", method, task.Url)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
result.Error = "请求失败: " + err.Error()
|
||||
return result
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
bodyStr := string(bodyBytes)
|
||||
|
||||
// 截断过长的输出
|
||||
if len(bodyStr) > 10000 {
|
||||
bodyStr = bodyStr[:10000] + "\n... (truncated)"
|
||||
}
|
||||
|
||||
statusLine := fmt.Sprintf("[HTTP %d]", resp.StatusCode)
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
result.IsOk = true
|
||||
result.Output = statusLine + " " + bodyStr
|
||||
} else {
|
||||
result.Error = statusLine + " " + bodyStr
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "HTTP响应 -> %s %s -> %s", method, task.Url, statusLine)
|
||||
return result
|
||||
}
|
||||
|
||||
// parseInt 简单字符串转整数
|
||||
func parseInt(s string) int {
|
||||
id := 0
|
||||
|
||||
Reference in New Issue
Block a user