AnCao/main.go
yanlongqi 960f557ca4 添加每日一练功能(未完成排行榜前端)
后端功能:
- 添加Exam模型is_system字段标识系统试卷
- 创建每日一练服务,使用PostgreSQL分布式锁
- 集成cron定时任务,每天凌晨1点自动生成试卷
- 自动分享给所有用户(批量插入)
- API权限控制:系统试卷禁止删除和再次分享
- 添加GetDailyExamRanking API返回排行榜

前端功能:
- 添加is_system类型定义
- 系统试卷显示"系统"标签
- 系统试卷隐藏删除和分享按钮
- 添加getDailyExamRanking API方法

技术亮点:
- 使用PostgreSQL Advisory Lock实现分布式锁
- 使用robfig/cron/v3调度定时任务
- 批量插入提升分享性能

待完成:首页添加每日一练排行榜组件

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 00:26:51 +08:00

165 lines
6.6 KiB
Go
Raw 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 main
import (
"ankao/internal/database"
"ankao/internal/handlers"
"ankao/internal/middleware"
"ankao/internal/services"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
)
func main() {
// 初始化数据库连接
if err := database.InitDB(); err != nil {
log.Fatal("数据库初始化失败:", err)
}
log.Println("数据库连接成功")
// 创建Gin路由器
r := gin.Default()
// 应用自定义中间件
r.Use(middleware.CORS())
r.Use(middleware.Logger())
// API路由组必须在静态文件服务之前注册
api := r.Group("/api")
{
// 健康检查
api.GET("/health", handlers.HealthCheckHandler)
// 用户相关API
api.POST("/login", handlers.Login) // 用户登录
api.POST("/register", handlers.Register) // 用户注册
// 需要认证的路由
auth := api.Group("", middleware.Auth())
{
// 用户相关API
auth.PUT("/user/type", handlers.UpdateUserType) // 更新用户类型
auth.PUT("/user/profile", handlers.UpdateProfile) // 更新用户信息
auth.PUT("/user/password", handlers.ChangePassword) // 修改密码
// 排行榜API
auth.GET("/ranking/daily", handlers.GetDailyRanking) // 获取今日排行榜
auth.GET("/ranking/total", handlers.GetTotalRanking) // 获取总排行榜
// 练习题相关API需要登录
auth.GET("/practice/questions", handlers.GetPracticeQuestions) // 获取练习题目列表
auth.GET("/practice/questions/:id", handlers.GetPracticeQuestionByID) // 获取指定练习题目
auth.POST("/practice/explain", handlers.ExplainQuestion) // 生成题目解析AI
// 练习题提交(需要登录才能记录错题)
auth.POST("/practice/submit", handlers.SubmitPracticeAnswer) // 提交练习答案
auth.GET("/practice/statistics", handlers.GetStatistics) // 获取统计数据
// 练习进度相关API
auth.GET("/practice/progress", handlers.GetPracticeProgress) // 获取练习进度
auth.DELETE("/practice/progress", handlers.ClearPracticeProgress) // 清除练习进度
// 错题本相关API
auth.GET("/wrong-questions", handlers.GetWrongQuestions) // 获取错题列表(支持筛选和排序)
auth.GET("/wrong-questions/stats", handlers.GetWrongQuestionStats) // 获取错题统计(含趋势)
auth.GET("/wrong-questions/recommended", handlers.GetRecommendedWrongQuestions) // 获取推荐错题
auth.GET("/wrong-questions/:id", handlers.GetWrongQuestionDetail) // 获取错题详情
auth.DELETE("/wrong-questions/:id", handlers.DeleteWrongQuestion) // 删除错题
auth.DELETE("/wrong-questions", handlers.ClearWrongQuestions) // 清空错题本
// 模拟考试相关API
auth.POST("/exams", handlers.CreateExam) // 创建试卷
auth.GET("/exams", handlers.GetExamList) // 获取试卷列表
auth.GET("/exams/:id", handlers.GetExamDetail) // 获取试卷详情
auth.POST("/exams/:id/start", handlers.StartExam) // 开始考试
auth.POST("/exam-records/:record_id/submit", handlers.SubmitExam) // 提交试卷答案
auth.GET("/exam-records/:record_id", handlers.GetExamRecord) // 获取考试记录详情
auth.GET("/exam-records", handlers.GetExamRecordList) // 获取考试记录列表
auth.DELETE("/exams/:id", handlers.DeleteExam) // 删除试卷
auth.POST("/exam-records/:record_id/progress", handlers.SaveExamProgress) // 保存考试进度
auth.GET("/exam-records/:record_id/answers", handlers.GetExamUserAnswers) // 获取用户答案
auth.GET("/users/shareable", handlers.GetShareableUsers) // 获取可分享的用户列表
auth.POST("/exams/:id/share", handlers.ShareExam) // 分享试卷
auth.GET("/daily-exam/ranking", handlers.GetDailyExamRanking) // 获取每日一练排行榜
}
// 题库管理API需要管理员权限
admin := api.Group("", middleware.Auth(), middleware.AdminAuth())
{
admin.POST("/practice/questions", handlers.CreatePracticeQuestion) // 创建题目
admin.PUT("/practice/questions/:id", handlers.UpdatePracticeQuestion) // 更新题目
admin.DELETE("/practice/questions/:id", handlers.DeletePracticeQuestion) // 删除题目
}
// 用户管理API仅yanlongqi用户可访问
userAdmin := api.Group("", middleware.Auth(), middleware.AdminOnly())
{
userAdmin.GET("/admin/users", handlers.GetAllUsersWithStats) // 获取所有用户及统计
userAdmin.GET("/admin/users/:id", handlers.GetUserDetailStats) // 获取用户详细统计
}
}
// 静态文件服务(使用 NoRoute 避免路由冲突)
// 当没有匹配到任何 API 路由时,尝试提供静态文件
r.NoRoute(gin.WrapH(handlers.StaticFileHandler("./web")))
// 创建自定义HTTP服务器设置超时时间
port := ":8080"
server := &http.Server{
Addr: port,
Handler: r,
ReadTimeout: 5 * time.Minute, // 读取超时5分钟
WriteTimeout: 5 * time.Minute, // 写入超时5分钟
IdleTimeout: 10 * time.Minute, // 空闲连接超时10分钟
MaxHeaderBytes: 1 << 20, // 最大请求头1MB
}
// 启动定时任务
startCronJobs()
log.Printf("服务器启动在端口 %s超时配置读/写 5分钟", port)
// 启动服务器
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic("服务器启动失败: " + err.Error())
}
}
// startCronJobs 启动定时任务
func startCronJobs() {
// 创建定时任务调度器(使用中国时区 UTC+8
c := cron.New(cron.WithLocation(time.FixedZone("CST", 8*3600)))
// 每天凌晨1点执行
_, err := c.AddFunc("0 1 * * *", func() {
log.Println("开始生成每日一练...")
service := services.NewDailyExamService()
if err := service.GenerateDailyExam(); err != nil {
log.Printf("生成每日一练失败: %v", err)
} else {
log.Println("每日一练生成成功")
}
})
if err != nil {
log.Printf("添加定时任务失败: %v", err)
return
}
// 启动调度器
c.Start()
log.Println("定时任务已启动每天凌晨1点生成每日一练")
// 可选:应用启动时立即生成一次(用于测试)
// go func() {
// log.Println("应用启动,立即生成一次每日一练...")
// service := services.NewDailyExamService()
// if err := service.GenerateDailyExam(); err != nil {
// log.Printf("生成每日一练失败: %v", err)
// }
// }()
}