- 新增「简记memo」一体化小程序产品原型设计文档 - 新增简记memo完整版UI视觉设计规范和界面细节 - 添加IDEA项目配置文件.gitignore - 创建404页面HTML文件,包含响应式布局和错误提示 - 添加关于页面HTML文件,展示品牌介绍和团队信息 - 实现AES加解密工具函数,支持请求体加密 - 添加用户协议页面基础框架
412 lines
11 KiB
Go
412 lines
11 KiB
Go
// 心情接口
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"simple-memo/global"
|
|
"simple-memo/models"
|
|
"simple-memo/utils"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// -------------------------- 1. 定义 Handler 接口 --------------------------
|
|
// MoodHandler 心情模块接口定义
|
|
type MoodHandler interface {
|
|
GetMoodByDate(c *gin.Context)
|
|
SaveMood(c *gin.Context)
|
|
GetMoodList(c *gin.Context)
|
|
DeleteMood(c *gin.Context)
|
|
MoodStat(c *gin.Context) // 心情统计
|
|
}
|
|
|
|
// -------------------------- 2. 实现结构体 --------------------------
|
|
// moodHandler 接口实现结构体
|
|
type moodHandler struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewMoodHandler 创建心情处理器
|
|
func NewMoodHandler() MoodHandler {
|
|
return &moodHandler{
|
|
db: global.DB,
|
|
}
|
|
}
|
|
|
|
// -------------------------- 3. 请求结构体 --------------------------
|
|
// GetMoodByDateReq 获取某天心情请求
|
|
type GetMoodByDateReq struct {
|
|
Date string `json:"date" binding:"required"`
|
|
}
|
|
|
|
// SaveMoodReq 保存心情请求
|
|
type SaveMoodReq struct {
|
|
ID uint `json:"id"` // 编辑时传入
|
|
Date string `json:"date" binding:"required"`
|
|
Emoji string `json:"emoji" binding:"required"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
// GetMoodListReq 获取心情列表请求
|
|
type GetMoodListReq struct {
|
|
StartDate string `json:"start_date"`
|
|
EndDate string `json:"end_date"`
|
|
}
|
|
|
|
// DeleteMoodReq 删除心情请求
|
|
type DeleteMoodReq struct {
|
|
ID uint `json:"id" binding:"required"`
|
|
}
|
|
|
|
// MoodStatReq 心情统计请求
|
|
type MoodStatReq struct {
|
|
Month string `json:"month" binding:"required"` // YYYY-MM
|
|
}
|
|
|
|
// -------------------------- 4. 接口实现 --------------------------
|
|
|
|
// MoodCalendarItem 日历心情项(优化后的返回结构)
|
|
type MoodCalendarItem struct {
|
|
ID uint `json:"id"`
|
|
Date string `json:"date"`
|
|
Emoji string `json:"emoji"`
|
|
Content string `json:"content"`
|
|
AIStatus int `json:"ai_status"` // 0无 1生成中 2成功 3失败
|
|
AIText string `json:"ai_text"`
|
|
AIImageURL string `json:"ai_image_url"`
|
|
AIGenerateCount int `json:"ai_generate_count"`
|
|
}
|
|
|
|
// GetMoodByDate 获取某天的心情(包含AI生成内容)
|
|
func (h *moodHandler) GetMoodByDate(c *gin.Context) {
|
|
userID := c.GetUint("userID")
|
|
var req GetMoodByDateReq
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
utils.Fail(c, "参数解析失败")
|
|
return
|
|
}
|
|
|
|
// 使用 GORM Joins 联表查询
|
|
var result MoodCalendarItem
|
|
h.db.Model(&models.Mood{}).
|
|
Select(`
|
|
sm_mood.ID,
|
|
sm_mood.date,
|
|
sm_mood.emoji,
|
|
sm_mood.content,
|
|
COALESCE(sm_mood_ai_generations.status, 0) as ai_status,
|
|
COALESCE(sm_mood_ai_generations.ai_text, '') as ai_text,
|
|
COALESCE(sm_mood_ai_generations.ai_image_url, '') as ai_image_url,
|
|
COALESCE(sm_mood_ai_generations.generate_count, 0) as ai_generate_count
|
|
`).
|
|
Joins("LEFT JOIN sm_mood_ai_generations ON sm_mood.id = sm_mood_ai_generations.mood_id").
|
|
Where("sm_mood.user_id = ? AND sm_mood.date = ?", userID, req.Date).
|
|
Scan(&result)
|
|
|
|
// 检查是否找到记录
|
|
if result.ID == 0 {
|
|
utils.Ok(c, nil)
|
|
return
|
|
}
|
|
|
|
utils.Ok(c, result)
|
|
}
|
|
|
|
// SaveMood 保存心情
|
|
func (h *moodHandler) SaveMood(c *gin.Context) {
|
|
userID := c.GetUint("userID")
|
|
var req SaveMoodReq
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
utils.Fail(c, "参数解析失败")
|
|
return
|
|
}
|
|
|
|
// 敏感词过滤
|
|
filteredContent := utils.FilterSensitive(req.Content)
|
|
|
|
var mood models.Mood
|
|
isNewRecord := false
|
|
|
|
// 如果传入了 id,先尝试按 id 查找
|
|
if req.ID > 0 {
|
|
err := h.db.Where("id = ? AND user_id = ?", req.ID, userID).First(&mood).Error
|
|
if err == nil {
|
|
// 编辑模式
|
|
mood.Emoji = req.Emoji
|
|
mood.Content = filteredContent
|
|
if err := h.db.Save(&mood).Error; err != nil {
|
|
utils.Fail(c, "保存失败")
|
|
return
|
|
}
|
|
} else {
|
|
// id 找不到,按日期查找
|
|
err = h.db.Where("user_id = ? AND date = ?", userID, req.Date).First(&mood).Error
|
|
if err == nil {
|
|
mood.Emoji = req.Emoji
|
|
mood.Content = filteredContent
|
|
if err := h.db.Save(&mood).Error; err != nil {
|
|
utils.Fail(c, "保存失败")
|
|
return
|
|
}
|
|
} else {
|
|
// 新增模式
|
|
mood = models.Mood{
|
|
UserID: userID,
|
|
Date: req.Date,
|
|
Emoji: req.Emoji,
|
|
Content: filteredContent,
|
|
}
|
|
if err := h.db.Create(&mood).Error; err != nil {
|
|
utils.Fail(c, "保存失败")
|
|
return
|
|
}
|
|
isNewRecord = true
|
|
}
|
|
}
|
|
} else {
|
|
// 没有传入 id,按日期查找
|
|
err := h.db.Where("user_id = ? AND date = ?", userID, req.Date).First(&mood).Error
|
|
if err == nil {
|
|
// 更新已有记录
|
|
mood.Emoji = req.Emoji
|
|
mood.Content = filteredContent
|
|
if err := h.db.Save(&mood).Error; err != nil {
|
|
utils.Fail(c, "保存失败")
|
|
return
|
|
}
|
|
isNewRecord = true
|
|
} else {
|
|
// 新增记录
|
|
mood = models.Mood{
|
|
UserID: userID,
|
|
Date: req.Date,
|
|
Emoji: req.Emoji,
|
|
Content: filteredContent,
|
|
}
|
|
if err := h.db.Create(&mood).Error; err != nil {
|
|
utils.Fail(c, "保存失败")
|
|
return
|
|
}
|
|
isNewRecord = true
|
|
}
|
|
}
|
|
|
|
var growthResult any
|
|
debugMode := os.Getenv("DEBUG_MODE")
|
|
if isNewRecord {
|
|
// ==================== 调试模式 ====================
|
|
if debugMode == "true" {
|
|
rand.Seed(time.Now().UnixNano())
|
|
levelUp := true // rand.Intn(10) < 2 // 20%概率升级
|
|
hasAchievement := true // rand.Intn(10) < 3 // 30%概率获得成就
|
|
growthResult = MockGrowthRewards(levelUp, hasAchievement)
|
|
} else {
|
|
// 生产模式:真实调用
|
|
today := time.Now().Format("2006-01-02")
|
|
if req.Date == today {
|
|
ctx := context.Background()
|
|
key := fmt.Sprintf("mood_count:%d:%s", userID, today)
|
|
exists, _ := global.Redis.Exists(ctx, key).Result()
|
|
if exists == 0 {
|
|
growthHandler := NewGrowthHandler()
|
|
growthResult = growthHandler.AddExpInternal(userID, "mood", fmt.Sprintf("%d", mood.ID), nil)
|
|
}
|
|
}
|
|
}
|
|
// ==================================================
|
|
}
|
|
|
|
// 异步触发AI生成(仅新记录触发,编辑不触发)
|
|
if isNewRecord {
|
|
GenerateMoodAIContent(mood.ID, userID, mood.Emoji, mood.Content, mood.Date)
|
|
}
|
|
|
|
// 查询 AI 生成记录并填充
|
|
var aiGen models.MoodAIGeneration
|
|
h.db.Where("mood_id = ?", mood.ID).First(&aiGen)
|
|
mood.AIGen = aiGen
|
|
|
|
utils.Ok(c, gin.H{
|
|
"success": true,
|
|
"growth": growthResult,
|
|
"mood": mood,
|
|
"debugMode": debugMode,
|
|
"isDebug": debugMode == "true",
|
|
"isNewRecord": isNewRecord,
|
|
})
|
|
}
|
|
|
|
// GetMoodList 获取心情列表(包含AI生成内容)
|
|
func (h *moodHandler) GetMoodList(c *gin.Context) {
|
|
userID := c.GetUint("userID")
|
|
var req GetMoodListReq
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
utils.Fail(c, "参数解析失败")
|
|
return
|
|
}
|
|
|
|
// 构建基础查询
|
|
query := h.db.Model(&models.Mood{}).
|
|
Select(`
|
|
sm_mood.ID,
|
|
sm_mood.date,
|
|
sm_mood.emoji,
|
|
sm_mood.content,
|
|
COALESCE(sm_mood_ai_generations.status, 0) as ai_status,
|
|
COALESCE(sm_mood_ai_generations.ai_text, '') as ai_text,
|
|
COALESCE(sm_mood_ai_generations.ai_image_url, '') as ai_image_url,
|
|
COALESCE(sm_mood_ai_generations.generate_count, 0) as ai_generate_count
|
|
`).
|
|
Joins("LEFT JOIN sm_mood_ai_generations ON sm_mood.id = sm_mood_ai_generations.mood_id").
|
|
Where("sm_mood.user_id = ?", userID)
|
|
|
|
// 日期筛选
|
|
if req.StartDate != "" && req.EndDate != "" {
|
|
query = query.Where("sm_mood.date BETWEEN ? AND ?", req.StartDate, req.EndDate)
|
|
} else if req.StartDate != "" {
|
|
query = query.Where("sm_mood.date >= ?", req.StartDate)
|
|
} else if req.EndDate != "" {
|
|
query = query.Where("sm_mood.date <= ?", req.EndDate)
|
|
}
|
|
|
|
var results []MoodCalendarItem
|
|
if err := query.Order("sm_mood.date DESC").Scan(&results).Error; err != nil {
|
|
utils.Fail(c, "获取失败")
|
|
return
|
|
}
|
|
|
|
utils.Ok(c, results)
|
|
}
|
|
|
|
// DeleteMood 删除心情
|
|
func (h *moodHandler) DeleteMood(c *gin.Context) {
|
|
userID := c.GetUint("userID")
|
|
var req DeleteMoodReq
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
utils.Fail(c, "参数解析失败")
|
|
return
|
|
}
|
|
|
|
// 查询心情信息(用于扣除积分)
|
|
var mood models.Mood
|
|
err := h.db.Where("id = ? AND user_id = ?", req.ID, userID).First(&mood).Error
|
|
if err != nil {
|
|
utils.Fail(c, "心情记录不存在")
|
|
return
|
|
}
|
|
|
|
// 删除心情
|
|
err = h.db.Where("id = ? AND user_id = ?", req.ID, userID).Delete(&models.Mood{}).Error
|
|
if err != nil {
|
|
utils.Fail(c, "删除失败")
|
|
return
|
|
}
|
|
|
|
// 扣除今天添加时获得的积分
|
|
growthHandler := NewGrowthHandler()
|
|
today := time.Now().Format("2006-01-02")
|
|
deducted := growthHandler.DeductExpInternal(userID, "mood", fmt.Sprintf("%d", req.ID), today)
|
|
|
|
utils.Ok(c, gin.H{
|
|
"deducted_exp": deducted,
|
|
})
|
|
}
|
|
|
|
// MoodStat 心情统计
|
|
func (h *moodHandler) MoodStat(c *gin.Context) {
|
|
userID := c.GetUint("userID")
|
|
var req MoodStatReq
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
utils.Fail(c, "参数解析失败")
|
|
return
|
|
}
|
|
|
|
// 计算月份范围
|
|
startDate := req.Month + "-01"
|
|
year, month := 0, 0
|
|
fmt.Sscanf(req.Month, "%d-%d", &year, &month)
|
|
_, lastDay := utils.GetMonthLastDay(year, month)
|
|
endDate := fmt.Sprintf("%s-%02d", req.Month, lastDay)
|
|
|
|
// 查询该月的心情记录
|
|
var moods []models.Mood
|
|
h.db.Where("user_id = ? AND date BETWEEN ? AND ?", userID, startDate, endDate).
|
|
Order("date ASC").
|
|
Find(&moods)
|
|
|
|
// 心情分布
|
|
moodDistribution := make(map[string]int)
|
|
for _, m := range moods {
|
|
moodDistribution[m.Emoji]++
|
|
}
|
|
|
|
// 计算总天数和已记录天数
|
|
totalDays := lastDay
|
|
recordedDays := len(moods)
|
|
|
|
// 周趋势(按周分组)
|
|
weeklyTrend := []gin.H{}
|
|
weekMap := make(map[string][]models.Mood)
|
|
|
|
for _, m := range moods {
|
|
// 解析日期,计算是第几周
|
|
t, _ := time.Parse("2006-01-02", m.Date)
|
|
_, week := t.ISOWeek()
|
|
weekKey := fmt.Sprintf("W%d", week)
|
|
weekMap[weekKey] = append(weekMap[weekKey], m)
|
|
}
|
|
|
|
// 计算每周的平均分数
|
|
// 心情emoji映射到分数(简单映射)
|
|
emojiScore := map[string]float64{
|
|
"😊": 5.0, "😄": 5.0, "😎": 4.5,
|
|
"😌": 4.0, "🙂": 3.5,
|
|
"😐": 3.0,
|
|
"😔": 2.5, "😢": 2.0,
|
|
"😤": 2.0, "😰": 1.5,
|
|
"😴": 3.0, "🤔": 3.0,
|
|
}
|
|
|
|
for weekKey, weekMoods := range weekMap {
|
|
var totalScore float64
|
|
for _, m := range weekMoods {
|
|
if score, ok := emojiScore[m.Emoji]; ok {
|
|
totalScore += score
|
|
} else {
|
|
totalScore += 3.0 // 默认中等分数
|
|
}
|
|
}
|
|
avgScore := 0.0
|
|
if len(weekMoods) > 0 {
|
|
avgScore = totalScore / float64(len(weekMoods))
|
|
}
|
|
weeklyTrend = append(weeklyTrend, gin.H{
|
|
"week": weekKey,
|
|
"avg_score": avgScore,
|
|
})
|
|
}
|
|
|
|
// 高频词汇(这里简化处理,返回固定示例)
|
|
topPhrases := []string{"开心", "平静", "工作顺利", "充实", "放松"}
|
|
|
|
utils.Ok(c, gin.H{
|
|
"month": req.Month,
|
|
"total_days": totalDays,
|
|
"recorded_days": recordedDays,
|
|
"mood_distribution": moodDistribution,
|
|
"weekly_trend": weeklyTrend,
|
|
"top_phrases": topPhrases,
|
|
})
|
|
}
|