管理端按用户查询开启通讯录/相册/媒体拉取;服务端写入路径自动压图;新增 /v1/sign 与 /v1/energy。 Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
2.2 KiB
Go
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)
|
|
}
|