Files
wx_service/internal/smoke/service/smoke_motivation_service.go
nepiedg 3154365ab2 Add motivation quote feature to smoking tracking API
- Introduced a new database model `SmokeMotivationQuote` for storing motivational quotes.
- Added a new API endpoint `GET /api/v1/smoke/motivation` to retrieve motivation quotes for users.
- Updated the main.go file to include the new model in the auto-migration process.
- Enhanced smoke_routes.go to register the new motivation route with the smoke handler.
2026-01-25 09:53:18 +00:00

116 lines
3.1 KiB
Go

package service
import (
"context"
"errors"
"time"
"gorm.io/gorm"
smokemodel "wx_service/internal/smoke/model"
)
type SmokeMotivation struct {
Message string `json:"message"`
Type string `json:"type"`
}
func (s *SmokeLogService) Motivation(ctx context.Context, uid int, asOf time.Time, profile *smokemodel.SmokeUserProfile) (SmokeMotivation, error) {
home, err := s.HomeSummary(ctx, uid, asOf)
if err != nil {
return SmokeMotivation{}, err
}
minutesSinceLast := -1
if home.LastSmokeAt != nil {
diff := int(asOf.Sub(*home.LastSmokeAt).Minutes())
if diff < 0 {
diff = 0
}
minutesSinceLast = diff
}
dailyTarget := 0
if profile != nil && profile.BaselineCigsPerDay > 0 {
dailyTarget = profile.BaselineCigsPerDay
}
quitMotivation := ""
if profile != nil && len(profile.QuitMotivations) > 0 {
quitMotivation = profile.QuitMotivations[0]
}
scene, fallback := buildMotivationScene(home, minutesSinceLast, dailyTarget, quitMotivation)
if quote, ok, err := s.loadMotivationQuote(ctx, scene); err != nil {
return fallback, err
} else if ok {
return SmokeMotivation{Message: quote.Message, Type: quote.Type}, nil
}
if scene != "default" {
if quote, ok, err := s.loadMotivationQuote(ctx, "default"); err != nil {
return fallback, err
} else if ok {
return SmokeMotivation{Message: quote.Message, Type: quote.Type}, nil
}
}
return fallback, nil
}
func buildMotivationScene(home SmokeHomeSummary, minutesSinceLast int, dailyTarget int, quitMotivation string) (string, SmokeMotivation) {
if home.ResistedCount > 0 && minutesSinceLast >= 0 && minutesSinceLast < 30 {
return "recent_resist", SmokeMotivation{
Message: "太棒了!你刚刚成功抵抗了一次烟瘾",
Type: "praise",
}
}
if dailyTarget > 0 && home.TodayCount < int(float64(dailyTarget)*0.5) {
return "below_half_target", SmokeMotivation{
Message: "今天的表现非常出色,继续保持!",
Type: "encourage",
}
}
if dailyTarget > 0 && home.TodayCount == dailyTarget-1 {
return "near_limit", SmokeMotivation{
Message: "还剩最后一支配额,考虑把它留到睡前?",
Type: "hint",
}
}
if dailyTarget > 0 && home.TodayCount > dailyTarget {
msg := "没关系,明天是新的一天。"
if quitMotivation != "" {
msg = msg + "记住你为什么要戒烟:" + quitMotivation
}
return "over_target", SmokeMotivation{
Message: msg,
Type: "comfort",
}
}
return "default", SmokeMotivation{
Message: "保持连胜纪录!",
Type: "encourage",
}
}
func (s *SmokeLogService) loadMotivationQuote(ctx context.Context, scene string) (smokemodel.SmokeMotivationQuote, bool, error) {
var quote smokemodel.SmokeMotivationQuote
if err := s.db.WithContext(ctx).
Where("scene = ? AND enabled = 1", scene).
Order("weight DESC, id ASC").
First(&quote).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return smokemodel.SmokeMotivationQuote{}, false, nil
}
return smokemodel.SmokeMotivationQuote{}, false, err
}
if quote.Message == "" || quote.Type == "" {
return smokemodel.SmokeMotivationQuote{}, false, nil
}
return quote, true, nil
}