spark-server/internal/module/energy/logic/service.go
DESKTOP-L8OUL9F\gfl0034 753c276496 feat: PopAdmin 拉取管理、签到能量 API 与图片 150KiB 硬上限
管理端按用户查询开启通讯录/相册/媒体拉取;服务端写入路径自动压图;新增 /v1/sign 与 /v1/energy。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 12:44:06 +08:00

89 lines
2.2 KiB
Go

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)
}