golang实现【任务调度管理系统】开发流程详解
一、系统概述
本任务调度管理系统是基于Go语言开发的一个完整的任务调度平台,采用前后端分离架构,后端使用Go语言的Iris框架,前端使用Vue3+HTML5实现。系统主要功能包括:
- 任务的创建、编辑、删除、启动/终止、暂停/继续等基本操作
- 支持一次性任务和周期性任务两种类型
- 周期性任务支持Cron表达式配置执行时间
- 任务执行状态实时监控(等待执行、正在执行、已完成、已超期)
- 任务执行结果记录与展示
- 超期任务自动处理
- 任务执行统计分析
- 任务执行日志查询
- 高频执行任务识别与监控
系统架构清晰,代码组织合理,具有良好的可扩展性和可维护性,可应用于各种需要自动化任务调度的场景。
二、运行效果图片展示
1. 任务列表页面
2. 新增任务页面
3. 修改任务页面
4. 任务日志页面
5. Cron表达式设置
6. 任务统计页面
三、项目结构
本项目采用典型的Go语言项目结构,遵循MVC架构模式,代码组织清晰合理。主要目录结构如下:
TaskSchedulingService/
├── controller/ # 控制器层,处理HTTP请求
│ └── task_controller.go
├── core/ # 核心功能模块
│ └── cron.go
├── external_call_result/ # 外部调用结果存储
├── global/ # 全局变量和配置
│ └── global.go
├── initialize/ # 初始化模块
│ ├── initDatabase.go
│ └── timer.go
├── middleware/ # 中间件
│ └── cors.go
├── model/ # 数据模型
│ └── task_model.go
├── router/ # 路由配置
│ └── router.go
├── scripts/ # 脚本工具
│ ├── add_cron_expressions.go
│ └── verify_cron_expressions.go
├── service/ # 业务逻辑层
│ └── task_service.go
├── static/ # 静态资源
│ └── js/
│ ├── axios.min.js
│ ├── echarts.min.js
│ └── vue.global.js
├── template/ # 前端模板
│ └── index.html
├── timer/ # 任务定时器
│ ├── dynamic_task.go
│ ├── scenario_tasks.go
│ └── simple_task.go
├── utils/ # 工具函数
│ └── port_utils.go
├── config.json # 配置文件
├── go.mod # Go模块定义
├── go.sum # Go依赖校验
└── main.go # 项目入口
核心目录说明:
- controller/: 控制器层,处理HTTP请求,实现RESTful API接口
- model/: 数据模型层,定义任务和执行日志的数据结构
- service/: 业务逻辑层,实现任务执行和管理的核心逻辑
- timer/: 任务定时器,负责任务的调度和执行
- initialize/: 初始化模块,负责数据库连接和任务加载
- template/: 前端模板,实现用户界面
- static/: 静态资源,包括Vue.js、Axios等前端库
四、项目开发流程图
1. 后端服务流程图
sequenceDiagram
participant Client as 前端
participant API as API层
participant Controller as 控制器层
participant Service as 服务层
participant Timer as 定时器
participant DB as 数据库
Client->>API: HTTP请求
API->>Controller: 路由分发
Controller->>DB: 数据操作
Controller->>Service: 业务逻辑处理
Controller->>Timer: 任务管理
Timer->>Service: 任务执行
Service->>DB: 执行结果存储
Controller-->>Client: 响应结果
2. 前端与后端交互操作功能流程图
sequenceDiagram
participant User as 用户
participant Frontend as 前端
participant Backend as 后端API
participant DB as 数据库
User->>Frontend: 访问系统
Frontend->>Backend: GET /api/tasks
Backend->>DB: 查询任务列表
DB-->>Backend: 返回任务数据
Backend-->>Frontend: 返回任务列表
Frontend-->>User: 显示任务列表
User->>Frontend: 点击新增任务
Frontend->>User: 显示新增任务表单
User->>Frontend: 填写任务信息
Frontend->>Backend: POST /api/tasks
Backend->>DB: 保存任务
Backend->>Backend: 注册任务到定时器
DB-->>Backend: 保存成功
Backend-->>Frontend: 返回任务信息
Frontend-->>User: 显示成功提示
User->>Frontend: 点击查看日志
Frontend->>Backend: GET /api/tasks/logs?task_id=1
Backend->>DB: 查询任务日志
DB-->>Backend: 返回日志数据
Backend-->>Frontend: 返回日志列表
Frontend-->>User: 显示任务日志
User->>Frontend: 点击启动/终止任务
Frontend->>Backend: POST /api/tasks/{id}/start
Backend->>DB: 更新任务状态
Backend->>Backend: 注册/移除任务
DB-->>Backend: 更新成功
Backend-->>Frontend: 返回任务信息
Frontend-->>User: 显示成功提示
五、后端服务结构体、核心包及包内所有函数介绍及核心源代码
1. 核心包介绍
1.1 controller 包
功能:控制器层,处理HTTP请求,实现RESTful API接口
核心结构体:
TaskController:任务控制器,处理任务相关的HTTP请求
核心函数:
NewTaskController():创建任务控制器实例TaskController.BeforeActivation():在激活前配置路由TaskController.ListTasks():列出所有任务TaskController.GetTask():获取任务详情TaskController.CreateTask():创建新任务TaskController.UpdateTask():更新任务信息TaskController.DeleteTask():删除任务TaskController.ListTaskFuncs():列出所有可用的任务函数TaskController.ListTaskLogs():列出任务执行日志TaskController.ListAllTaskLogs():列出所有任务执行日志TaskController.GetTaskStatistics():获取任务统计信息TaskController.StopTask():终止任务TaskController.StartTask():启动任务TaskController.PauseTask():暂停任务TaskController.ResumeTask():继续任务TaskController.ListCronExpressions():列出Cron表达式TaskController.CreateCronExpression():创建Cron表达式TaskController.UpdateCronExpression():更新Cron表达式TaskController.DeleteCronExpression():删除Cron表达式executeTask():执行任务并记录日志
1.2 model 包
功能:数据模型层,定义任务和执行日志的数据结构
核心结构体:
Task:任务模型,包含任务的基本信息和状态TaskExecutionLog:任务执行日志模型,记录任务执行的详细信息CronExpression:定时表达式模型,存储常用的Cron表达式
核心函数:
Task.TableName():设置任务表名TaskExecutionLog.TableName():设置任务执行日志表名CronExpression.TableName():设置Cron表达式表名CreateTable():创建数据库表
1.3 service 包
功能:业务逻辑层,实现任务执行和管理的核心逻辑
核心结构体:
TaskFuncInfo:任务函数信息结构体,包含任务函数的名称和描述ExternalAPICallParams:外部API调用参数结构体,包含API调用的相关参数
核心函数:
InitTaskFuncMap():初始化任务函数注册中心DataBackup():数据备份任务SystemCheck():系统检查任务UserReport():用户报告任务CleanCache():清理缓存任务SyncData():数据同步任务AddCustomTask():添加自定义任务函数RemoveCustomTask():移除自定义任务函数GetTaskFunc():获取任务函数ListTaskFuncs():列出所有可用的任务函数ExecuteExternalAPI():执行外部API调用的万能函数saveResult():保存API调用结果到文件
1.4 initialize 包
功能:初始化模块,负责数据库连接和任务加载
核心函数:
InitDatabase():初始化数据库连接Timer():初始化定时任务loadTasksFromDatabase():从数据库加载任务并注册到定时器
1.5 core 包
功能:核心功能模块,包含Cron定时器的初始化
核心函数:
InitCron():初始化Cron实例
2. 核心源代码
2.1 项目入口 (main.go)
func main() {
// 初始化全局日志
logger, _ := zap.NewProduction()
defer logger.Sync()
global.GVA_LOG = logger
// 初始化数据库
initialize.InitDatabase()
// 初始化全局 cron 实例
global.GVA_CRON = core.InitCron()
// 初始化定时任务注册
initialize.Timer()
// 初始化任务函数注册中心
service.InitTaskFuncMap()
// 设置路由
app := router.SetupRouter()
// 启动Web服务
global.GVA_LOG.Info("Web服务启动成功,访问地址:http://localhost:8082")
port := os.Getenv("PORT")
if port == "" {
port = "8082" // 更改端口为8082
}
// 检查端口是否可用
portNum, err := strconv.Atoi(port)
if err != nil {
global.GVA_LOG.Fatal("Web服务启动失败", zap.Error(err))
}
if utils.IsPortInUse(portNum) {
global.GVA_LOG.Info("端口 %s 被占用,尝试终止占用进程...", zap.String("port", port))
err := utils.FindAndKillProcess(portNum)
if err != nil {
global.GVA_LOG.Fatal("终止进程失败", zap.Error(err))
}
}
app.Listen(fmt.Sprintf(":%s", port))
}
2.2 任务控制器 (controller/task_controller.go)
// TaskController 任务控制器
type TaskController struct{}
// NewTaskController 创建任务控制器实例
func NewTaskController() *TaskController {
return &TaskController{}
}
// BeforeActivation 在激活前配置路由
func (c *TaskController) BeforeActivation(b mvc.BeforeActivation) {
// 配置路由
b.Handle("GET", "/", "ListTasks")
b.Handle("GET", "/{id:string}", "GetTask")
b.Handle("POST", "/", "CreateTask")
b.Handle("PUT", "/{id:string}", "UpdateTask")
b.Handle("DELETE", "/{id:string}", "DeleteTask")
b.Handle("GET", "/funcs", "ListTaskFuncs")
b.Handle("GET", "/logs", "ListTaskLogs")
b.Handle("GET", "/logs/all", "ListAllTaskLogs")
b.Handle("GET", "/statistics", "GetTaskStatistics")
// 终止/启动和暂停/继续路由
b.Handle("POST", "/{id:string}/stop", "StopTask")
b.Handle("POST", "/{id:string}/start", "StartTask")
b.Handle("POST", "/{id:string}/pause", "PauseTask")
b.Handle("POST", "/{id:string}/resume", "ResumeTask")
// cron表达式路由
b.Handle("GET", "/cron", "ListCronExpressions")
b.Handle("POST", "/cron", "CreateCronExpression")
b.Handle("PUT", "/cron/{id:string}", "UpdateCronExpression")
b.Handle("DELETE", "/cron/{id:string}", "DeleteCronExpression")
}
// ListTasks 列出所有任务
func (c *TaskController) ListTasks(ctx iris.Context) mvc.Result {
var tasks []*model.Task
err := global.GVA_DB.Find(&tasks)
if err != nil {
global.GVA_LOG.Error("查询任务列表失败", zap.Error(err))
return mvc.Response{
Code: iris.StatusInternalServerError,
Object: iris.Map{
"code": iris.StatusInternalServerError,
"message": "查询任务列表失败",
"error": err.Error(),
},
}
}
// 处理一次性任务超期问题
now := time.Now()
for _, task := range tasks {
if task.Type == "single" && task.ExecuteAt.Before(now) {
// 将超期的一次性任务更新为已完成状态,并设置为禁用和暂停
task.Status = "completed"
task.EnabledStatus = false
task.PausedStatus = true
task.UpdateTime = now
_, err := global.GVA_DB.Where("id = ?", task.ID).Update(task)
if err != nil {
global.GVA_LOG.Error("更新超期任务状态失败", zap.Error(err), zap.Int64("task_id", task.ID))
} else {
global.GVA_LOG.Info("更新超期任务状态成功", zap.Int64("task_id", task.ID))
}
}
}
return mvc.Response{
Code: iris.StatusOK,
Object: iris.Map{
"code": iris.StatusOK,
"message": "success",
"data": tasks,
},
}
}
// CreateTask 创建任务
func (c *TaskController) CreateTask(ctx iris.Context) mvc.Result {
var req struct {
// ID 由数据库自动生成(bigint 自增),不需要用户输入
ID int64 `json:"id"`
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Type string `json:"type" binding:"required,oneof=single recurring"`
CronExpr string `json:"cron_expr"`
ExecuteAt time.Time `json:"execute_at"`
Timeout int `json:"timeout" binding:"min=1"`
TaskFuncName string `json:"task_func_name" binding:"required"`
Params string `json:"params"`
}
if err := ctx.ReadJSON(&req); err != nil {
return mvc.Response{
Code: iris.StatusBadRequest,
Object: iris.Map{
"code": iris.StatusBadRequest,
"message": "参数错误",
"error": err.Error(),
},
}
}
// 检查任务函数是否存在
_, err := service.GetTaskFunc(req.TaskFuncName)
if err != nil {
return mvc.Response{
Code: iris.StatusBadRequest,
Object: iris.Map{
"code": iris.StatusBadRequest,
"message": err.Error(),
},
}
}
// 创建任务
task := &model.Task{
Name: req.Name,
Description: req.Description,
Type: req.Type,
CronExpr: req.CronExpr,
ExecuteAt: req.ExecuteAt,
Timeout: req.Timeout,
Status: "pending",
TaskFuncName: req.TaskFuncName,
Params: req.Params,
CreateTime: time.Now(),
UpdateTime: time.Now(),
ExecuteCount: 0,
LastExecuteAt: time.Time{},
NextExecuteAt: req.ExecuteAt,
}
// 保存到数据库
_, err = global.GVA_DB.Insert(task)
if err != nil {
global.GVA_LOG.Error("创建任务失败", zap.Error(err), zap.Any("task", task))
return mvc.Response{
Code: iris.StatusInternalServerError,
Object: iris.Map{
"code": iris.StatusInternalServerError,
"message": "创建任务失败",
"error": err.Error(),
},
}
}
// 添加到任务管理器
taskFunc, _ := service.GetTaskFunc(req.TaskFuncName)
if req.Type == "single" {
err = global.GVA_TASK_MANAGER.AddTask(
fmt.Sprintf("%d", task.ID),
task.ExecuteAt,
time.Duration(task.Timeout)*time.Second,
func() {
executeTask(task.ID, taskFunc, task)
},
)
} else {
err = global.GVA_TASK_MANAGER.AddRecurringTask(
fmt.Sprintf("%d", task.ID),
task.CronExpr,
time.Duration(task.Timeout)*time.Second,
func() {
executeTask(task.ID, taskFunc, task)
},
)
}
if err != nil {
global.GVA_LOG.Error("添加任务到任务管理器失败", zap.Error(err), zap.Int64("id", task.ID))
return mvc.Response{
Code: iris.StatusInternalServerError,
Object: iris.Map{
"code": iris.StatusInternalServerError,
"message": "添加任务到任务管理器失败",
"error": err.Error(),
},
}
}
return mvc.Response{
Code: iris.StatusOK,
Object: iris.Map{
"code": iris.StatusOK,
"message": "任务创建成功",
"data": task,
},
}
}
// executeTask 执行任务并记录日志
func executeTask(taskID int64, taskFunc func(string), task *model.Task) {
// 检查任务是否被暂停或禁用
var currentTask model.Task
has, err := global.GVA_DB.SQL("SELECT enabled_status, paused_status FROM tasks WHERE id = ?", taskID).Get(¤tTask)
if err != nil {
global.GVA_LOG.Error("查询任务状态失败", zap.Error(err), zap.Int64("task_id", taskID))
return
}
if !has || !currentTask.EnabledStatus || currentTask.PausedStatus {
global.GVA_LOG.Info("任务被暂停或禁用,跳过执行", zap.Int64("task_id", taskID))
return
}
// 更新任务状态为运行中
task.Status = "running"
task.UpdateTime = time.Now()
task.LastExecuteAt = time.Now()
_, err = global.GVA_DB.Exec("UPDATE tasks SET status = ?, update_time = ?, last_execute_at = ? WHERE id = ?", task.Status, task.UpdateTime, task.LastExecuteAt, taskID)
if err != nil {
global.GVA_LOG.Error("更新任务状态为运行中失败", zap.Error(err), zap.Int64("task_id", taskID))
}
// 创建执行日志
log := &model.TaskExecutionLog{
TaskID: taskID,
StartTime: time.Now(),
Status: "running",
Message: "",
}
global.GVA_DB.Insert(log)
// 执行任务
startTime := time.Now()
defer func() {
if r := recover(); r != nil {
// 任务执行失败
endTime := time.Now()
log.EndTime = endTime
log.Status = "failed"
log.Message = fmt.Sprintf("任务执行失败: %v", r)
log.ExecutionTime = endTime.Sub(startTime).Milliseconds()
_, err = global.GVA_DB.Exec("UPDATE task_execution_logs SET end_time = ?, status = ?, message = ?, execution_time = ? WHERE id = ?", log.EndTime, log.Status, log.Message, log.ExecutionTime, log.ID)
if err != nil {
global.GVA_LOG.Error("更新任务执行失败日志失败", zap.Error(err), zap.Int64("log_id", log.ID))
}
// 更新任务状态
task.Status = "completed"
task.ExecuteCount++
task.UpdateTime = endTime
_, err = global.GVA_DB.Exec("UPDATE tasks SET status = ?, execute_count = execute_count + 1, update_time = ? WHERE id = ?", task.Status, task.UpdateTime, taskID)
if err != nil {
global.GVA_LOG.Error("更新任务状态为完成失败", zap.Error(err), zap.Int64("task_id", taskID))
}
}
}()
// 执行任务函数
taskFunc(fmt.Sprintf("%d", taskID))
// 任务执行成功
endTime := time.Now()
log.EndTime = endTime
log.Status = "success"
log.Message = "任务执行成功"
log.ExecutionTime = endTime.Sub(startTime).Milliseconds()
_, err = global.GVA_DB.Exec("UPDATE task_execution_logs SET end_time = ?, status = ?, message = ?, execution_time = ? WHERE id = ?", log.EndTime, log.Status, log.Message, log.ExecutionTime, log.ID)
if err != nil {
global.GVA_LOG.Error("更新任务执行成功日志失败", zap.Error(err), zap.Int64("log_id", log.ID))
}
// 更新任务状态
task.Status = "completed"
task.ExecuteCount++
task.UpdateTime = endTime
// 如果是一次性任务,不需要更新下次执行时间
if task.Type == "recurring" {
// 这里可以根据cron表达式计算下次执行时间
// 为了简化,这里暂时不实现
}
// 更新任务状态和执行次数
result, err := global.GVA_DB.Exec("UPDATE tasks SET status = ?, execute_count = execute_count + 1, update_time = ? WHERE id = ?", task.Status, task.UpdateTime, taskID)
if err != nil {
global.GVA_LOG.Error("更新任务状态为完成失败", zap.Error(err), zap.Int64("task_id", taskID))
} else {
// 记录更新结果
rowsAffected, _ := result.RowsAffected()
global.GVA_LOG.Info("更新任务状态成功", zap.Int64("task_id", taskID), zap.Int64("rows_affected", rowsAffected))
}
}
2.3 数据模型 (model/task_model.go)
// Task 任务模型
type Task struct {
ID int64 `xorm:"bigint pk autoincr 'id'" json:"id"`
Name string `xorm:"varchar(100) notnull 'name'" json:"name"`
Description string `xorm:"text 'description'" json:"description"`
Type string `xorm:"varchar(20) notnull 'type'" json:"type"` // single, recurring
CronExpr string `xorm:"varchar(50) 'cron_expr'" json:"cron_expr"`
ExecuteAt time.Time `xorm:"datetime 'execute_at'" json:"execute_at"`
Timeout int `xorm:"int notnull default(30) 'timeout'" json:"timeout"`
Status string `xorm:"varchar(20) notnull default('pending') 'status'" json:"status"`
EnabledStatus bool `xorm:"bool notnull default(true) 'enabled_status'" json:"enabled_status"` // 是否启用(终止/启动)
PausedStatus bool `xorm:"bool notnull default(false) 'paused_status'" json:"paused_status"` // 是否暂停
TaskFuncName string `xorm:"varchar(100) notnull 'task_func_name'" json:"task_func_name"`
Params string `xorm:"text 'params'" json:"params"`
CreateTime time.Time `xorm:"datetime notnull 'create_time'" json:"create_time"`
UpdateTime time.Time `xorm:"datetime notnull 'update_time'" json:"update_time"`
ExecuteCount int `xorm:"int notnull default(0) 'execute_count'" json:"execute_count"`
LastExecuteAt time.Time `xorm:"datetime 'last_execute_at'" json:"last_execute_at"`
NextExecuteAt time.Time `xorm:"datetime 'next_execute_at'" json:"next_execute_at"`
}
// TaskExecutionLog 任务执行日志模型
type TaskExecutionLog struct {
ID int64 `xorm:"bigint pk autoincr 'id'" json:"id"`
TaskID int64 `xorm:"bigint notnull 'task_id'" json:"task_id"`
StartTime time.Time `xorm:"datetime notnull" json:"start_time"`
EndTime time.Time `xorm:"datetime" json:"end_time"`
Status string `xorm:"varchar(20) notnull" json:"status"` // success, failed, timeout
Message string `xorm:"text" json:"message"`
ExecutionTime int64 `xorm:"bigint" json:"execution_time"` // 执行时间(毫秒)
}
// CronExpression 定时表达式模型
type CronExpression struct {
ID int64 `xorm:"bigint pk autoincr 'id'" json:"id"`
Label string `xorm:"varchar(100) notnull 'label'" json:"label"`
Value string `xorm:"varchar(50) notnull 'value'" json:"value"`
CreateTime time.Time `xorm:"datetime notnull 'create_time'" json:"create_time"`
UpdateTime time.Time `xorm:"datetime notnull 'update_time'" json:"update_time"`
}
// TableName 设置表名
func (Task) TableName() string {
return "tasks"
}
// TableName 设置表名
func (TaskExecutionLog) TableName() string {
return "task_execution_logs"
}
// TableName 设置表名
func (CronExpression) TableName() string {
return "cron_expressions"
}
// CreateTable 创建表
func CreateTable(engine *xorm.Engine) error {
// 创建或更新任务执行日志表
err := engine.Sync2(&TaskExecutionLog{})
if err != nil {
return err
}
// 创建或更新任务表
err = engine.Sync2(&Task{})
if err != nil {
return err
}
// 创建或更新定时表达式表
err = engine.Sync2(&CronExpression{})
if err != nil {
return err
}
return nil
}
2.4 任务服务 (service/task_service.go)
// TaskFuncInfo 任务函数信息结构体
type TaskFuncInfo struct {
Name string `json:"name"` // 函数名
Description string `json:"description"` // 中文功能名称
}
// InitTaskFuncMap 初始化任务函数注册中心
func InitTaskFuncMap() {
// 注册内置任务函数
TaskFuncMap["DataBackup"] = DataBackup
TaskFuncMap["SystemCheck"] = SystemCheck
TaskFuncMap["UserReport"] = UserReport
TaskFuncMap["CleanCache"] = CleanCache
TaskFuncMap["SyncData"] = SyncData
TaskFuncMap["ExecuteExternalAPI"] = ExecuteExternalAPI
// 注册场景任务函数
TaskFuncMap["SyncDataFromExternalSystem"] = timer.SyncDataFromExternalSystem
TaskFuncMap["BackupDatabase"] = timer.BackupDatabase
TaskFuncMap["CleanInvalidData"] = timer.CleanInvalidData
TaskFuncMap["AnalyzeBusinessData"] = timer.AnalyzeBusinessData
TaskFuncMap["GenerateDailyReport"] = timer.GenerateDailyReport
TaskFuncMap["SendEmailNotification"] = timer.SendEmailNotification
TaskFuncMap["CheckSystemStatus"] = timer.CheckSystemStatus
TaskFuncMap["TriggerBusinessProcess"] = timer.TriggerBusinessProcess
TaskFuncMap["RotateSystemLogs"] = timer.RotateSystemLogs
TaskFuncMap["RefreshSystemCache"] = timer.RefreshSystemCache
TaskFuncMap["CleanupSystemResources"] = timer.CleanupSystemResources
TaskFuncMap["CollectSystemMetrics"] = timer.CollectSystemMetrics
TaskFuncMap["ExecuteDelayedBusinessTask"] = timer.ExecuteDelayedBusinessTask
TaskFuncMap["ExecuteTemporaryTask"] = timer.ExecuteTemporaryTask
TaskFuncMap["ExecuteEventBasedTask"] = timer.ExecuteEventBasedTask
}
// GetTaskFunc 获取任务函数
func GetTaskFunc(name string) (func(string), error) {
task, exists := TaskFuncMap[name]
if !exists {
return nil, fmt.Errorf("任务函数不存在: %s", name)
}
return task, nil
}
// ListTaskFuncs 列出所有可用的任务函数
func ListTaskFuncs() []TaskFuncInfo {
var funcs []TaskFuncInfo
for name := range TaskFuncMap {
description := TaskFuncDescriptions[name]
if description == "" {
description = name // 如果没有找到中文描述,使用函数名
}
funcs = append(funcs, TaskFuncInfo{
Name: name,
Description: description,
})
}
return funcs
}
// ExecuteExternalAPI 执行外部API调用的万能函数
func ExecuteExternalAPI(taskID string) {
global.GVA_LOG.Info("执行外部API调用任务", zap.String("taskID", taskID))
fmt.Println("正在执行外部API调用...")
// 从数据库读取任务信息
var task model.Task
// 将字符串类型的taskID转换为int64类型
taskIDInt, err := strconv.ParseInt(taskID, 10, 64)
if err != nil {
global.GVA_LOG.Error("解析任务ID失败", zap.Error(err), zap.String("taskID", taskID))
fmt.Printf("解析任务ID失败: %v\n", err)
return
}
// 使用原生SQL语句查询任务,避免XORM生成可能不兼容的SQL
found, err := global.GVA_DB.SQL("SELECT * FROM tasks WHERE id = ?", taskIDInt).Get(&task)
if err != nil || !found {
global.GVA_LOG.Error("从数据库读取任务失败", zap.Error(err), zap.String("taskID", taskID), zap.Bool("found", found))
fmt.Printf("从数据库读取任务失败: %v\n", err)
return
}
// 检查任务参数
paramsJSON := task.Params
if paramsJSON == "" {
global.GVA_LOG.Error("任务参数为空", zap.String("taskID", taskID))
fmt.Println("任务参数为空")
return
}
// 解析参数
var params ExternalAPICallParams
err = json.Unmarshal([]byte(paramsJSON), ¶ms)
if err != nil {
global.GVA_LOG.Error("解析API调用参数失败", zap.Error(err))
fmt.Printf("解析API调用参数失败: %v\n", err)
return
}
// 确保结果目录存在
resultDir := "external_call_result"
if err := os.MkdirAll(resultDir, 0755); err != nil {
global.GVA_LOG.Error("创建结果目录失败", zap.Error(err))
fmt.Printf("创建结果目录失败: %v\n", err)
return
}
// 验证API地址
if params.APIAddress == "" {
global.GVA_LOG.Error("API地址为空", zap.String("taskID", taskID))
fmt.Println("API地址为空")
// 保存失败结果
saveResult(resultDir, taskID, "API地址为空")
return
}
// 去除API地址中的反引号和空格
params.APIAddress = strings.Trim(params.APIAddress, "` ")
// 验证API地址是否包含协议方案
if !strings.HasPrefix(params.APIAddress, "http://") && !strings.HasPrefix(params.APIAddress, "https://") {
global.GVA_LOG.Error("API地址缺少协议方案", zap.String("taskID", taskID), zap.String("APIAddress", params.APIAddress))
fmt.Printf("API地址缺少协议方案: %s\n", params.APIAddress)
// 保存失败结果
saveResult(resultDir, taskID, fmt.Sprintf("API地址缺少协议方案: %s", params.APIAddress))
return
}
// 将请求方式转换为大写
params.RequestMethod = strings.ToUpper(params.RequestMethod)
// 输出API地址以便调试
global.GVA_LOG.Info("执行API调用", zap.String("APIAddress", params.APIAddress), zap.String("RequestMethod", params.RequestMethod))
fmt.Printf("正在调用API: %s\n", params.APIAddress)
fmt.Printf("请求方式: %s\n", params.RequestMethod)
// 准备请求
// 创建自定义TLS配置,跳过证书验证(仅用于开发测试环境)
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
client := &http.Client{
Timeout: time.Duration(params.Timeout) * time.Second,
Transport: transport,
}
// 准备请求体
var body []byte
var bodyReader io.Reader
if len(params.Data) > 0 {
body, err = json.Marshal(params.Data)
if err != nil {
global.GVA_LOG.Error("序列化请求数据失败", zap.Error(err))
fmt.Printf("序列化请求数据失败: %v\n", err)
return
}
bodyReader = bytes.NewBuffer(body)
} else {
// 当请求体为空时,使用nil
bodyReader = nil
}
// 创建请求
req, err := http.NewRequest(params.RequestMethod, params.APIAddress, bodyReader)
if err != nil {
global.GVA_LOG.Error("创建HTTP请求失败", zap.Error(err))
fmt.Printf("创建HTTP请求失败: %v\n", err)
return
}
// 设置默认请求头
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
// 设置用户指定的请求头
for key, value := range params.Headers {
req.Header.Set(key, value)
}
// 发送请求
resp, err := client.Do(req)
if err != nil {
global.GVA_LOG.Error("发送HTTP请求失败", zap.Error(err))
fmt.Printf("发送HTTP请求失败: %v\n", err)
// 保存失败结果
saveResult(resultDir, taskID, fmt.Sprintf("请求失败: %v", err))
return
}
defer resp.Body.Close()
// 读取响应
respBody, err := io.ReadAll(resp.Body)
if err != nil {
global.GVA_LOG.Error("读取响应失败", zap.Error(err))
fmt.Printf("读取响应失败: %v\n", err)
// 保存失败结果
saveResult(resultDir, taskID, fmt.Sprintf("读取响应失败: %v", err))
return
}
// 构建结果字符串
responseBodyStr := string(respBody)
// 尝试将响应体解析为JSON并格式化
var jsonObj interface{}
if err := json.Unmarshal(respBody, &jsonObj); err == nil {
// 如果是JSON,格式化显示
formattedJSON, err := json.MarshalIndent(jsonObj, "", " ")
if err == nil {
responseBodyStr = string(formattedJSON)
}
}
result := fmt.Sprintf("状态码: %d\n响应体: \n%s", resp.StatusCode, responseBodyStr)
// 保存结果
saveResult(resultDir, taskID, result)
global.GVA_LOG.Info("外部API调用任务执行完成")
fmt.Println("外部API调用完成")
}
六、前端html/vue3各功能介绍及核心源代码
1. 核心功能介绍
1.1 任务列表展示
功能:显示所有任务的列表,包括任务ID、名称、类型、执行间隔、执行次数、执行结果等信息
核心功能:
- 分页显示任务列表
- 任务状态实时更新
- 执行结果颜色区分
- 超期任务特殊标记
- 任务操作按钮
1.2 任务管理操作
功能:提供任务的新增、编辑、删除、启动/终止、暂停/继续等操作
核心功能:
- 新增任务表单
- 任务类型切换(一次性/周期性)
- Cron表达式配置
- 任务状态管理
1.3 任务统计分析
功能:提供任务执行统计和分析图表
核心功能:
- 任务执行趋势图
- 任务状态分布图
- 任务类型分布图
- 任务执行时长分析
- 高频执行任务列表
1.4 任务日志查询
功能:查询和显示任务的执行日志
核心功能:
- 按任务ID查询日志
- 日志分页显示
- 执行状态和时间显示
2. 核心源代码
2.1 任务列表渲染 (template/index.html)
<!-- 任务列表 -->
<div v-if="activeTab === 'tasks'">
<div class="task-list">
<table class="task-table">
<thead>
<tr>
<th>序号</th>
<th>任务ID</th>
<th>任务名称</th>
<th>任务类型</th>
<th>执行间隔</th>
<th>执行次数</th>
<th>执行结果</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-if="tasks.length === 0">
<td colspan="8" style="text-align: center; color: #666; padding: 20px;">暂无任务</td>
</tr>
<tr v-for="(task, index) in paginatedTasks" :key="task.id">
<td>{{ (tasksPage - 1) * tasksPageSize + index + 1 }}</td>
<td>{{ task.id }}</td>
<td>{{ task.name }}</td>
<td>{{ task.type === 'single' ? '一次性' : '周期性' }}</td>
<td>
<span v-if="task.type === 'single'"> {{ formatDateTime(task.execute_at) }}</span>
<span v-else>{{ getCronInterval(task.cron_expr) }}</span>
</td>
<td>{{ task.execute_count }}</td>
<td>
<span v-if="task.status === 'completed'">
<span v-if="task.type === 'single' && new Date(task.execute_at) < new Date()" class="error-result">任务已超期</span>
<span v-else :class="allTaskLogs.find(log => log.task_id === task.id)?.status === 'success' ? 'success-result' : 'error-result'>
{{ allTaskLogs.find(log => log.task_id === task.id)?.status === 'success' ? '任务执行完成' : '失败' }}
</span>
</span>
</td>
<td>
<div class="action-buttons">
<span :class="['task-status', `status-${task.status}`, task.status !== 'running' && task.type === 'single' && new Date(task.execute_at) < new Date() ? 'status-overdue' : '']">
{{ task.status === 'pending' ? '等待执行' : task.status === 'running' ? '正在执行' : task.type === 'single' && new Date(task.execute_at) < new Date() ? '已超期' : '已完成' }}
</span>
<button :class="['btn', task.enabled_status ? 'btn-danger' : 'btn-success']" @click="toggleTaskStatus(task.id, task.enabled_status)">
{{ task.enabled_status ? '终止' : '启动' }}
</button>
<button :class="['btn', task.paused_status ? 'btn-warning' : 'btn-primary']" :disabled="!task.enabled_status" @click="toggleTaskPause(task.id, task.paused_status)">
{{ task.paused_status ? '继续' : '暂停' }}
</button>
<button class="btn btn-primary" @click="showTaskLogs(task.id)">查看日志</button>
<button class="btn btn-warning" @click="editTask(task)">修改</button>
<button class="btn btn-danger" @click="deleteTask(task.id)">删除</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination" v-if="tasksTotal > tasksPageSize">
<div class="page-item" v-if="tasksPage > 1">
<a class="page-link" @click="tasksPage--">上一页</a>
</div>
<div class="page-item" v-for="page in tasksDisplayedPages" :key="page">
<a class="page-link" :class="{ active: page === tasksPage }" @click="tasksPage = page">{{ page }}</a>
</div>
<div class="page-item" v-if="tasksPage < tasksTotalPages">
<a class="page-link" @click="tasksPage++">下一页</a>
</div>
</div>
</div>
2.2 Vue3 数据管理和方法 (template/index.html)
<script>
const { createApp, ref, computed, onMounted, watch } = Vue;
createApp({
setup() {
// 状态管理
const activeTab = ref('tasks');
const tasks = ref([]);
const taskLogs = ref([]);
const allTaskLogs = ref([]);
const taskFuncs = ref([]);
const statistics = ref({});
const failedTasks = ref([]);
const frequentTasks = ref([]);
const cronExpressions = ref([]);
// 分页管理
const tasksPage = ref(1);
const tasksPageSize = ref(10);
const tasksTotal = ref(0);
const cronPage = ref(1);
const cronPageSize = ref(10);
const cronTotal = ref(0);
// 排序管理
const sortBy = ref('default');
const sortOrder = ref('asc');
// 模态框管理
const showAddTaskModal = ref(false);
const showEditTaskModal = ref(false);
const showLogsModal = ref(false);
const showCronModal = ref(false);
// 任务表单
const newTask = ref({
name: '',
description: '',
type: 'single',
cron_expr: '',
execute_at: '',
timeout: 30,
task_func_name: '',
params: ''
});
const editingTask = ref({});
const editingCron = ref({});
// 任务信息
const taskName = ref('');
const taskFuncName = ref('');
const taskFuncDesc = ref('');
// 常用Cron表达式
const commonCronExpressions = [
{ label: '每10秒', value: '*/10 * * * * *' },
{ label: '每分钟', value: '0 * * * * *' },
{ label: '每5分钟', value: '0 */5 * * * *' },
{ label: '每小时', value: '0 0 * * * *' },
{ label: '每天', value: '0 0 0 * * *' },
{ label: '每周', value: '0 0 0 * * 0' },
{ label: '每月', value: '0 0 0 1 * *' }
];
// 加载任务列表
const loadTasks = async () => {
try {
const response = await axios.get('/api/tasks');
if (response.data.code === 200) {
tasks.value = response.data.data;
tasksTotal.value = tasks.value.length;
} else {
showToast('加载任务列表失败', 'error');
}
} catch (error) {
showToast('加载任务列表失败', 'error');
console.error('加载任务列表失败:', error);
}
};
// 加载所有任务日志
const loadAllTaskLogs = async () => {
try {
const response = await axios.get('/api/tasks/logs/all');
if (response.data.code === 200) {
allTaskLogs.value = response.data.data;
}
} catch (error) {
console.error('加载任务日志失败:', error);
}
};
// 加载任务函数
const loadTaskFuncs = async () => {
try {
const response = await axios.get('/api/tasks/funcs');
if (response.data.code === 200) {
taskFuncs.value = response.data.data;
}
} catch (error) {
console.error('加载任务函数失败:', error);
}
};
// 加载统计数据
const loadStatistics = async () => {
try {
const response = await axios.get('/api/tasks/statistics');
if (response.data.code === 200) {
statistics.value = response.data.data;
initCharts();
}
} catch (error) {
console.error('加载统计数据失败:', error);
}
};
// 加载失败任务
const loadFailedTasks = async () => {
try {
const response = await axios.get('/api/tasks/failed');
if (response.data.code === 200) {
failedTasks.value = response.data.data;
}
} catch (error) {
console.error('加载失败任务失败:', error);
}
};
// 加载高频任务
const loadFrequentTasks = async () => {
try {
const response = await axios.get('/api/tasks/frequent');
if (response.data.code === 200) {
frequentTasks.value = response.data.data;
}
} catch (error) {
console.error('加载高频任务失败:', error);
}
};
// 加载Cron表达式
const loadCronExpressions = async () => {
try {
const response = await axios.get('/api/tasks/cron');
if (response.data.code === 200) {
cronExpressions.value = response.data.data;
cronTotal.value = cronExpressions.value.length;
}
} catch (error) {
console.error('加载Cron表达式失败:', error);
}
};
// 新增任务
const addTask = async () => {
try {
// 转换日期格式
if (newTask.value.type === 'single') {
newTask.value.execute_at = new Date(newTask.value.execute_at).toISOString();
}
const response = await axios.post('/api/tasks', newTask.value);
if (response.data.code === 200) {
showToast('任务创建成功', 'success');
showAddTaskModal.value = false;
resetNewTask();
loadTasks();
} else {
showToast('任务创建失败', 'error');
}
} catch (error) {
showToast('任务创建失败', 'error');
console.error('创建任务失败:', error);
}
};
// 重置新增任务表单
const resetNewTask = () => {
newTask.value = {
name: '',
description: '',
type: 'single',
cron_expr: '',
execute_at: '',
timeout: 30,
task_func_name: '',
params: ''
};
};
// 编辑任务
const editTask = (task) => {
editingTask.value = { ...task };
// 转换日期格式
if (editingTask.value.type === 'single') {
editingTask.value.execute_at = new Date(editingTask.value.execute_at).toISOString().slice(0, 16);
}
showEditTaskModal.value = true;
};
// 更新任务
const updateTask = async () => {
try {
// 转换日期格式
if (editingTask.value.type === 'single') {
editingTask.value.execute_at = new Date(editingTask.value.execute_at).toISOString();
}
const response = await axios.put(`/api/tasks/${editingTask.value.id}`, editingTask.value);
if (response.data.code === 200) {
showToast('任务更新成功', 'success');
showEditTaskModal.value = false;
loadTasks();
} else {
showToast('任务更新失败', 'error');
}
} catch (error) {
showToast('任务更新失败', 'error');
console.error('更新任务失败:', error);
}
};
// 删除任务
const deleteTask = async (id) => {
if (confirm('确定要删除这个任务吗?')) {
try {
const response = await axios.delete(`/api/tasks/${id}`);
if (response.data.code === 200) {
showToast('任务删除成功', 'success');
loadTasks();
} else {
showToast('任务删除失败', 'error');
}
} catch (error) {
showToast('任务删除失败', 'error');
console.error('删除任务失败:', error);
}
}
};
// 切换任务状态
const toggleTaskStatus = async (id, enabled) => {
try {
const action = enabled ? 'stop' : 'start';
const response = await axios.post(`/api/tasks/${id}/${action}`);
if (response.data.code === 200) {
showToast(`任务${enabled ? '终止' : '启动'}成功`, 'success');
loadTasks();
} else {
showToast(`任务${enabled ? '终止' : '启动'}失败`, 'error');
}
} catch (error) {
showToast(`任务${enabled ? '终止' : '启动'}失败`, 'error');
console.error(`${enabled ? '终止' : '启动'}任务失败:`, error);
}
};
// 切换任务暂停状态
const toggleTaskPause = async (id, paused) => {
try {
const action = paused ? 'resume' : 'pause';
const response = await axios.post(`/api/tasks/${id}/${action}`);
if (response.data.code === 200) {
showToast(`任务${paused ? '继续' : '暂停'}成功`, 'success');
loadTasks();
} else {
showToast(`任务${paused ? '继续' : '暂停'}失败`, 'error');
}
} catch (error) {
showToast(`任务${paused ? '继续' : '暂停'}失败`, 'error');
console.error(`${paused ? '继续' : '暂停'}任务失败:`, error);
}
};
// 查看任务日志
const showTaskLogs = async (id) => {
try {
const response = await axios.get(`/api/tasks/logs?task_id=${id}`);
if (response.data.code === 200) {
taskLogs.value = response.data.data.logs;
taskName.value = response.data.data.taskName || '';
taskFuncName.value = response.data.data.taskFuncName || '';
taskFuncDesc.value = response.data.data.taskFuncDesc || '';
showLogsModal.value = true;
} else {
showToast('加载任务日志失败', 'error');
}
} catch (error) {
showToast('加载任务日志失败', 'error');
console.error('加载任务日志失败:', error);
}
};
// 选择Cron模板
const selectCronTemplate = (value) => {
if (value) {
newTask.value.cron_expr = value;
}
};
// 处理任务函数变更
const handleTaskFuncChange = (funcName) => {
// 可以在这里添加任务函数变更的处理逻辑
};
// 排序任务
const sortTasks = () => {
// 实现任务排序逻辑
};
// 格式化日期时间
const formatDateTime = (dateTime) => {
if (!dateTime) return '';
const date = new Date(dateTime);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
// 获取Cron间隔描述
const getCronInterval = (cronExpr) => {
if (!cronExpr) return '';
// 简单的Cron表达式解析
if (cronExpr === '*/10 * * * * *') return '每10秒';
if (cronExpr === '0 * * * * *') return '每分钟';
if (cronExpr === '0 */5 * * * *') return '每5分钟';
if (cronExpr === '0 0 * * * *') return '每小时';
if (cronExpr === '0 0 0 * * *') return '每天';
if (cronExpr === '0 0 0 * * 0') return '每周';
if (cronExpr === '0 0 0 1 * *') return '每月';
return cronExpr;
};
// 计算分页显示的页码
const tasksDisplayedPages = computed(() => {
const totalPages = Math.ceil(tasksTotal.value / tasksPageSize.value);
const pages = [];
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
return pages;
});
const cronDisplayedPages = computed(() => {
const totalPages = Math.ceil(cronTotal.value / cronPageSize.value);
const pages = [];
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
return pages;
});
// 计算分页后的任务
const paginatedTasks = computed(() => {
const start = (tasksPage.value - 1) * tasksPageSize.value;
const end = start + tasksPageSize.value;
return tasks.value.slice(start, end);
});
const paginatedCronExpressions = computed(() => {
const start = (cronPage.value - 1) * cronPageSize.value;
const end = start + cronPageSize.value;
return cronExpressions.value.slice(start, end);
});
// 初始化图表
const initCharts = () => {
// 实现图表初始化逻辑
};
// 显示提示
const showToast = (message, type = 'info') => {
// 实现提示功能
};
// 生命周期钩子
onMounted(() => {
loadTasks();
loadAllTaskLogs();
loadTaskFuncs();
loadCronExpressions();
});
watch(activeTab, (newTab) => {
if (newTab === 'statistics') {
loadStatistics();
loadFailedTasks();
loadFrequentTasks();
}
});
return {
// 状态
activeTab,
tasks,
taskLogs,
allTaskLogs,
taskFuncs,
statistics,
failedTasks,
frequentTasks,
cronExpressions,
tasksPage,
tasksPageSize,
tasksTotal,
cronPage,
cronPageSize,
cronTotal,
sortBy,
sortOrder,
showAddTaskModal,
showEditTaskModal,
showLogsModal,
showCronModal,
newTask,
editingTask,
editingCron,
taskName,
taskFuncName,
taskFuncDesc,
commonCronExpressions,
// 方法
loadTasks,
addTask,
editTask,
updateTask,
deleteTask,
toggleTaskStatus,
toggleTaskPause,
showTaskLogs,
selectCronTemplate,
handleTaskFuncChange,
sortTasks,
formatDateTime,
getCronInterval,
tasksDisplayedPages,
cronDisplayedPages,
paginatedTasks,
paginatedCronExpressions
};
}
}).mount('#app');
</script>
七、项目应用的第三方包介绍
1. 后端第三方包
| 包名 | 版本 | 用途 | 来源 |
|---|---|---|---|
github.com/kataras/iris/v12 | 12.2.11 | Web框架 | go.mod |
go.uber.org/zap | 1.24.0 | 日志库 | go.mod |
xorm.io/xorm | 1.3.6 | ORM框架 | go.mod |
github.com/robfig/cron/v3 | 3.0.1 | Cron表达式解析 | go.mod |
2. 前端第三方库
| 库名 | 版本 | 用途 | 来源 |
|---|---|---|---|
| Vue.js | 3.x | 前端框架 | static/js/vue.global.js |
| Axios | 0.27.2 | HTTP客户端 | static/js/axios.min.js |
| ECharts | 5.4.0 | 图表库 | static/js/echarts.min.js |
八、项目应用场景
1. 系统自动化运维
应用场景:定期执行系统维护任务,如日志清理、数据库备份、系统更新等。
优势:
- 自动化执行,减少人工干预
- 定时执行,确保任务在合适的时间点运行
- 执行结果可追踪,便于问题排查
2. 数据处理与分析
应用场景:定期处理和分析业务数据,如销售数据汇总、用户行为分析、报表生成等。
优势:
- 周期性执行,确保数据及时更新
- 可设置超时时间,避免任务执行过长影响系统性能
- 执行结果可记录,便于数据追溯
3. 外部系统集成
应用场景:与外部系统进行数据同步,如API调用、数据采集、消息推送等。
优势:
- 可设置执行间隔,控制API调用频率
- 执行失败可记录,便于问题排查
- 可暂停和继续任务,灵活控制集成过程
4. 定时任务调度
应用场景:执行各种定时任务,如邮件发送、短信提醒、系统通知等。
优势:
- 支持一次性和周期性任务
- 可设置执行时间,确保任务在指定时间执行
- 超期任务自动处理,避免任务堆积
5. 高频任务监控
应用场景:监控高频执行的任务,如实时数据采集、状态检查等。
优势:
- 可识别高频任务,便于资源规划
- 执行次数统计,便于任务评估
- 执行状态监控,确保任务正常运行
总结
本任务调度管理系统是一个功能完整、架构清晰的Go语言项目,采用前后端分离架构,后端使用Go语言的Iris框架,前端使用Vue3+HTML5实现。系统支持任务的创建、编辑、删除、启动/终止、暂停/继续等基本操作,支持一次性任务和周期性任务两种类型,具有任务执行状态实时监控、执行结果记录与展示、超期任务自动处理、任务执行统计分析等功能。
项目代码组织合理,核心功能实现完善,具有良好的可扩展性和可维护性,可应用于各种需要自动化任务调度的场景。通过本项目的开发,我们展示了如何使用Go语言构建一个完整的任务调度系统,包括后端API开发、前端界面实现、数据库操作、任务调度和执行等各个方面。
该系统不仅满足了基本的任务调度需求,还提供了丰富的统计分析功能,帮助用户更好地了解任务执行情况,优化任务配置和资源分配。同时,系统的超期任务自动处理功能确保了任务的及时清理,避免了任务堆积和资源浪费。
总之,本任务调度管理系统是一个实用、高效、可靠的自动化任务处理平台,为各种业务场景提供了强大的任务调度能力。