package logic import ( "context" "time" "spark-server/internal/module/energy/entity" ) // Repository persists wallets, signs, and energy logs. Model implements it. type Repository interface { GetWallet(ctx context.Context, userID int64) (*entity.Wallet, error) EnsureWallet(ctx context.Context, userID int64) (*entity.Wallet, error) AddEnergy(ctx context.Context, userID, delta int64, reason string, dayKey int, source string) (*entity.Wallet, *entity.EnergyLog, error) ListSignsInRange(ctx context.Context, userID int64, fromDay, toDay int) ([]*entity.SignRecord, error) GetSign(ctx context.Context, userID int64, dayKey int) (*entity.SignRecord, error) CreateSign(ctx context.Context, rec *entity.SignRecord) error ListEnergyLogs(ctx context.Context, userID, cursor int64, limit int) ([]*entity.EnergyLog, error) ListRecentSigns(ctx context.Context, userID int64, limit int) ([]*entity.SignRecord, error) } // Clock abstracts "now" for tests. type Clock interface { Now() time.Time } type realClock struct{} func (realClock) Now() time.Time { return time.Now() } // Service is the sign + energy use-case layer. type Service struct { repo Repository clock Clock loc *time.Location } func NewService(repo Repository) *Service { loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { loc = time.FixedZone("CST", 8*3600) } return &Service{repo: repo, clock: realClock{}, loc: loc} } func (s *Service) withClock(c Clock) *Service { if c != nil { s.clock = c } return s } func (s *Service) ready() bool { return s != nil && s.repo != nil } func (s *Service) now() time.Time { if s.clock == nil { return time.Now().In(s.loc) } return s.clock.Now().In(s.loc) } func dayKeyOf(t time.Time) int { y, m, d := t.Date() return y*10000 + int(m)*100 + d } func parseDayKey(day int) (time.Time, bool) { if day < 19700101 || day > 21001231 { return time.Time{}, false } y, rem := day/10000, day%10000 mo, d := rem/100, rem%100 if mo < 1 || mo > 12 || d < 1 || d > 31 { return time.Time{}, false } return time.Date(y, time.Month(mo), d, 0, 0, 0, 0, time.UTC), true } func addDays(day int, n int) int { t, ok := parseDayKey(day) if !ok { return day } t = t.AddDate(0, 0, n) return dayKeyOf(t) }