docs(website): 添加简记memo产品原型和UI设计规范文档

- 新增「简记memo」一体化小程序产品原型设计文档
- 新增简记memo完整版UI视觉设计规范和界面细节
- 添加IDEA项目配置文件.gitignore
- 创建404页面HTML文件,包含响应式布局和错误提示
- 添加关于页面HTML文件,展示品牌介绍和团队信息
- 实现AES加解密工具函数,支持请求体加密
- 添加用户协议页面基础框架
This commit is contained in:
sunct
2026-07-31 14:12:32 +08:00
commit 3c3bf53ae4
115 changed files with 21304 additions and 0 deletions
+411
View File
@@ -0,0 +1,411 @@
// 心情接口
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,
})
}