feat: 新增路由配置、交易处理和用户连击功能,并初始化相关服务与处理器
This commit is contained in:
243
internal/service/streak_service.go
Normal file
243
internal/service/streak_service.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"accounting-app/internal/models"
|
||||
"accounting-app/internal/repository"
|
||||
)
|
||||
|
||||
// StreakService handles business logic for user streaks
|
||||
type StreakService struct {
|
||||
repo *repository.StreakRepository
|
||||
}
|
||||
|
||||
// NewStreakService creates a new StreakService instance
|
||||
func NewStreakService(repo *repository.StreakRepository) *StreakService {
|
||||
return &StreakService{repo: repo}
|
||||
}
|
||||
|
||||
// GetStreakInfo returns the streak information for a user
|
||||
func (s *StreakService) GetStreakInfo(userID uint) (*models.StreakInfo, error) {
|
||||
streak, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
|
||||
// Check if user has recorded today
|
||||
hasRecordToday, err := s.repo.HasTransactionOnDate(userID, today)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Determine message based on streak status
|
||||
var message string
|
||||
if hasRecordToday {
|
||||
if streak.CurrentStreak >= 7 {
|
||||
message = "太厉害了!连续记账一周以上 🔥"
|
||||
} else if streak.CurrentStreak >= 3 {
|
||||
message = "继续保持,养成好习惯!💪"
|
||||
} else {
|
||||
message = "今日已记账,明天继续哦 ✅"
|
||||
}
|
||||
} else {
|
||||
if streak.CurrentStreak > 0 {
|
||||
message = "今天还没有记账哦,别断了连续记录!"
|
||||
} else {
|
||||
message = "今天还没有记账哦,开始记录吧!"
|
||||
}
|
||||
}
|
||||
|
||||
return &models.StreakInfo{
|
||||
CurrentStreak: streak.CurrentStreak,
|
||||
LongestStreak: streak.LongestStreak,
|
||||
TotalRecordDays: streak.TotalRecordDays,
|
||||
HasRecordToday: hasRecordToday,
|
||||
Message: message,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateStreak updates the streak when a transaction is created
|
||||
// This should be called after a transaction is created
|
||||
func (s *StreakService) UpdateStreak(userID uint, transactionDate time.Time) error {
|
||||
streak, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Normalize transaction date to start of day
|
||||
txDate := time.Date(transactionDate.Year(), transactionDate.Month(), transactionDate.Day(), 0, 0, 0, 0, transactionDate.Location())
|
||||
now := time.Now()
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
|
||||
// Only update streak for today's date or past dates (not future)
|
||||
if txDate.After(today) {
|
||||
return nil // Don't update streak for future transactions
|
||||
}
|
||||
|
||||
// If this is the first record ever
|
||||
if streak.LastRecordDate == nil {
|
||||
streak.CurrentStreak = 1
|
||||
streak.LongestStreak = 1
|
||||
streak.TotalRecordDays = 1
|
||||
streak.LastRecordDate = &txDate
|
||||
return s.repo.Update(streak)
|
||||
}
|
||||
|
||||
lastDate := *streak.LastRecordDate
|
||||
lastDateNormalized := time.Date(lastDate.Year(), lastDate.Month(), lastDate.Day(), 0, 0, 0, 0, lastDate.Location())
|
||||
|
||||
// If already recorded on this date, no update needed
|
||||
if txDate.Equal(lastDateNormalized) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If recording for a new date
|
||||
if txDate.After(lastDateNormalized) {
|
||||
daysDiff := int(txDate.Sub(lastDateNormalized).Hours() / 24)
|
||||
|
||||
if daysDiff == 1 {
|
||||
// Consecutive day - increment streak
|
||||
streak.CurrentStreak++
|
||||
} else {
|
||||
// Streak broken - reset to 1
|
||||
streak.CurrentStreak = 1
|
||||
}
|
||||
|
||||
// Update longest streak if current is higher
|
||||
if streak.CurrentStreak > streak.LongestStreak {
|
||||
streak.LongestStreak = streak.CurrentStreak
|
||||
}
|
||||
|
||||
// Increment total record days
|
||||
streak.TotalRecordDays++
|
||||
streak.LastRecordDate = &txDate
|
||||
} else {
|
||||
// Recording for a past date - just increment total if it's a new day
|
||||
hasRecord, err := s.repo.HasTransactionOnDate(userID, txDate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// This is a bit tricky - we'd need to check if this specific date already has records
|
||||
// For simplicity, if it's a past date and we're adding a new transaction,
|
||||
// we'll just increment total (this might not be 100% accurate for edits)
|
||||
if !hasRecord {
|
||||
streak.TotalRecordDays++
|
||||
}
|
||||
}
|
||||
|
||||
return s.repo.Update(streak)
|
||||
}
|
||||
|
||||
// RecalculateStreak recalculates the entire streak from transaction history
|
||||
// This is useful for fixing streak data or after bulk imports
|
||||
func (s *StreakService) RecalculateStreak(userID uint) error {
|
||||
streak, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all transaction dates for the user (last 365 days should be enough)
|
||||
now := time.Now()
|
||||
startDate := now.AddDate(-1, 0, 0) // 1 year ago
|
||||
|
||||
dates, err := s.repo.GetTransactionDatesInRange(userID, startDate, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dates) == 0 {
|
||||
// No transactions, reset streak
|
||||
streak.CurrentStreak = 0
|
||||
streak.LongestStreak = 0
|
||||
streak.TotalRecordDays = 0
|
||||
streak.LastRecordDate = nil
|
||||
return s.repo.Update(streak)
|
||||
}
|
||||
|
||||
// Calculate streaks
|
||||
streak.TotalRecordDays = len(dates)
|
||||
|
||||
// Find longest streak and current streak
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
|
||||
currentStreak := 1
|
||||
longestStreak := 1
|
||||
tempStreak := 1
|
||||
|
||||
for i := 1; i < len(dates); i++ {
|
||||
prevDate := dates[i-1]
|
||||
currDate := dates[i]
|
||||
daysDiff := int(currDate.Sub(prevDate).Hours() / 24)
|
||||
|
||||
if daysDiff == 1 {
|
||||
tempStreak++
|
||||
if tempStreak > longestStreak {
|
||||
longestStreak = tempStreak
|
||||
}
|
||||
} else {
|
||||
tempStreak = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Determine current streak (must include today or yesterday)
|
||||
lastRecordDate := dates[len(dates)-1]
|
||||
lastRecordNormalized := time.Date(lastRecordDate.Year(), lastRecordDate.Month(), lastRecordDate.Day(), 0, 0, 0, 0, lastRecordDate.Location())
|
||||
|
||||
daysSinceLastRecord := int(today.Sub(lastRecordNormalized).Hours() / 24)
|
||||
|
||||
if daysSinceLastRecord <= 1 {
|
||||
// Calculate current streak going backwards from last record
|
||||
currentStreak = 1
|
||||
for i := len(dates) - 2; i >= 0; i-- {
|
||||
currDate := dates[i+1]
|
||||
prevDate := dates[i]
|
||||
daysDiff := int(currDate.Sub(prevDate).Hours() / 24)
|
||||
|
||||
if daysDiff == 1 {
|
||||
currentStreak++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Streak is broken
|
||||
currentStreak = 0
|
||||
}
|
||||
|
||||
streak.CurrentStreak = currentStreak
|
||||
streak.LongestStreak = longestStreak
|
||||
streak.LastRecordDate = &lastRecordNormalized
|
||||
|
||||
return s.repo.Update(streak)
|
||||
}
|
||||
|
||||
// CheckAndResetStreak checks if streak should be reset (called daily or on app open)
|
||||
func (s *StreakService) CheckAndResetStreak(userID uint) error {
|
||||
streak, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if streak.LastRecordDate == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
lastDate := *streak.LastRecordDate
|
||||
lastDateNormalized := time.Date(lastDate.Year(), lastDate.Month(), lastDate.Day(), 0, 0, 0, 0, lastDate.Location())
|
||||
|
||||
daysDiff := int(today.Sub(lastDateNormalized).Hours() / 24)
|
||||
|
||||
// If more than 1 day has passed without recording, reset streak
|
||||
if daysDiff > 1 {
|
||||
streak.CurrentStreak = 0
|
||||
return s.repo.Update(streak)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user