AnCao/internal/handlers/handlers.go

208 lines
5.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"ankao/internal/database"
"ankao/internal/models"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
// HomeHandler 首页处理器
func HomeHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "欢迎使用AnKao Web服务",
"version": "1.0.0",
})
}
// HealthCheckHandler 健康检查处理器
func HealthCheckHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
})
}
// GetDailyRanking 获取今日排行榜
func GetDailyRanking(c *gin.Context) {
db := database.GetDB()
// 获取查询参数
limitStr := c.DefaultQuery("limit", "10")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 || limit > 100 {
limit = 10
}
// 查询今日排行榜(按答题数量和正确率排序)
var rankings []models.UserStats
query := `
SELECT
u.id as user_id,
u.username,
u.nickname,
u.avatar,
u.user_type,
COALESCE(COUNT(ar.id), 0) as total_answers,
COALESCE(SUM(CASE WHEN ar.is_correct = true THEN 1 ELSE 0 END), 0) as correct_count,
COALESCE(SUM(CASE WHEN ar.is_correct = false THEN 1 ELSE 0 END), 0) as wrong_count,
CASE
WHEN COUNT(ar.id) > 0 THEN
ROUND(CAST(CAST(SUM(CASE WHEN ar.is_correct = true THEN 1 ELSE 0 END) AS FLOAT) / COUNT(ar.id) * 100 AS NUMERIC), 2)
ELSE 0
END as accuracy,
u.created_at,
MAX(ar.answered_at) as last_answer_at
FROM users u
LEFT JOIN user_answer_records ar ON u.id = ar.user_id
AND ar.deleted_at IS NULL
AND DATE(ar.answered_at) = CURRENT_DATE
WHERE u.deleted_at IS NULL
GROUP BY u.id, u.username, u.nickname, u.avatar, u.user_type, u.created_at
HAVING COUNT(ar.id) > 0
ORDER BY total_answers DESC, accuracy DESC, correct_count DESC
LIMIT ?
`
if err := db.Raw(query, limit).Scan(&rankings).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "获取排行榜数据失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": rankings,
})
}
// GetTotalRanking 获取总排行榜
func GetTotalRanking(c *gin.Context) {
db := database.GetDB()
// 获取查询参数
limitStr := c.DefaultQuery("limit", "10")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 || limit > 100 {
limit = 10
}
// 查询总排行榜(按总答题数量和正确率排序)
var rankings []models.UserStats
query := `
SELECT
u.id as user_id,
u.username,
u.nickname,
u.avatar,
u.user_type,
COALESCE(COUNT(ar.id), 0) as total_answers,
COALESCE(SUM(CASE WHEN ar.is_correct = true THEN 1 ELSE 0 END), 0) as correct_count,
COALESCE(SUM(CASE WHEN ar.is_correct = false THEN 1 ELSE 0 END), 0) as wrong_count,
CASE
WHEN COUNT(ar.id) > 0 THEN
ROUND(CAST(CAST(SUM(CASE WHEN ar.is_correct = true THEN 1 ELSE 0 END) AS FLOAT) / COUNT(ar.id) * 100 AS NUMERIC), 2)
ELSE 0
END as accuracy,
u.created_at,
MAX(ar.answered_at) as last_answer_at
FROM users u
LEFT JOIN user_answer_records ar ON u.id = ar.user_id AND ar.deleted_at IS NULL
WHERE u.deleted_at IS NULL
GROUP BY u.id, u.username, u.nickname, u.avatar, u.user_type, u.created_at
HAVING COUNT(ar.id) > 0
ORDER BY total_answers DESC, accuracy DESC, correct_count DESC
LIMIT ?
`
if err := db.Raw(query, limit).Scan(&rankings).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "获取总排行榜数据失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": rankings,
})
}
// StaticFileHandler 静态文件处理器,用于服务前端静态资源
// 使用 NoRoute 避免与 API 路由冲突
func StaticFileHandler(root string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 获取请求路径
path := r.URL.Path
// 构建完整文件路径
fullPath := filepath.Join(root, path)
// 检查文件是否存在
info, err := os.Stat(fullPath)
if err != nil {
// 文件不存在,尝试返回 index.htmlSPA 应用)
indexPath := filepath.Join(root, "index.html")
if _, err := os.Stat(indexPath); err == nil {
http.ServeFile(w, r, indexPath)
return
}
http.NotFound(w, r)
return
}
// 如果是目录,尝试返回目录下的 index.html
if info.IsDir() {
indexPath := filepath.Join(fullPath, "index.html")
if _, err := os.Stat(indexPath); err == nil {
http.ServeFile(w, r, indexPath)
return
}
http.NotFound(w, r)
return
}
// 设置正确的 Content-Type
setContentType(w, fullPath)
// 返回文件
http.ServeFile(w, r, fullPath)
})
}
// setContentType 根据文件扩展名设置正确的 Content-Type
func setContentType(w http.ResponseWriter, filePath string) {
ext := strings.ToLower(filepath.Ext(filePath))
contentTypes := map[string]string{
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "application/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".eot": "application/vnd.ms-fontobject",
}
if contentType, ok := contentTypes[ext]; ok {
w.Header().Set("Content-Type", contentType)
}
}