Explorar el Código

feat(task): 添加游戏盾过期检查和数据同步功能

- 新增 GameShieldTask 接口和 gameShieldTask 结构体
- 实现检查已过期和即将过期的游戏盾功能
- 添加同步所有游戏盾记录过期时间的功能
- 在任务服务器中注册游戏盾检查和数据同步任务- 更新游戏盾仓库接口,增加相关查询方法
fusu hace 3 meses
padre
commit
9412eb832e

+ 2 - 0
cmd/task/wire/wire.go

@@ -20,11 +20,13 @@ var repositorySet = wire.NewSet(
 	repository.NewRepository,
 	repository.NewTransaction,
 	repository.NewUserRepository,
+	repository.NewGameShieldRepository,
 )
 
 var taskSet = wire.NewSet(
 	task.NewTask,
 	task.NewUserTask,
+	task.NewGameShieldTask,
 )
 var serverSet = wire.NewSet(
 	server.NewTaskServer,

+ 5 - 3
cmd/task/wire/wire_gen.go

@@ -27,7 +27,9 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 	taskTask := task.NewTask(transaction, logger, sidSid)
 	userRepository := repository.NewUserRepository(repositoryRepository)
 	userTask := task.NewUserTask(taskTask, userRepository)
-	taskServer := server.NewTaskServer(logger, userTask)
+	gameShieldRepository := repository.NewGameShieldRepository(repositoryRepository)
+	gameShieldTask := task.NewGameShieldTask(taskTask, gameShieldRepository)
+	taskServer := server.NewTaskServer(logger, userTask, gameShieldTask)
 	appApp := newApp(taskServer)
 	return appApp, func() {
 	}, nil
@@ -35,9 +37,9 @@ func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), err
 
 // wire.go:
 
-var repositorySet = wire.NewSet(repository.NewDB, repository.NewRepository, repository.NewTransaction, repository.NewUserRepository)
+var repositorySet = wire.NewSet(repository.NewDB, repository.NewRepository, repository.NewTransaction, repository.NewUserRepository, repository.NewGameShieldRepository)
 
-var taskSet = wire.NewSet(task.NewTask, task.NewUserTask)
+var taskSet = wire.NewSet(task.NewTask, task.NewUserTask, task.NewGameShieldTask)
 
 var serverSet = wire.NewSet(server.NewTaskServer)
 

+ 72 - 0
internal/repository/gameshield.go

