管理端按用户查询开启通讯录/相册/媒体拉取;服务端写入路径自动压图;新增 /v1/sign 与 /v1/energy。 Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
4.0 KiB
Go
147 lines
4.0 KiB
Go
package logic
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"spark-server/internal/module/energy/entity"
|
||
"spark-server/internal/pkg/errs"
|
||
)
|
||
|
||
// DayStatus is one calendar cell for the sign-in page.
|
||
type DayStatus struct {
|
||
Day int64 `json:"day"`
|
||
SignInStatus string `json:"sign_in_status"`
|
||
RewardPoint int `json:"reward_point"`
|
||
IsDouble bool `json:"is_double"`
|
||
IsSurprise bool `json:"is_surprise"`
|
||
VideoDouble bool `json:"video_double"`
|
||
}
|
||
|
||
// CalendarView matches the legacy SignInBean shape used by the Android page.
|
||
type CalendarView struct {
|
||
ContinueDays int `json:"continue_days"`
|
||
StartDate int64 `json:"start_date"`
|
||
NeedDays int `json:"need_days"`
|
||
RewardPoint int `json:"reward_point"`
|
||
TodaySignStatus string `json:"today_sign_status"`
|
||
TotalRewardPoint int64 `json:"total_reward_point"`
|
||
DrawPrizeStatus int `json:"draw_prize_status"`
|
||
Energy int64 `json:"energy"`
|
||
StatusList []DayStatus `json:"status_list"`
|
||
}
|
||
|
||
// Calendar returns month status for year/month (1–12). year=0 uses current year.
|
||
func (s *Service) Calendar(ctx context.Context, userID int64, year, month int) (*CalendarView, error) {
|
||
if !s.ready() || userID <= 0 {
|
||
return nil, errs.ErrUnauthorized
|
||
}
|
||
now := s.now()
|
||
if year <= 0 {
|
||
year = now.Year()
|
||
}
|
||
if month < 1 || month > 12 {
|
||
month = int(now.Month())
|
||
}
|
||
first := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, s.loc)
|
||
last := first.AddDate(0, 1, -1)
|
||
fromDay, toDay := dayKeyOf(first), dayKeyOf(last)
|
||
today := dayKeyOf(now)
|
||
|
||
records, err := s.repo.ListSignsInRange(ctx, userID, fromDay, toDay)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
byDay := map[int]*entity.SignRecord{}
|
||
for _, r := range records {
|
||
byDay[r.DayKey] = r
|
||
}
|
||
|
||
streak, start := s.computeStreak(ctx, userID, today)
|
||
need := entity.DefaultNeedDays - (streak % entity.DefaultNeedDays)
|
||
if need == entity.DefaultNeedDays && streak > 0 && streak%entity.DefaultNeedDays == 0 {
|
||
need = entity.DefaultNeedDays
|
||
}
|
||
if streak == 0 {
|
||
need = entity.DefaultNeedDays
|
||
}
|
||
nextReward := entity.CycleRewards(streak + 1)
|
||
|
||
wallet, err := s.repo.EnsureWallet(ctx, userID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
list := make([]DayStatus, 0, last.Day())
|
||
todayStatus := entity.StatusUnsigned
|
||
for d := 1; d <= last.Day(); d++ {
|
||
day := year*10000 + month*100 + d
|
||
st := DayStatus{
|
||
Day: int64(day),
|
||
RewardPoint: entity.CycleRewards(1),
|
||
}
|
||
if rec, ok := byDay[day]; ok {
|
||
if rec.Kind == entity.KindMakeup {
|
||
st.SignInStatus = entity.StatusMakeupDone
|
||
} else {
|
||
st.SignInStatus = entity.StatusSigned
|
||
}
|
||
st.RewardPoint = rec.Reward
|
||
} else if day == today {
|
||
st.SignInStatus = entity.StatusUnsigned
|
||
st.RewardPoint = nextReward
|
||
} else if day < today {
|
||
st.SignInStatus = entity.StatusCanMakeup
|
||
st.RewardPoint = entity.CycleRewards(1)
|
||
} else {
|
||
// Future days: not yet signed (client treats as empty slot).
|
||
st.SignInStatus = entity.StatusUnsigned
|
||
st.RewardPoint = entity.CycleRewards(1)
|
||
}
|
||
if day == today {
|
||
todayStatus = st.SignInStatus
|
||
if todayStatus == entity.StatusUnsigned {
|
||
st.RewardPoint = nextReward
|
||
}
|
||
}
|
||
list = append(list, st)
|
||
}
|
||
|
||
return &CalendarView{
|
||
ContinueDays: streak,
|
||
StartDate: int64(start),
|
||
NeedDays: need,
|
||
RewardPoint: nextReward,
|
||
TodaySignStatus: todayStatus,
|
||
TotalRewardPoint: wallet.Energy,
|
||
DrawPrizeStatus: 0,
|
||
Energy: wallet.Energy,
|
||
StatusList: list,
|
||
}, nil
|
||
}
|
||
|
||
func (s *Service) computeStreak(ctx context.Context, userID int64, today int) (streak int, start int) {
|
||
recent, err := s.repo.ListRecentSigns(ctx, userID, 60)
|
||
if err != nil || len(recent) == 0 {
|
||
return 0, 0
|
||
}
|
||
have := map[int]bool{}
|
||
for _, r := range recent {
|
||
have[r.DayKey] = true
|
||
}
|
||
cursor := today
|
||
if !have[today] {
|
||
cursor = addDays(today, -1)
|
||
}
|
||
if !have[cursor] {
|
||
return 0, 0
|
||
}
|
||
start = cursor
|
||
for have[cursor] {
|
||
streak++
|
||
start = cursor
|
||
cursor = addDays(cursor, -1)
|
||
}
|
||
return streak, start
|
||
}
|