package logic import ( "context" "errors" "time" "spark-server/internal/module/energy/entity" "spark-server/internal/pkg/errs" ) // CheckInResult is returned after a successful daily sign-in. type CheckInResult struct { Day int `json:"day"` RewardPoint int `json:"reward_point"` ContinueDays int `json:"continue_days"` NeedDays int `json:"need_days"` Energy int64 `json:"energy"` Kind int16 `json:"kind"` } // CheckIn signs the current local day and credits energy. func (s *Service) CheckIn(ctx context.Context, userID int64) (*CheckInResult, error) { if !s.ready() || userID <= 0 { return nil, errs.ErrUnauthorized } today := dayKeyOf(s.now()) return s.signDay(ctx, userID, today, entity.KindSign, "签到奖励") } func (s *Service) signDay(ctx context.Context, userID int64, day int, kind int16, source string) (*CheckInResult, error) { if _, ok := parseDayKey(day); !ok { return nil, errs.ErrInvalidArgument } if _, err := s.repo.GetSign(ctx, userID, day); err == nil { return nil, errs.ErrDuplicate } else if err != nil && !errors.Is(err, errs.ErrNotFound) { return nil, err } streak, _ := s.computeStreak(ctx, userID, dayKeyOf(s.now())) rewardDay := streak + 1 if kind == entity.KindMakeup { rewardDay = 1 } reward := entity.CycleRewards(rewardDay) reason := "sign" if kind == entity.KindMakeup { reason = "makeup" } rec := &entity.SignRecord{ UserID: userID, DayKey: day, Kind: kind, Reward: reward, CreatedMS: time.Now().UnixMilli(), } if err := s.repo.CreateSign(ctx, rec); err != nil { return nil, err } wallet, _, err := s.repo.AddEnergy(ctx, userID, int64(reward), reason, day, source) if err != nil { return nil, err } newStreak, _ := s.computeStreak(ctx, userID, dayKeyOf(s.now())) need := entity.DefaultNeedDays - (newStreak % entity.DefaultNeedDays) if need <= 0 { need = entity.DefaultNeedDays } return &CheckInResult{ Day: day, RewardPoint: reward, ContinueDays: newStreak, NeedDays: need, Energy: wallet.Energy, Kind: kind, }, nil }