@@ -3,6 +3,7 @@ package repository
 import (
 	"context"
 	"github.com/go-nunu/nunu-layout-advanced/internal/model"
+	"time"
 )
 
 type GameShieldRepository interface {
@@ -19,6 +20,10 @@ type GameShieldRepository interface {
 	UpdateGameShieldByHostId(ctx context.Context, gameShield *model.GameShield) error
 	GetGameShieldByHostId(ctx context.Context, hostId int) (*model.GameShield, error)
 	GetHostById(ctx context.Context, id int) (string, error)
+	GetSoonExpiredGameShields(ctx context.Context, daysThreshold int) ([]*model.GameShield, error)
+	GetExpiredGameShields(ctx context.Context) ([]*model.GameShield, error)
+	SyncExpireTimeFromHostNextDueDate(ctx context.Context, uid int, hostId string) error
+	GetAllGameShield(ctx context.Context) ([]*model.GameShield, error)
 }
 
 func NewGameShieldRepository(
@@ -151,3 +156,70 @@ func (r *gameShieldRepository) GetHostById(ctx context.Context, id int) (string,
 	}
 	return res, nil
 }
+
+// GetSoonExpiredGameShields 获取即将在指定天数内过期的记录
+func (r *gameShieldRepository) GetSoonExpiredGameShields(ctx context.Context, daysThreshold int) ([]*model.GameShield, error) {
+	var shields []*model.GameShield
+
+	// 当前时间戳
+	nowTimestamp := time.Now().Unix()
+
+	// 计算N天后的时间戳
+	futureTimestamp := nowTimestamp + int64(daysThreshold*24*60*60)
+
+	// 查询即将到期的记录 (过期时间在当前时间和N天后之间)
+	if err := r.DB(ctx).
+		Where("expire_time > ? AND expire_time < ?", nowTimestamp, futureTimestamp).
+		Find(&shields).Error; err != nil {
+		return nil, err
+	}
+
+	return shields, nil
+}
+
+// GetExpiredGameShields 获取已过期的记录
+func (r *gameShieldRepository) GetExpiredGameShields(ctx context.Context) ([]*model.GameShield, error) {
+	var shields []*model.GameShield
+
+	// 当前时间戳
+	nowTimestamp := time.Now().Unix()
+
+	// 查询已过期的记录(过期时间小于当前时间)
+	if err := r.DB(ctx).
+		Where("expire_time < ?", nowTimestamp).
+		Find(&shields).Error; err != nil {
+		return nil, err
+	}
+
+	return shields, nil
+}
+
+// SyncExpireTimeFromHostNextDueDate 同步host表的nextduedate到game_shield表的ExpireTime
+func (r *gameShieldRepository) SyncExpireTimeFromHostNextDueDate(ctx context.Context, uid int, hostId string) error {
+	var nextduedate int64
+
+	// 先从shd_host表获取nextduedate
+	err := r.DB(ctx).Table("shd_host").
+		Select("nextduedate").
+		Where("id = ?", hostId).
+		Where("uid = ?", uid).
+		Scan(&nextduedate).Error
+
+	if err != nil {
+		return err
+	}
+
+	// 更新shd_game_shield表的ExpireTime
+	return r.DB(ctx).Model(&model.GameShield{}).
+		Where("host_id = ?", hostId).
+		Where("uid = ?", uid).
+		Update("expire_time", nextduedate).Error
+}
+
+func (r *gameShieldRepository) GetAllGameShield(ctx context.Context) ([]*model.GameShield, error) {
+	var res []*model.GameShield
+	if err := r.DB(ctx).Model(&model.GameShield{}).Find(&res).Error; err != nil {
+		return nil, err
+	}
+	return res, nil
+}

+ 33 - 9
internal/server/task.go

@@ -10,18 +10,21 @@ import (
 )
 
 type TaskServer struct {
-	log       *log.Logger
-	scheduler *gocron.Scheduler
-	userTask  task.UserTask
+	log            *log.Logger
+	scheduler      *gocron.Scheduler
+	userTask       task.UserTask
+	gameShieldTask task.GameShieldTask
 }
 
 func NewTaskServer(
 	log *log.Logger,
 	userTask task.UserTask,
+	gameShieldTask task.GameShieldTask,
 ) *TaskServer {
 	return &TaskServer{
-		log:      log,
-		userTask: userTask,
+		log:            log,
+		userTask:       userTask,
+		gameShieldTask: gameShieldTask,
 	}
 }
 func (t *TaskServer) Start(ctx context.Context) error {
@@ -35,16 +38,37 @@ func (t *TaskServer) Start(ctx context.Context) error {
 	// t.scheduler = gocron.NewScheduler(time.FixedZone("PRC", 8*60*60))
 
 	//_, err := t.scheduler.Every("3s").Do(func()
-	_, err := t.scheduler.CronWithSeconds("0/3 * * * * *").Do(func() {
-		err := t.userTask.CheckUser(ctx)
+	//_, err := t.scheduler.CronWithSeconds("0/3 * * * * *").Do(func() {
+	//	err := t.userTask.CheckUser(ctx)
+	//	if err != nil {
+	//		t.log.Error("CheckUser error", zap.Error(err))
+	//	}
+	//})
+	//if err != nil {
+	//	t.log.Error("CheckUser error", zap.Error(err))
+	//}
+
+	// 添加游戏盾检查任务 - 每2小时执行
+	_, err := t.scheduler.Cron("0 */2 * * *").Do(func() {
+		err := t.gameShieldTask.CheckGameShield(ctx)
 		if err != nil {
-			t.log.Error("CheckUser error", zap.Error(err))
+			t.log.Error("CheckGameShield error", zap.Error(err))
 		}
 	})
 	if err != nil {
-		t.log.Error("CheckUser error", zap.Error(err))
+		t.log.Error("Register CheckGameShield task error", zap.Error(err))
 	}
 
+	// 添加游戏盾数据同步任务 - 每天凌晨3点执行
+	_, err = t.scheduler.Cron("* * * * *").Do(func() {
+		err := t.gameShieldTask.SyncAllExpireTimeFromHost(ctx)
+		if err != nil {
+			t.log.Error("SyncAllExpireTimeFromHost error", zap.Error(err))
+		}
+	})
+	if err != nil {
+		t.log.Error("Register SyncAllExpireTimeFromHost task error", zap.Error(err))
+	}
 	t.scheduler.StartBlocking()
 	return nil
 }

+ 182 - 0
internal/task/gameShield.go

@@ -1 +1,183 @@
 package task
+
+import (
+	"context"
+	"github.com/go-nunu/nunu-layout-advanced/internal/repository"
+	"go.uber.org/zap"
+	"strconv"
+	"time"
+)
+
+type GameShieldTask interface {
+	CheckGameShield(ctx context.Context) error
+	SyncAllExpireTimeFromHost(ctx context.Context) error
+}
+
+func NewGameShieldTask(
+	task *Task,
+	gameShieldRepo repository.GameShieldRepository,
+
+) GameShieldTask {
+	return &gameShieldTask{
+		Task:           task,
+		gameShieldRepo: gameShieldRepo,
+	}
+}
+
+type gameShieldTask struct {
+	*Task
+	gameShieldRepo repository.GameShieldRepository
+}
+
+// 检查已过期的记录
+func (t gameShieldTask) CheckExpiredGameShields(ctx context.Context) error {
+	// 获取已过期的商品
+	expiredShields, err := t.gameShieldRepo.GetExpiredGameShields(ctx)
+	if err != nil {
+		t.logger.Error("查询已过期的游戏盾失败", zap.Error(err))
+		return err
+	}
+
+	t.logger.Info("找到已过期的游戏盾商品", zap.Int("数量", len(expiredShields)))
+
+	// 遍历已过期的商品
+	for _, shield := range expiredShields {
+		t.logger.Info("已过期商品",
+			zap.Int("ID", shield.Id),
+			zap.Int("用户ID", shield.Uid),
+			zap.String("商品", shield.AppName),
+			zap.Time("过期时间", time.Unix(shield.ExpireTime, 0)))
+
+		// 检查用户是否已续费(检查nextduedate字段)
+		nextduedate, err := t.gameShieldRepo.GetGameShieldNextduedate(ctx, int64(shield.Uid), shield.HostId)
+		if err != nil {
+			t.logger.Error("获取nextduedate失败", zap.Int("用户ID", shield.Uid), zap.Error(err))
+			continue
+		}
+
+		nextduedateTime, err := strconv.ParseInt(nextduedate, 10, 64)
+		if err != nil {
+			t.logger.Error("解析nextduedate失败", zap.Error(err))
+			continue
+		}
+
+		currentTime := time.Now().Unix()
+		renewed := nextduedateTime > currentTime
+
+		if renewed {
+			// 已过期但已续费,更新过期时间
+			shield.ExpireTime = nextduedateTime
+			if err := t.gameShieldRepo.UpdateGameShield(ctx, shield); err != nil {
+				t.logger.Error("更新过期游戏盾的过期时间失败",
+					zap.String("商品", shield.AppName),
+					zap.Error(err))
+				continue
+			}
+
+			t.logger.Info("已过期商品续期成功",
+				zap.Int("用户ID", shield.Uid),
+				zap.String("商品", shield.AppName),
+				zap.Time("新过期时间", time.Unix(nextduedateTime, 0)))
+		} else {
+			// 已过期且未续费,执行过期操作
+			t.logger.Info("商品已过期且未续费",
+				zap.Int("用户ID", shield.Uid),
+				zap.String("商品", shield.AppName))
+
+			// TODO: 添加您的过期处理逻辑,例如:
+			// - 发送过期通知
+			// - 禁用服务
+			// - 标记记录状态
+		}
+	}
+
+	return nil
+}
+
+// CheckSoonExpiredGameShields 检查即将过期的记录并处理续费
+func (t gameShieldTask) CheckSoonExpiredGameShields(ctx context.Context) error {
+	// 获取7天内即将过期的商品
+	soonExpiredShields, err := t.gameShieldRepo.GetSoonExpiredGameShields(ctx, 7)
+	if err != nil {
+		t.logger.Error("查询即将过期的游戏盾失败", zap.Error(err))
+		return err
+	}
+
+	t.logger.Info("找到 %d 个即将过期的游戏盾商品", zap.Int("数量", len(soonExpiredShields)))
+
+	// 遍历即将过期的商品
+	for _, shield := range soonExpiredShields {
+		t.logger.Info("即将过期商品",
+			zap.Int("ID", shield.Id),
+			zap.Int("用户ID", shield.Uid),
+			zap.String("商品", shield.AppName),
+			zap.Time("过期时间", time.Unix(shield.ExpireTime, 0)))
+		currentTime := time.Now().Unix()
+		// 检查用户是否已续费(检查nextduedate字段)
+		nextduedate, err := t.gameShieldRepo.GetGameShieldNextduedate(ctx, int64(shield.Uid), shield.HostId)
+		nextduedateTime, err := strconv.ParseInt(nextduedate, 10, 64)
+		if err != nil {
+			t.logger.Error("解析nextduedate失败", zap.Error(err))
+			continue
+		}
+		renewed := nextduedateTime > currentTime
+
+		if renewed {
+			// 用户已续费,更新shd_game_shield表中的ExpireTime字段
+			// 使用shd_host表中的nextduedate作为新的过期时间
+			shield.ExpireTime = nextduedateTime
+
+			if err := t.gameShieldRepo.UpdateGameShield(ctx, shield); err != nil {
+				t.logger.Error("更新shd_game_shield表失败: %v", zap.Error(err))
+				continue
+			}
+
+			t.logger.Info("自动续期成功: 用户ID=%d, 商品=%s, 新过期时间=%v",
+				zap.Int("用户ID", shield.Uid), zap.String("商品", shield.AppName), zap.Time("新过期时间", time.Unix(shield.ExpireTime, 0)))
+		} else {
+			// 用户未续费,可以发送提醒通知
+			t.logger.Info("用户未续费: 用户ID=%d, 商品=%s", zap.Int("用户ID", shield.Uid), zap.String("商品", shield.AppName))
+		}
+	}
+
+	return nil
+}
+
+// SyncAllExpireTimeFromHost 同步所有记录的过期时间
+func (t gameShieldTask) SyncAllExpireTimeFromHost(ctx context.Context) error {
+	// 获取所有游戏盾记录
+	shields, err := t.gameShieldRepo.GetAllGameShield(ctx)
+	if err != nil {
+		t.logger.Error("查询所有游戏盾记录失败", zap.Error(err))
+		return err
+	}
+
+	t.logger.Info("找到 %d 条游戏盾记录", zap.Int("数量", len(shields)))
+
+	// 同步每条记录的过期时间
+	for _, shield := range shields {
+		if err := t.gameShieldRepo.SyncExpireTimeFromHostNextDueDate(ctx, shield.Uid, shield.HostId); err != nil {
+			t.logger.Error("同步记录 ID=%d 的过期时间失败: %v", zap.Int("ID", shield.Id), zap.Error(err))
+			continue
+		}
+
+		t.logger.Info("同步记录 ID=%d 的过期时间成功", zap.Int("ID", shield.Id))
+	}
+
+	return nil
+}
+
+// 综合处理已过期和即将过期的记录
+func (t gameShieldTask) CheckGameShield(ctx context.Context) error {
+	// 先检查已过期的记录
+	if err := t.CheckExpiredGameShields(ctx); err != nil {
+		return err
+	}
+
+	// 再检查即将过期的记录
+	if err := t.CheckSoonExpiredGameShields(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}