feat(cron): 实现定时任务管理功能

- 新增 cron模块,支持定时任务管理- 实现了任务列表获取、任务添加、任务移除和任务状态获取等接口
- 添加了默认任务,包括数据同步、数据清理、健康检查和缓存刷新等
- 实现了优雅关闭功能,确保在服务停止时正确停止所有任务
- 添加了定时任务相关文档和使用指南
This commit is contained in:
hu xiaotong
2025-06-23 15:19:38 +08:00
parent 89a6cdc001
commit cecb19e497
16 changed files with 1571 additions and 7 deletions

View File

@@ -0,0 +1,238 @@
package cron
import (
"context"
"epic/internal/model/dto"
"epic/utility"
"fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/os/gtime"
"time"
)
// ThirdPartyDataSync 第三方数据同步器
type ThirdPartyDataSync struct {
client *gclient.Client
}
// NewThirdPartyDataSync 创建第三方数据同步器
func NewThirdPartyDataSync() *ThirdPartyDataSync {
return &ThirdPartyDataSync{
client: gclient.New().Timeout(30 * time.Second),
}
}
// SyncHeroData 同步英雄数据
func (t *ThirdPartyDataSync) SyncHeroData(ctx context.Context) error {
g.Log().Info(ctx, "开始同步英雄数据...")
// 示例从第三方API获取英雄数据
heroData, err := t.fetchHeroDataFromAPI(ctx)
if err != nil {
g.Log().Error(ctx, "获取英雄数据失败:", err)
return err
}
// 处理并保存数据
if err := t.processAndSaveHeroData(ctx, heroData); err != nil {
g.Log().Error(ctx, "处理英雄数据失败:", err)
return err
}
g.Log().Info(ctx, "英雄数据同步完成")
return nil
}
// SyncArtifactData 同步神器数据
func (t *ThirdPartyDataSync) SyncArtifactData(ctx context.Context) error {
g.Log().Info(ctx, "开始同步神器数据...")
utility.RedisCache.Set(ctx, "artifacts_all11", "asd", 0)
// 示例从第三方API获取神器数据
//artifactData, err := t.fetchArtifactDataFromAPI(ctx)
//if err != nil {
// g.Log().Error(ctx, "获取神器数据失败:", err)
// return err
//}
//
//// 处理并保存数据
//if err := t.processAndSaveArtifactData(ctx, artifactData); err != nil {
// g.Log().Error(ctx, "处理神器数据失败:", err)
// return err
//}
g.Log().Info(ctx, "神器数据同步完成")
return nil
}
// fetchHeroDataFromAPI 从API获取英雄数据
func (t *ThirdPartyDataSync) fetchHeroDataFromAPI(ctx context.Context) ([]byte, error) {
// 示例API地址实际使用时需要替换为真实的API
apiURL := "https://api.example.com/heroes"
// 添加请求头
headers := map[string]string{
"User-Agent": "EpicGameBot/1.0",
"Accept": "application/json",
}
// 发送GET请求
resp, err := t.client.Header(headers).Get(ctx, apiURL)
if err != nil {
return nil, fmt.Errorf("API请求失败: %v", err)
}
defer resp.Close()
// 检查响应状态
if resp.StatusCode != 200 {
return nil, fmt.Errorf("API响应错误状态码: %d", resp.StatusCode)
}
// 读取响应内容
content := resp.ReadAll()
g.Log().Debug(ctx, "API响应内容长度:", len(content))
return content, nil
}
// 从API获取神器数据
func (t *ThirdPartyDataSync) fetchArtifactDataFromAPI(ctx context.Context) (string, error) {
// 示例API地址
apiURL := "https://static.smilegatemegaport.com/gameRecord/epic7/epic7_artifact.json?_=1729322698936"
headers := map[string]string{
//"User-Agent": "EpicGameBot/1.0",
"Accept": "application/json",
}
resp, err := t.client.Header(headers).Get(ctx, apiURL)
if err != nil {
return "", fmt.Errorf("API请求失败: %v", err)
}
defer resp.Close()
if resp.StatusCode != 200 {
return "", fmt.Errorf("API响应错误状态码: %d", resp.StatusCode)
}
content := resp.ReadAll()
g.Log().Debug(ctx, "神器API响应内容长度:", len(content))
return string(content), nil
}
// processAndSaveHeroData 处理并保存英雄数据
func (t *ThirdPartyDataSync) processAndSaveHeroData(ctx context.Context, data []byte) error {
// 使用 gjson 解析
j := gjson.New(data)
// 检查json对象本身和其内部值并使用 .Var().IsSlice() 这种更可靠的方式判断是否为数组
if j == nil || j.IsNil() || !j.Var().IsSlice() {
return fmt.Errorf("英雄数据格式错误期望是一个JSON数组")
}
var heroes []*dto.ThirdPartyHeroDTO
if err := j.Scan(&heroes); err != nil {
return fmt.Errorf("解析英雄数据到DTO失败: %v", err)
}
g.Log().Info(ctx, "解析到", len(heroes), "个英雄数据")
// 批量处理数据
for _, hero := range heroes {
if err := t.saveHeroData(ctx, hero); err != nil {
g.Log().Error(ctx, "保存英雄数据失败:", err)
// 继续处理其他数据,不中断整个流程
continue
}
}
return nil
}
// processAndSaveArtifactData 处理并保存神器数据
func (t *ThirdPartyDataSync) processAndSaveArtifactData(ctx context.Context, data string) error {
// 使用 gjson 解析
j := gjson.New(data)
zhcn := j.Get("zh-CN")
// 检查json对象本身和其内部值并使用 .Var().IsSlice() 这种更可靠的方式判断是否为数组
if !zhcn.IsSlice() {
return fmt.Errorf("神器数据格式错误期望是一个JSON数组")
}
var artifacts []*dto.ThirdPartyArtifactDTO
if err := zhcn.Scan(&artifacts); err != nil {
return fmt.Errorf("解析神器数据到DTO失败: %v", err)
}
g.Log().Info(ctx, "解析到", len(artifacts), "个神器数据")
// 批量处理数据
for _, artifact := range artifacts {
if err := t.saveArtifactData(ctx, artifact); err != nil {
g.Log().Error(ctx, "保存神器数据失败:", err)
continue
}
}
return nil
}
// saveHeroData 保存单个英雄数据
func (t *ThirdPartyDataSync) saveHeroData(ctx context.Context, hero *dto.ThirdPartyHeroDTO) error {
// TODO: 实现具体的数据库保存逻辑
// 现在 hero 是一个强类型对象,可以直接使用 hero.Code, hero.Name 等
// 示例:记录同步日志
syncLog := map[string]interface{}{
"type": "hero_sync",
"hero_code": hero.Code,
"sync_time": gtime.Now(),
"status": "success",
}
g.Log().Debug(ctx, "保存英雄数据:", syncLog)
return nil
}
// 保存单个神器数据
func (t *ThirdPartyDataSync) saveArtifactData(ctx context.Context, artifact *dto.ThirdPartyArtifactDTO) error {
// TODO: 实现具体的数据库保存逻辑
// 现在 artifact 是一个强类型对象, 可以直接使用 artifact.Code, artifact.Name 等
// 示例:记录同步日志
syncLog := map[string]interface{}{
"type": "artifact_sync",
"artifact_code": artifact.Code,
"sync_time": gtime.Now(),
"status": "success",
}
g.Log().Debug(ctx, "保存神器数据:", syncLog)
return nil
}
// SyncAllData 同步所有数据
func (t *ThirdPartyDataSync) SyncAllData(ctx context.Context) error {
g.Log().Info(ctx, "开始同步所有第三方数据...")
// 同步英雄数据
if err := t.SyncHeroData(ctx); err != nil {
g.Log().Error(ctx, "英雄数据同步失败:", err)
// 继续同步其他数据
}
// 同步神器数据
if err := t.SyncArtifactData(ctx); err != nil {
g.Log().Error(ctx, "神器数据同步失败:", err)
// 继续同步其他数据
}
// 可以继续添加其他数据类型的同步
g.Log().Info(ctx, "所有第三方数据同步完成")
return nil
}