240 lines
5.8 KiB
Go
240 lines
5.8 KiB
Go
package handler
|
||
|
||
import (
|
||
"database/sql"
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
"healthflow/internal/config"
|
||
"healthflow/internal/model"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
type ExerciseHandler struct {
|
||
db *sql.DB
|
||
cfg *config.Config
|
||
}
|
||
|
||
func NewExerciseHandler(db *sql.DB, cfg *config.Config) *ExerciseHandler {
|
||
return &ExerciseHandler{db: db, cfg: cfg}
|
||
}
|
||
|
||
// CreateCheckin 创建运动打卡
|
||
func (h *ExerciseHandler) CreateCheckin(c *gin.Context) {
|
||
userID := c.GetInt64("user_id")
|
||
|
||
var req model.CreateExerciseCheckinRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 每次都创建新记录,支持一天多次打卡
|
||
result, err := h.db.Exec(`
|
||
INSERT INTO exercise_checkins (user_id, checkin_date, image_url, note)
|
||
VALUES (?, ?, ?, ?)
|
||
`, userID, req.CheckinDate, req.ImageURL, req.Note)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "打卡失败", "detail": err.Error()})
|
||
return
|
||
}
|
||
|
||
id, _ := result.LastInsertId()
|
||
c.JSON(http.StatusOK, gin.H{"message": "打卡成功", "id": id, "date": req.CheckinDate})
|
||
}
|
||
|
||
// GetCheckins 获取打卡记录列表
|
||
func (h *ExerciseHandler) GetCheckins(c *gin.Context) {
|
||
userID := c.GetInt64("user_id")
|
||
|
||
query := `
|
||
SELECT id, user_id, checkin_date, image_url, note, created_at
|
||
FROM exercise_checkins
|
||
WHERE user_id = ?
|
||
ORDER BY checkin_date DESC
|
||
LIMIT 50
|
||
`
|
||
|
||
rows, err := h.db.Query(query, userID)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败"})
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var checkins []model.ExerciseCheckin
|
||
for rows.Next() {
|
||
var checkin model.ExerciseCheckin
|
||
if err := rows.Scan(&checkin.ID, &checkin.UserID, &checkin.CheckinDate,
|
||
&checkin.ImageURL, &checkin.Note, &checkin.CreatedAt); err != nil {
|
||
continue
|
||
}
|
||
checkins = append(checkins, checkin)
|
||
}
|
||
|
||
if checkins == nil {
|
||
checkins = []model.ExerciseCheckin{}
|
||
}
|
||
c.JSON(http.StatusOK, checkins)
|
||
}
|
||
|
||
// GetHeatmap 获取热力图数据
|
||
func (h *ExerciseHandler) GetHeatmap(c *gin.Context) {
|
||
userID := c.GetInt64("user_id")
|
||
year := c.Query("year")
|
||
if year == "" {
|
||
year = strconv.Itoa(time.Now().Year())
|
||
}
|
||
|
||
// 使用 substr 提取日期的年份部分进行匹配
|
||
// 同时只取日期部分(前10个字符)作为分组键
|
||
query := `
|
||
SELECT substr(checkin_date, 1, 10) as date, COUNT(*) as count
|
||
FROM exercise_checkins
|
||
WHERE user_id = ? AND substr(checkin_date, 1, 4) = ?
|
||
GROUP BY substr(checkin_date, 1, 10)
|
||
ORDER BY date
|
||
`
|
||
|
||
rows, err := h.db.Query(query, userID, year)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败", "detail": err.Error()})
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var data []model.ExerciseHeatmapData
|
||
for rows.Next() {
|
||
var item model.ExerciseHeatmapData
|
||
if err := rows.Scan(&item.Date, &item.Count); err != nil {
|
||
continue
|
||
}
|
||
data = append(data, item)
|
||
}
|
||
|
||
if data == nil {
|
||
data = []model.ExerciseHeatmapData{}
|
||
}
|
||
|
||
c.JSON(http.StatusOK, data)
|
||
}
|
||
|
||
// GetStats 获取运动统计
|
||
func (h *ExerciseHandler) GetStats(c *gin.Context) {
|
||
userID := c.GetInt64("user_id")
|
||
now := time.Now()
|
||
|
||
stats := model.ExerciseStats{}
|
||
|
||
// 总打卡次数
|
||
h.db.QueryRow(`
|
||
SELECT COUNT(*) FROM exercise_checkins WHERE user_id = ?
|
||
`, userID).Scan(&stats.TotalCheckins)
|
||
|
||
// 本月打卡
|
||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||
h.db.QueryRow(`
|
||
SELECT COUNT(*) FROM exercise_checkins
|
||
WHERE user_id = ? AND checkin_date >= ?
|
||
`, userID, monthStart.Format("2006-01-02")).Scan(&stats.ThisMonth)
|
||
|
||
// 本周打卡
|
||
weekday := int(now.Weekday())
|
||
if weekday == 0 {
|
||
weekday = 7
|
||
}
|
||
weekStart := now.AddDate(0, 0, -(weekday - 1))
|
||
h.db.QueryRow(`
|
||
SELECT COUNT(*) FROM exercise_checkins
|
||
WHERE user_id = ? AND checkin_date >= ?
|
||
`, userID, weekStart.Format("2006-01-02")).Scan(&stats.ThisWeek)
|
||
|
||
// 今年运动天数(去重)
|
||
yearStart := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
|
||
h.db.QueryRow(`
|
||
SELECT COUNT(DISTINCT checkin_date) FROM exercise_checkins
|
||
WHERE user_id = ? AND checkin_date >= ?
|
||
`, userID, yearStart.Format("2006-01-02")).Scan(&stats.ThisYearDays)
|
||
|
||
// 计算连续打卡天数
|
||
stats.CurrentStreak = h.calculateStreak(userID)
|
||
|
||
c.JSON(http.StatusOK, stats)
|
||
}
|
||
|
||
// calculateStreak 计算连续打卡天数
|
||
func (h *ExerciseHandler) calculateStreak(userID int64) int {
|
||
rows, err := h.db.Query(`
|
||
SELECT DISTINCT checkin_date FROM exercise_checkins
|
||
WHERE user_id = ?
|
||
ORDER BY checkin_date DESC
|
||
`, userID)
|
||
if err != nil {
|
||
return 0
|
||
}
|
||
defer rows.Close()
|
||
|
||
var dates []string
|
||
for rows.Next() {
|
||
var date string
|
||
if err := rows.Scan(&date); err == nil {
|
||
dates = append(dates, date)
|
||
}
|
||
}
|
||
|
||
if len(dates) == 0 {
|
||
return 0
|
||
}
|
||
|
||
streak := 0
|
||
today := time.Now().Format("2006-01-02")
|
||
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
|
||
|
||
// 检查今天或昨天是否有打卡
|
||
if dates[0] != today && dates[0] != yesterday {
|
||
return 0
|
||
}
|
||
|
||
expectedDate := dates[0]
|
||
for _, date := range dates {
|
||
if date == expectedDate {
|
||
streak++
|
||
t, _ := time.Parse("2006-01-02", expectedDate)
|
||
expectedDate = t.AddDate(0, 0, -1).Format("2006-01-02")
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
|
||
return streak
|
||
}
|
||
|
||
// DeleteCheckin 删除打卡记录
|
||
func (h *ExerciseHandler) DeleteCheckin(c *gin.Context) {
|
||
userID := c.GetInt64("user_id")
|
||
idStr := c.Param("id")
|
||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
|
||
return
|
||
}
|
||
|
||
result, err := h.db.Exec(`
|
||
DELETE FROM exercise_checkins WHERE id = ? AND user_id = ?
|
||
`, id, userID)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
|
||
return
|
||
}
|
||
|
||
affected, _ := result.RowsAffected()
|
||
if affected == 0 {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "记录不存在"})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
|
||
}
|