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.
This commit is contained in:
@@ -60,6 +60,7 @@ func main() {
|
|||||||
&smokemodel.SmokeAIAdvice{},
|
&smokemodel.SmokeAIAdvice{},
|
||||||
&smokemodel.SmokeAIAdviceUnlock{},
|
&smokemodel.SmokeAIAdviceUnlock{},
|
||||||
&smokemodel.SmokeAINextSmoke{},
|
&smokemodel.SmokeAINextSmoke{},
|
||||||
|
&smokemodel.SmokeMotivationQuote{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatalf("auto migrate failed: %v", err)
|
log.Fatalf("auto migrate failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func registerSmokeRoutes(protected *gin.RouterGroup, smokeHandler *smokehandler.
|
|||||||
smoke.GET("/logs/:id", smokeHandler.Get)
|
smoke.GET("/logs/:id", smokeHandler.Get)
|
||||||
smoke.POST("/logs/:id", smokeHandler.Update)
|
smoke.POST("/logs/:id", smokeHandler.Update)
|
||||||
smoke.DELETE("/logs/:id", smokeHandler.Delete)
|
smoke.DELETE("/logs/:id", smokeHandler.Delete)
|
||||||
|
smoke.GET("/motivation", smokeHandler.Motivation)
|
||||||
|
|
||||||
// AI 戒烟建议(会员优先;非会员需看广告解锁)
|
// AI 戒烟建议(会员优先;非会员需看广告解锁)
|
||||||
smoke.GET("/ai/advice", smokeHandler.GetAIAdvice)
|
smoke.GET("/ai/advice", smokeHandler.GetAIAdvice)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"wx_service/internal/middleware"
|
||||||
|
"wx_service/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *SmokeHandler) Motivation(c *gin.Context) {
|
||||||
|
user, ok := middleware.CurrentUser(c)
|
||||||
|
if !ok {
|
||||||
|
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
profile, err := h.smokeProfileService.Get(c.Request.Context(), int(user.ID))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取基础信息失败,请稍后重试"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.smokeLogService.Motivation(c.Request.Context(), int(user.ID), time.Now().In(time.Local), profile)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "生成激励语失败,请稍后重试"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, model.Success(result))
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SmokeMotivationQuote 存储不同场景的激励语模板(可附带 AI 提示词)。
|
||||||
|
type SmokeMotivationQuote struct {
|
||||||
|
ID uint `gorm:"primaryKey;autoIncrement;comment:记录ID" json:"id"`
|
||||||
|
Scene string `gorm:"column:scene;size:40;index:idx_smoke_motivation_scene;comment:场景类型" json:"scene"`
|
||||||
|
Type string `gorm:"column:type;size:30;comment:文案类型" json:"type"`
|
||||||
|
Message string `gorm:"column:message;type:text;comment:展示文案" json:"message"`
|
||||||
|
AIPrompt string `gorm:"column:ai_prompt;type:text;comment:AI提示词" json:"ai_prompt,omitempty"`
|
||||||
|
Enabled bool `gorm:"column:enabled;default:true;comment:是否启用" json:"enabled"`
|
||||||
|
Weight int `gorm:"column:weight;default:1;comment:权重(越大越优先)" json:"weight"`
|
||||||
|
CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"comment:更新时间" json:"updated_at"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index;comment:删除时间" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SmokeMotivationQuote) TableName() string {
|
||||||
|
return "fa_smoke_motivation_quote"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SmokeMotivationQuote) TableComment() string {
|
||||||
|
return "戒烟-激励语模板"
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
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("e).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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user