Files
TaskPool/internal/services/tasks/task_log_service.go
T

252 lines
7.2 KiB
Go

package tasks
import (
"encoding/json"
"time"
"github.com/engigu/baihu-panel/internal/constant"
"github.com/engigu/baihu-panel/internal/database"
"github.com/engigu/baihu-panel/internal/logger"
"github.com/engigu/baihu-panel/internal/models"
"github.com/engigu/baihu-panel/internal/systime"
"github.com/engigu/baihu-panel/internal/utils"
)
// SendStatsService 接口定义(避免循环依赖)
type SendStatsService interface {
IncrementStats(taskID string, status string) error
}
// TaskLogService 任务日志服务
type TaskLogService struct {
sendStatsService SendStatsService
}
// NewTaskLogService 创建任务日志服务
func NewTaskLogService(sendStatsService SendStatsService) *TaskLogService {
return &TaskLogService{
sendStatsService: sendStatsService,
}
}
// CleanConfig 清理配置
type CleanConfig struct {
Type string `json:"type"` // day 或 count
Keep int `json:"keep"` // 保留天数或条数
}
// CreateEmptyLog 创建一个空的日志记录(任务开始时调用)
func (s *TaskLogService) CreateEmptyLog(taskID string, command string) (*models.TaskLog, error) {
startTime := models.Now()
taskLog := &models.TaskLog{
ID: utils.GenerateID(),
TaskID: taskID,
Command: models.BigText(command),
Status: "running",
StartTime: &startTime,
CreatedAt: models.Now(),
}
if err := database.DB.Create(taskLog).Error; err != nil {
return nil, err
}
// 任务开始时即更新任务的 last_run 为启动时间
database.DB.Model(&models.Task{}).Where("id = ?", taskID).Update("last_run", startTime)
return taskLog, nil
}
// SaveTaskLog 保存或更新任务日志
func (s *TaskLogService) SaveTaskLog(taskLog *models.TaskLog) error {
var err error
if taskLog.ID != "" {
// 先检查记录是否存在,如果不存在则创建,存在则更新
var count int64
database.DB.Model(&models.TaskLog{}).Where("id = ?", taskLog.ID).Count(&count)
if count > 0 {
err = database.DB.Model(taskLog).Where("id = ?", taskLog.ID).Updates(taskLog).Error
} else {
err = database.DB.Create(taskLog).Error
}
} else {
taskLog.ID = utils.GenerateID()
if taskLog.CreatedAt.Time().IsZero() {
taskLog.CreatedAt = models.Now()
}
err = database.DB.Create(taskLog).Error
}
if err != nil {
return err
}
// 更新任务的 last_run
// 更新任务的 last_run,优先使用日志记录的启动时间
lastRun := models.Now()
if taskLog.StartTime != nil {
lastRun = *taskLog.StartTime
}
database.DB.Model(&models.Task{}).Where("id = ?", taskLog.TaskID).Update("last_run", lastRun)
return nil
}
// UpdateTaskDuration 更新任务耗时(心跳)
func (s *TaskLogService) UpdateTaskDuration(logID string, duration int64) error {
return database.DB.Model(&models.TaskLog{}).Where("id = ?", logID).Update("duration", duration).Error
}
// UpdateLogCommand 更新日志中的命令内容(用于动态生成的命令脱敏)
func (s *TaskLogService) UpdateLogCommand(logID string, command string) error {
return database.DB.Model(&models.TaskLog{}).Where("id = ?", logID).Update("command", models.BigText(command)).Error
}
// UpdateTaskStats 更新任务统计
func (s *TaskLogService) UpdateTaskStats(taskID string, status string) {
if s.sendStatsService == nil {
logger.Error("[TaskLog] SendStatsService 未初始化")
return
}
err := s.sendStatsService.IncrementStats(taskID, status)
if err != nil {
logger.Errorf("UpdateTaskStats err: %v", err)
return
}
}
// CleanTaskLogs 清理任务日志
func (s *TaskLogService) CleanTaskLogs(taskID string) {
var task models.Task
res := database.DB.Where("id = ?", taskID).Limit(1).Find(&task)
if res.Error != nil || res.RowsAffected == 0 {
return
}
if task.CleanConfig == "" {
return
}
var config CleanConfig
if err := json.Unmarshal([]byte(task.CleanConfig), &config); err != nil {
logger.Errorf("[TaskLog] 解析清理配置失败: %v", err)
return
}
if config.Keep <= 0 {
return
}
var deleted int64
switch config.Type {
case "day":
cutoff := systime.InCST(time.Now()).AddDate(0, 0, -config.Keep)
result := database.DB.Where("task_id = ? AND created_at < ?", taskID, cutoff).Delete(&models.TaskLog{})
deleted = result.RowsAffected
case "count":
var boundaryLog models.TaskLog
res := database.DB.Where("task_id = ?", taskID).Order("id DESC").Offset(config.Keep - 1).Limit(1).Find(&boundaryLog)
if res.Error == nil && res.RowsAffected > 0 {
result := database.DB.Where("task_id = ? AND id < ?", taskID, boundaryLog.ID).Delete(&models.TaskLog{})
deleted = result.RowsAffected
}
}
if deleted > 0 {
logger.Infof("[TaskLog] 清理旧日志: #%s 共 %d 条", taskID, deleted)
}
}
// ProcessTaskCompletion 处理任务完成后的所有操作(保存日志、更新统计、清理旧日志)
func (s *TaskLogService) ProcessTaskCompletion(taskLog *models.TaskLog) error {
// 1. 保存/更新日志
if err := s.SaveTaskLog(taskLog); err != nil {
return err
}
// 2. 更新统计
s.UpdateTaskStats(taskLog.TaskID, taskLog.Status)
// 3. 异步清理旧日志
go s.CleanTaskLogs(taskLog.TaskID)
return nil
}
// CreateTaskLogFromAgentResult 从 Agent 结果创建任务日志
func (s *TaskLogService) CreateTaskLogFromAgentResult(result *models.AgentTaskResult) (*models.TaskLog, error) {
// 裁剪并压缩输出
trimmedOutput := utils.TrimLog(result.Output, constant.MaxLogSize)
compressed, err := utils.CompressToBase64(trimmedOutput)
if err != nil {
logger.Errorf("[TaskLog] 压缩日志失败: %v", err)
compressed = ""
}
logID := result.LogID
if logID == "" {
logID = utils.GenerateID()
}
taskLog := &models.TaskLog{
ID: logID,
TaskID: result.TaskID,
AgentID: &result.AgentID,
Command: models.BigText(result.Command),
Output: models.BigText(compressed),
Error: models.BigText(result.Error),
Status: result.Status,
Duration: result.Duration,
ExitCode: result.ExitCode,
CreatedAt: models.Now(),
}
// 处理开始和结束时间
if result.StartTime > 0 {
startTime := models.LocalTime(time.Unix(result.StartTime, 0))
taskLog.StartTime = &startTime
}
if result.EndTime > 0 {
endTime := models.LocalTime(time.Unix(result.EndTime, 0))
taskLog.EndTime = &endTime
}
return taskLog, nil
}
// CreateTaskLogFromLocalExecution 从本地执行结果创建任务日志
func (s *TaskLogService) CreateTaskLogFromLocalExecution(taskID string, command, output, systemErr, status string, duration int64, exitCode int, start, end time.Time, isCompressed bool) (*models.TaskLog, error) {
var compressed string
var err error
if isCompressed {
compressed = output
} else {
// 裁剪并压缩输出
trimmedOutput := utils.TrimLog(output, constant.MaxLogSize)
compressed, err = utils.CompressToBase64(trimmedOutput)
if err != nil {
logger.Errorf("[TaskLog] 压缩日志失败: %v", err)
compressed = ""
}
}
startTime := models.LocalTime(start)
endTime := models.LocalTime(end)
taskLog := &models.TaskLog{
ID: utils.GenerateID(),
TaskID: taskID,
Command: models.BigText(command),
Output: models.BigText(compressed),
Error: models.BigText(systemErr),
Status: status,
Duration: duration,
ExitCode: exitCode,
StartTime: &startTime,
EndTime: &endTime,
CreatedAt: models.Now(),
}
return taskLog, nil
}