init
This commit is contained in:
105
serve/internal/handler/ai_handler.go
Normal file
105
serve/internal/handler/ai_handler.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AIHandler AI相关处理器
|
||||
type AIHandler struct {
|
||||
aiService service.AIService
|
||||
}
|
||||
|
||||
// NewAIHandler 创建AI处理器
|
||||
func NewAIHandler() *AIHandler {
|
||||
return &AIHandler{
|
||||
aiService: service.NewAIService(),
|
||||
}
|
||||
}
|
||||
|
||||
// WritingCorrectionRequest 写作批改请求
|
||||
type WritingCorrectionRequest struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
TaskType string `json:"task_type" binding:"required"`
|
||||
}
|
||||
|
||||
// CorrectWriting 写作批改
|
||||
func (h *AIHandler) CorrectWriting(c *gin.Context) {
|
||||
var req WritingCorrectionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Invalid request parameters",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 调用AI服务进行写作批改
|
||||
result, err := h.aiService.CorrectWriting(c.Request.Context(), req.Content, req.TaskType)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "AI service error",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Writing correction completed",
|
||||
"data": result,
|
||||
})
|
||||
}
|
||||
|
||||
// SpeakingEvaluationRequest 口语评估请求
|
||||
type SpeakingEvaluationRequest struct {
|
||||
AudioText string `json:"audio_text" binding:"required"`
|
||||
Prompt string `json:"prompt" binding:"required"`
|
||||
}
|
||||
|
||||
// EvaluateSpeaking 口语评估
|
||||
func (h *AIHandler) EvaluateSpeaking(c *gin.Context) {
|
||||
var req SpeakingEvaluationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Invalid request parameters",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 调用AI服务进行口语评估
|
||||
result, err := h.aiService.EvaluateSpeaking(c.Request.Context(), req.AudioText, req.Prompt)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "AI service error",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Speaking evaluation completed",
|
||||
"data": result,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAIUsageStats 获取AI使用统计
|
||||
func (h *AIHandler) GetAIUsageStats(c *gin.Context) {
|
||||
// TODO: 实现实际的统计查询逻辑
|
||||
// 目前返回模拟数据
|
||||
stats := gin.H{
|
||||
"writing_corrections": 15,
|
||||
"speaking_evaluations": 8,
|
||||
"recommendations": 5,
|
||||
"exercises_generated": 12,
|
||||
"total_requests": 40,
|
||||
"this_month_requests": 15,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "AI usage statistics",
|
||||
"data": stats,
|
||||
})
|
||||
}
|
||||
145
serve/internal/handler/learning_session_handler.go
Normal file
145
serve/internal/handler/learning_session_handler.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/common"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type LearningSessionHandler struct {
|
||||
sessionService *services.LearningSessionService
|
||||
}
|
||||
|
||||
func NewLearningSessionHandler(sessionService *services.LearningSessionService) *LearningSessionHandler {
|
||||
return &LearningSessionHandler{sessionService: sessionService}
|
||||
}
|
||||
|
||||
// StartLearning 开始学习
|
||||
// POST /api/v1/vocabulary/books/:id/learn
|
||||
func (h *LearningSessionHandler) StartLearning(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
bookID := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
DailyGoal int `json:"dailyGoal" binding:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ErrorResponse(c, 400, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 创建学习会话
|
||||
session, err := h.sessionService.StartLearningSession(userID, bookID, req.DailyGoal)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "创建学习会话失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取今日学习任务(新词数 = 用户选择,复习词 = 所有到期)
|
||||
tasks, err := h.sessionService.GetTodayLearningTasks(userID, bookID, req.DailyGoal)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取学习任务失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"session": session,
|
||||
"tasks": tasks,
|
||||
})
|
||||
}
|
||||
|
||||
// GetTodayTasks 获取今日学习任务
|
||||
// GET /api/v1/vocabulary/books/:id/tasks
|
||||
func (h *LearningSessionHandler) GetTodayTasks(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
bookID := c.Param("id")
|
||||
|
||||
newWordsLimit, _ := strconv.Atoi(c.DefaultQuery("newWords", "20"))
|
||||
|
||||
// 获取学习任务(复习词不限数量)
|
||||
tasks, err := h.sessionService.GetTodayLearningTasks(userID, bookID, newWordsLimit)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取学习任务失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, tasks)
|
||||
}
|
||||
|
||||
// SubmitWordStudy 提交单词学习结果
|
||||
// POST /api/v1/vocabulary/words/:id/study
|
||||
func (h *LearningSessionHandler) SubmitWordStudy(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
wordIDStr := c.Param("id")
|
||||
|
||||
wordID, err := strconv.ParseInt(wordIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的单词ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Difficulty string `json:"difficulty" binding:"required,oneof=forgot hard good easy perfect"`
|
||||
SessionID int64 `json:"sessionId"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ErrorResponse(c, 400, "参数错误:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 记录学习结果
|
||||
progress, err := h.sessionService.RecordWordStudy(userID, wordID, req.Difficulty)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "记录学习结果失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, progress)
|
||||
}
|
||||
|
||||
// GetLearningStatistics 获取学习统计
|
||||
// GET /api/v1/vocabulary/books/:id/statistics
|
||||
func (h *LearningSessionHandler) GetLearningStatistics(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
bookID := c.Param("id")
|
||||
|
||||
stats, err := h.sessionService.GetLearningStatistics(userID, bookID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取学习统计失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, stats)
|
||||
}
|
||||
|
||||
// GetTodayOverallStatistics 获取今日总体学习统计
|
||||
// GET /api/v1/learning/today/statistics
|
||||
func (h *LearningSessionHandler) GetTodayOverallStatistics(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
stats, err := h.sessionService.GetTodayOverallStatistics(userID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取今日学习统计失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, stats)
|
||||
}
|
||||
|
||||
// GetTodayReviewWords 获取今日需要复习的单词
|
||||
// GET /api/v1/learning/today/review-words
|
||||
func (h *LearningSessionHandler) GetTodayReviewWords(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
words, err := h.sessionService.GetTodayReviewWords(userID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取今日复习单词失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, words)
|
||||
}
|
||||
232
serve/internal/handler/listening_handler.go
Normal file
232
serve/internal/handler/listening_handler.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
|
||||
)
|
||||
|
||||
type ListeningHandler struct {
|
||||
listeningService *services.ListeningService
|
||||
}
|
||||
|
||||
func NewListeningHandler(listeningService *services.ListeningService) *ListeningHandler {
|
||||
return &ListeningHandler{listeningService: listeningService}
|
||||
}
|
||||
|
||||
func getUserIDString(c *gin.Context) string {
|
||||
uid, exists := c.Get("user_id")
|
||||
if !exists || uid == nil {
|
||||
return ""
|
||||
}
|
||||
switch v := uid.(type) {
|
||||
case string:
|
||||
return v
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// GetListeningMaterials 获取听力材料列表
|
||||
func (h *ListeningHandler) GetListeningMaterials(c *gin.Context) {
|
||||
level := c.Query("level")
|
||||
category := c.Query("category")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
materials, total, err := h.listeningService.GetListeningMaterials(level, category, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"materials": materials, "total": total})
|
||||
}
|
||||
|
||||
// GetListeningMaterial 获取听力材料详情
|
||||
func (h *ListeningHandler) GetListeningMaterial(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
material, err := h.listeningService.GetListeningMaterial(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Listening material not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"material": material})
|
||||
}
|
||||
|
||||
// CreateListeningMaterial 创建听力材料
|
||||
func (h *ListeningHandler) CreateListeningMaterial(c *gin.Context) {
|
||||
var material models.ListeningMaterial
|
||||
if err := c.ShouldBindJSON(&material); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.listeningService.CreateListeningMaterial(&material); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"material": material})
|
||||
}
|
||||
|
||||
// UpdateListeningMaterial 更新听力材料
|
||||
func (h *ListeningHandler) UpdateListeningMaterial(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var updates map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.listeningService.UpdateListeningMaterial(id, updates); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回最新数据
|
||||
material, err := h.listeningService.GetListeningMaterial(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"material": material})
|
||||
}
|
||||
|
||||
// DeleteListeningMaterial 删除听力材料
|
||||
func (h *ListeningHandler) DeleteListeningMaterial(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := h.listeningService.DeleteListeningMaterial(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Listening material deleted successfully"})
|
||||
}
|
||||
|
||||
// SearchListeningMaterials 搜索听力材料
|
||||
func (h *ListeningHandler) SearchListeningMaterials(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
level := c.Query("level")
|
||||
category := c.Query("category")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
materials, total, err := h.listeningService.SearchListeningMaterials(keyword, level, category, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"materials": materials, "total": total})
|
||||
}
|
||||
|
||||
// 已移除未实现的 GetRecommendedMaterials,避免编译错误
|
||||
|
||||
// CreateListeningRecord 创建听力记录
|
||||
func (h *ListeningHandler) CreateListeningRecord(c *gin.Context) {
|
||||
var record models.ListeningRecord
|
||||
if err := c.ShouldBindJSON(&record); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
record.UserID = getUserIDString(c)
|
||||
if err := h.listeningService.CreateListeningRecord(&record); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// UpdateListeningRecord 更新听力记录
|
||||
func (h *ListeningHandler) UpdateListeningRecord(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var updates map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.listeningService.UpdateListeningRecord(id, updates); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
record, err := h.listeningService.GetListeningRecord(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// GetListeningRecord 获取听力记录详情
|
||||
func (h *ListeningHandler) GetListeningRecord(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
record, err := h.listeningService.GetListeningRecord(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Listening record not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// GetUserListeningRecords 获取用户听力记录列表
|
||||
func (h *ListeningHandler) GetUserListeningRecords(c *gin.Context) {
|
||||
userID := getUserIDString(c)
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
records, total, err := h.listeningService.GetUserListeningRecords(userID, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": records, "total": total})
|
||||
}
|
||||
|
||||
// 移除未实现的 SubmitListening 与 GradeListening,避免编译错误
|
||||
|
||||
// GetUserListeningStats 获取用户听力统计
|
||||
func (h *ListeningHandler) GetUserListeningStats(c *gin.Context) {
|
||||
userID := getUserIDString(c)
|
||||
|
||||
stats, err := h.listeningService.GetUserListeningStats(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
||||
}
|
||||
|
||||
// GetListeningProgress 获取听力进度
|
||||
func (h *ListeningHandler) GetListeningProgress(c *gin.Context) {
|
||||
userID := getUserIDString(c)
|
||||
materialID := c.Param("material_id")
|
||||
|
||||
progress, err := h.listeningService.GetListeningProgress(userID, materialID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Listening progress not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"progress": progress})
|
||||
}
|
||||
144
serve/internal/handler/notification_handler.go
Normal file
144
serve/internal/handler/notification_handler.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/common"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
)
|
||||
|
||||
// NotificationHandler 通知处理器
|
||||
type NotificationHandler struct {
|
||||
notificationService *services.NotificationService
|
||||
}
|
||||
|
||||
// NewNotificationHandler 创建通知处理器
|
||||
func NewNotificationHandler(notificationService *services.NotificationService) *NotificationHandler {
|
||||
return &NotificationHandler{
|
||||
notificationService: notificationService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetNotifications 获取通知列表
|
||||
// @Summary 获取用户通知列表
|
||||
// @Tags notification
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param limit query int false "每页数量" default(10)
|
||||
// @Param only_unread query bool false "只显示未读" default(false)
|
||||
// @Success 200 {object} common.Response
|
||||
// @Router /api/v1/notifications [get]
|
||||
func (h *NotificationHandler) GetNotifications(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
// 解析分页参数
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
onlyUnread, _ := strconv.ParseBool(c.DefaultQuery("only_unread", "false"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if limit < 1 || limit > 100 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
notifications, total, err := h.notificationService.GetUserNotifications(userID, page, limit, onlyUnread)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] 获取通知列表失败: userID=%d, error=%v", userID, err)
|
||||
common.ErrorResponse(c, http.StatusInternalServerError, "获取通知列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"notifications": notifications,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
})
|
||||
}
|
||||
|
||||
// GetUnreadCount 获取未读通知数量
|
||||
// @Summary 获取未读通知数量
|
||||
// @Tags notification
|
||||
// @Success 200 {object} common.Response
|
||||
// @Router /api/v1/notifications/unread-count [get]
|
||||
func (h *NotificationHandler) GetUnreadCount(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
count, err := h.notificationService.GetUnreadCount(userID)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] 获取未读通知数量失败: userID=%d, error=%v", userID, err)
|
||||
common.ErrorResponse(c, http.StatusInternalServerError, "获取未读通知数量失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"count": count,
|
||||
})
|
||||
}
|
||||
|
||||
// MarkAsRead 标记通知为已读
|
||||
// @Summary 标记通知为已读
|
||||
// @Tags notification
|
||||
// @Param id path int true "通知ID"
|
||||
// @Success 200 {object} common.Response
|
||||
// @Router /api/v1/notifications/:id/read [put]
|
||||
func (h *NotificationHandler) MarkAsRead(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
notificationID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, http.StatusBadRequest, "无效的通知ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.notificationService.MarkAsRead(userID, notificationID); err != nil {
|
||||
log.Printf("[ERROR] 标记通知已读失败: userID=%d, notificationID=%d, error=%v", userID, notificationID, err)
|
||||
common.ErrorResponse(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, nil)
|
||||
}
|
||||
|
||||
// MarkAllAsRead 标记所有通知为已读
|
||||
// @Summary 标记所有通知为已读
|
||||
// @Tags notification
|
||||
// @Success 200 {object} common.Response
|
||||
// @Router /api/v1/notifications/read-all [put]
|
||||
func (h *NotificationHandler) MarkAllAsRead(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
if err := h.notificationService.MarkAllAsRead(userID); err != nil {
|
||||
log.Printf("[ERROR] 标记所有通知已读失败: userID=%d, error=%v", userID, err)
|
||||
common.ErrorResponse(c, http.StatusInternalServerError, "标记所有通知已读失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, nil)
|
||||
}
|
||||
|
||||
// DeleteNotification 删除通知
|
||||
// @Summary 删除通知
|
||||
// @Tags notification
|
||||
// @Param id path int true "通知ID"
|
||||
// @Success 200 {object} common.Response
|
||||
// @Router /api/v1/notifications/:id [delete]
|
||||
func (h *NotificationHandler) DeleteNotification(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
notificationID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, http.StatusBadRequest, "无效的通知ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.notificationService.DeleteNotification(userID, notificationID); err != nil {
|
||||
log.Printf("[ERROR] 删除通知失败: userID=%d, notificationID=%d, error=%v", userID, notificationID, err)
|
||||
common.ErrorResponse(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, nil)
|
||||
}
|
||||
226
serve/internal/handler/reading_handler.go
Normal file
226
serve/internal/handler/reading_handler.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
|
||||
)
|
||||
|
||||
type ReadingHandler struct {
|
||||
readingService *services.ReadingService
|
||||
}
|
||||
|
||||
func NewReadingHandler(readingService *services.ReadingService) *ReadingHandler {
|
||||
return &ReadingHandler{readingService: readingService}
|
||||
}
|
||||
|
||||
// GetReadingMaterials 获取阅读材料列表
|
||||
func (h *ReadingHandler) GetReadingMaterials(c *gin.Context) {
|
||||
level := c.Query("level")
|
||||
category := c.Query("category")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
materials, total, err := h.readingService.GetReadingMaterials(level, category, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"materials": materials, "total": total})
|
||||
}
|
||||
|
||||
// GetReadingMaterial 获取阅读材料详情
|
||||
func (h *ReadingHandler) GetReadingMaterial(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
material, err := h.readingService.GetReadingMaterial(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Reading material not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"material": material})
|
||||
}
|
||||
|
||||
// CreateReadingMaterial 创建阅读材料
|
||||
func (h *ReadingHandler) CreateReadingMaterial(c *gin.Context) {
|
||||
var material models.ReadingMaterial
|
||||
if err := c.ShouldBindJSON(&material); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.readingService.CreateReadingMaterial(&material); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"material": material})
|
||||
}
|
||||
|
||||
// UpdateReadingMaterial 更新阅读材料
|
||||
func (h *ReadingHandler) UpdateReadingMaterial(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var updates map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.readingService.UpdateReadingMaterial(id, updates); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
material, err := h.readingService.GetReadingMaterial(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"material": material})
|
||||
}
|
||||
|
||||
// DeleteReadingMaterial 删除阅读材料
|
||||
func (h *ReadingHandler) DeleteReadingMaterial(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := h.readingService.DeleteReadingMaterial(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Reading material deleted successfully"})
|
||||
}
|
||||
|
||||
// SearchReadingMaterials 搜索阅读材料
|
||||
func (h *ReadingHandler) SearchReadingMaterials(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
level := c.Query("level")
|
||||
category := c.Query("category")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
materials, total, err := h.readingService.SearchReadingMaterials(keyword, level, category, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"materials": materials, "total": total})
|
||||
}
|
||||
|
||||
// GetRecommendedMaterials 获取推荐阅读材料
|
||||
func (h *ReadingHandler) GetRecommendedMaterials(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "5"))
|
||||
|
||||
materials, err := h.readingService.GetRecommendedMaterials(userID, limit)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"materials": materials})
|
||||
}
|
||||
|
||||
// CreateReadingRecord 创建阅读记录
|
||||
func (h *ReadingHandler) CreateReadingRecord(c *gin.Context) {
|
||||
var record models.ReadingRecord
|
||||
if err := c.ShouldBindJSON(&record); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
record.UserID = c.GetString("user_id")
|
||||
if err := h.readingService.CreateReadingRecord(&record); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// UpdateReadingRecord 更新阅读记录
|
||||
func (h *ReadingHandler) UpdateReadingRecord(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var updates map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.readingService.UpdateReadingRecord(id, updates); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
record, err := h.readingService.GetReadingRecord(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// GetReadingRecord 获取阅读记录详情
|
||||
func (h *ReadingHandler) GetReadingRecord(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
record, err := h.readingService.GetReadingRecord(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Reading record not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// GetUserReadingRecords 获取用户阅读记录列表
|
||||
func (h *ReadingHandler) GetUserReadingRecords(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
records, total, err := h.readingService.GetUserReadingRecords(userID, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"records": records, "total": total})
|
||||
}
|
||||
|
||||
// 已移除未实现的 SubmitReading 与 GradeReading,避免编译错误
|
||||
|
||||
// GetUserReadingStats 获取用户阅读统计
|
||||
func (h *ReadingHandler) GetUserReadingStats(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
|
||||
stats, err := h.readingService.GetUserReadingStats(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
||||
}
|
||||
|
||||
// GetReadingProgress 获取阅读进度
|
||||
func (h *ReadingHandler) GetReadingProgress(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
materialID := c.Param("material_id")
|
||||
|
||||
progress, err := h.readingService.GetReadingProgress(userID, materialID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Reading progress not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"progress": progress})
|
||||
}
|
||||
276
serve/internal/handler/speaking_handler.go
Normal file
276
serve/internal/handler/speaking_handler.go
Normal file
@@ -0,0 +1,276 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
|
||||
)
|
||||
|
||||
type SpeakingHandler struct {
|
||||
speakingService *services.SpeakingService
|
||||
}
|
||||
|
||||
func NewSpeakingHandler(speakingService *services.SpeakingService) *SpeakingHandler {
|
||||
return &SpeakingHandler{speakingService: speakingService}
|
||||
}
|
||||
|
||||
// GetSpeakingScenarios 获取口语场景列表
|
||||
func (h *SpeakingHandler) GetSpeakingScenarios(c *gin.Context) {
|
||||
level := c.Query("level")
|
||||
category := c.Query("category")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
scenarios, total, err := h.speakingService.GetSpeakingScenarios(level, category, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"scenarios": scenarios, "total": total})
|
||||
}
|
||||
|
||||
// GetSpeakingScenario 获取单个口语场景
|
||||
func (h *SpeakingHandler) GetSpeakingScenario(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
scenario, err := h.speakingService.GetSpeakingScenario(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Speaking scenario not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"scenario": scenario})
|
||||
}
|
||||
|
||||
// CreateSpeakingScenario 创建口语场景
|
||||
func (h *SpeakingHandler) CreateSpeakingScenario(c *gin.Context) {
|
||||
var scenario models.SpeakingScenario
|
||||
if err := c.ShouldBindJSON(&scenario); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.speakingService.CreateSpeakingScenario(&scenario); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"scenario": scenario})
|
||||
}
|
||||
|
||||
// UpdateSpeakingScenario 更新口语场景
|
||||
func (h *SpeakingHandler) UpdateSpeakingScenario(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var updates models.SpeakingScenario
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.speakingService.UpdateSpeakingScenario(id, &updates); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
scenario, err := h.speakingService.GetSpeakingScenario(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"scenario": scenario})
|
||||
}
|
||||
|
||||
// DeleteSpeakingScenario 删除口语场景
|
||||
func (h *SpeakingHandler) DeleteSpeakingScenario(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := h.speakingService.DeleteSpeakingScenario(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Speaking scenario deleted successfully"})
|
||||
}
|
||||
|
||||
// SearchSpeakingScenarios 搜索口语场景
|
||||
func (h *SpeakingHandler) SearchSpeakingScenarios(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
level := c.Query("level")
|
||||
category := c.Query("category")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
scenarios, total, err := h.speakingService.SearchSpeakingScenarios(keyword, level, category, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"scenarios": scenarios, "total": total})
|
||||
}
|
||||
|
||||
// GetRecommendedScenarios 获取推荐口语场景
|
||||
func (h *SpeakingHandler) GetRecommendedScenarios(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "5"))
|
||||
|
||||
scenarios, err := h.speakingService.GetRecommendedScenarios(userID, limit)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"scenarios": scenarios})
|
||||
}
|
||||
|
||||
// CreateSpeakingRecord 创建口语记录
|
||||
func (h *SpeakingHandler) CreateSpeakingRecord(c *gin.Context) {
|
||||
var record models.SpeakingRecord
|
||||
if err := c.ShouldBindJSON(&record); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
record.UserID = c.GetString("user_id")
|
||||
if err := h.speakingService.CreateSpeakingRecord(&record); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// UpdateSpeakingRecord 更新口语记录
|
||||
func (h *SpeakingHandler) UpdateSpeakingRecord(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var updates models.SpeakingRecord
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := h.speakingService.UpdateSpeakingRecord(id, &updates); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
record, err := h.speakingService.GetSpeakingRecord(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// GetSpeakingRecord 获取口语记录详情
|
||||
func (h *SpeakingHandler) GetSpeakingRecord(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
record, err := h.speakingService.GetSpeakingRecord(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Speaking record not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"record": record})
|
||||
}
|
||||
|
||||
// GetUserSpeakingRecords 获取用户口语记录列表
|
||||
func (h *SpeakingHandler) GetUserSpeakingRecords(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
records, total, err := h.speakingService.GetUserSpeakingRecords(userID, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"records": records,
|
||||
"total": total,
|
||||
})
|
||||
}
|
||||
|
||||
// SubmitSpeaking 提交口语练习
|
||||
func (h *SpeakingHandler) SubmitSpeaking(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
AudioURL string `json:"audio_url"`
|
||||
Transcript string `json:"transcript"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.speakingService.SubmitSpeaking(id, req.AudioURL, req.Transcript); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Speaking submitted successfully"})
|
||||
}
|
||||
|
||||
// GradeSpeaking AI评分口语练习
|
||||
func (h *SpeakingHandler) GradeSpeaking(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Pronunciation float64 `json:"pronunciation"`
|
||||
Fluency float64 `json:"fluency"`
|
||||
Accuracy float64 `json:"accuracy"`
|
||||
Score float64 `json:"score"`
|
||||
Feedback string `json:"feedback"`
|
||||
Suggestions string `json:"suggestions"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.speakingService.GradeSpeaking(id, req.Pronunciation, req.Fluency, req.Accuracy, req.Score, req.Feedback, req.Suggestions); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Speaking graded successfully"})
|
||||
}
|
||||
|
||||
// GetUserSpeakingStats 获取用户口语统计
|
||||
func (h *SpeakingHandler) GetUserSpeakingStats(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
|
||||
stats, err := h.speakingService.GetUserSpeakingStats(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
||||
}
|
||||
|
||||
// GetSpeakingProgress 获取口语进度
|
||||
func (h *SpeakingHandler) GetSpeakingProgress(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
scenarioID := c.Param("scenario_id")
|
||||
|
||||
progress, err := h.speakingService.GetSpeakingProgress(userID, scenarioID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Speaking progress not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"progress": progress})
|
||||
}
|
||||
270
serve/internal/handler/study_plan_handler.go
Normal file
270
serve/internal/handler/study_plan_handler.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/common"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// StudyPlanHandler 学习计划处理器
|
||||
type StudyPlanHandler struct {
|
||||
studyPlanService *services.StudyPlanService
|
||||
}
|
||||
|
||||
func NewStudyPlanHandler(studyPlanService *services.StudyPlanService) *StudyPlanHandler {
|
||||
return &StudyPlanHandler{studyPlanService: studyPlanService}
|
||||
}
|
||||
|
||||
// CreateStudyPlan 创建学习计划
|
||||
// POST /api/v1/study-plans
|
||||
func (h *StudyPlanHandler) CreateStudyPlan(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
var req struct {
|
||||
PlanName string `json:"plan_name" binding:"required,min=1,max=200"`
|
||||
Description *string `json:"description"`
|
||||
DailyGoal int `json:"daily_goal" binding:"required,min=1,max=200"`
|
||||
BookID *string `json:"book_id"`
|
||||
StartDate string `json:"start_date" binding:"required"` // YYYY-MM-DD
|
||||
EndDate *string `json:"end_date"` // YYYY-MM-DD
|
||||
RemindTime *string `json:"remind_time"` // HH:mm
|
||||
RemindDays *string `json:"remind_days"` // "1,2,3,4,5" 表示周一到周五
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ErrorResponse(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 解析日期
|
||||
startDate, err := time.Parse("2006-01-02", req.StartDate)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "开始日期格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
var endDate *time.Time
|
||||
if req.EndDate != nil {
|
||||
parsedEndDate, err := time.Parse("2006-01-02", *req.EndDate)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "结束日期格式错误")
|
||||
return
|
||||
}
|
||||
endDate = &parsedEndDate
|
||||
}
|
||||
|
||||
plan, err := h.studyPlanService.CreateStudyPlan(
|
||||
userID,
|
||||
req.PlanName,
|
||||
req.Description,
|
||||
req.DailyGoal,
|
||||
req.BookID,
|
||||
startDate,
|
||||
endDate,
|
||||
req.RemindTime,
|
||||
req.RemindDays,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "创建学习计划失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, plan)
|
||||
}
|
||||
|
||||
// GetUserStudyPlans 获取用户的学习计划列表
|
||||
// GET /api/v1/study-plans
|
||||
func (h *StudyPlanHandler) GetUserStudyPlans(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
status := c.DefaultQuery("status", "all") // all, active, paused, completed, cancelled
|
||||
|
||||
plans, err := h.studyPlanService.GetUserStudyPlans(userID, status)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取学习计划失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"plans": plans,
|
||||
"total": len(plans),
|
||||
})
|
||||
}
|
||||
|
||||
// GetStudyPlanByID 获取学习计划详情
|
||||
// GET /api/v1/study-plans/:id
|
||||
func (h *StudyPlanHandler) GetStudyPlanByID(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
planIDStr := c.Param("id")
|
||||
|
||||
planID, err := strconv.ParseInt(planIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的计划ID")
|
||||
return
|
||||
}
|
||||
|
||||
plan, err := h.studyPlanService.GetStudyPlanByID(planID, userID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 404, "学习计划不存在")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, plan)
|
||||
}
|
||||
|
||||
// UpdateStudyPlan 更新学习计划
|
||||
// PUT /api/v1/study-plans/:id
|
||||
func (h *StudyPlanHandler) UpdateStudyPlan(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
planIDStr := c.Param("id")
|
||||
|
||||
planID, err := strconv.ParseInt(planIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的计划ID")
|
||||
return
|
||||
}
|
||||
|
||||
var updates map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
common.ErrorResponse(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
plan, err := h.studyPlanService.UpdateStudyPlan(planID, userID, updates)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "更新学习计划失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, plan)
|
||||
}
|
||||
|
||||
// DeleteStudyPlan 删除学习计划
|
||||
// DELETE /api/v1/study-plans/:id
|
||||
func (h *StudyPlanHandler) DeleteStudyPlan(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
planIDStr := c.Param("id")
|
||||
|
||||
planID, err := strconv.ParseInt(planIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的计划ID")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.studyPlanService.DeleteStudyPlan(planID, userID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "删除学习计划失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"message": "学习计划已删除",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePlanStatus 更新计划状态
|
||||
// PATCH /api/v1/study-plans/:id/status
|
||||
func (h *StudyPlanHandler) UpdatePlanStatus(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
planIDStr := c.Param("id")
|
||||
|
||||
planID, err := strconv.ParseInt(planIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的计划ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required,oneof=active paused completed cancelled"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ErrorResponse(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = h.studyPlanService.UpdatePlanStatus(planID, userID, req.Status)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "更新状态失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"message": "状态更新成功",
|
||||
"status": req.Status,
|
||||
})
|
||||
}
|
||||
|
||||
// RecordStudyProgress 记录学习进度
|
||||
// POST /api/v1/study-plans/:id/progress
|
||||
func (h *StudyPlanHandler) RecordStudyProgress(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
planIDStr := c.Param("id")
|
||||
|
||||
planID, err := strconv.ParseInt(planIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的计划ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
WordsStudied int `json:"words_studied" binding:"required,min=1"`
|
||||
StudyDuration int `json:"study_duration"` // 学习时长(分钟)
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ErrorResponse(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = h.studyPlanService.RecordStudyProgress(planID, userID, req.WordsStudied, req.StudyDuration)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "记录进度失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"message": "进度记录成功",
|
||||
})
|
||||
}
|
||||
|
||||
// GetStudyPlanStatistics 获取学习计划统计
|
||||
// GET /api/v1/study-plans/:id/statistics
|
||||
func (h *StudyPlanHandler) GetStudyPlanStatistics(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
planIDStr := c.Param("id")
|
||||
|
||||
planID, err := strconv.ParseInt(planIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的计划ID")
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := h.studyPlanService.GetStudyPlanStatistics(planID, userID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取统计信息失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, stats)
|
||||
}
|
||||
|
||||
// GetTodayStudyPlans 获取今日需要执行的学习计划
|
||||
// GET /api/v1/study-plans/today
|
||||
func (h *StudyPlanHandler) GetTodayStudyPlans(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
plans, err := h.studyPlanService.GetTodayStudyPlans(userID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取今日计划失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"plans": plans,
|
||||
"total": len(plans),
|
||||
})
|
||||
}
|
||||
222
serve/internal/handler/upload_handler.go
Normal file
222
serve/internal/handler/upload_handler.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UploadHandler 文件上传处理器
|
||||
type UploadHandler struct {
|
||||
uploadService service.UploadService
|
||||
}
|
||||
|
||||
// NewUploadHandler 创建文件上传处理器
|
||||
func NewUploadHandler() *UploadHandler {
|
||||
uploadService := service.NewUploadService("./uploads", "http://localhost:8080")
|
||||
return &UploadHandler{
|
||||
uploadService: uploadService,
|
||||
}
|
||||
}
|
||||
|
||||
// UploadAudio 上传音频文件
|
||||
func (h *UploadHandler) UploadAudio(c *gin.Context) {
|
||||
// 获取上传的文件
|
||||
file, err := c.FormFile("audio")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "获取文件失败",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件类型
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
allowedTypes := []string{".mp3", ".wav", ".m4a", ".aac"}
|
||||
isValidType := false
|
||||
for _, allowedType := range allowedTypes {
|
||||
if ext == allowedType {
|
||||
isValidType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isValidType {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "不支持的文件类型",
|
||||
"details": "只支持 mp3, wav, m4a, aac 格式",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件大小 (最大10MB)
|
||||
maxSize := int64(10 * 1024 * 1024)
|
||||
if file.Size > maxSize {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "文件过大",
|
||||
"details": "文件大小不能超过10MB",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 使用上传服务保存文件
|
||||
result, err := h.uploadService.UploadAudio(file)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "文件上传失败",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回上传结果
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "音频文件上传成功",
|
||||
"data": result,
|
||||
})
|
||||
}
|
||||
|
||||
// UploadImage 上传图片文件
|
||||
func (h *UploadHandler) UploadImage(c *gin.Context) {
|
||||
// 获取上传的文件
|
||||
file, err := c.FormFile("image")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "获取文件失败",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件类型
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
allowedTypes := []string{".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||||
isValidType := false
|
||||
for _, allowedType := range allowedTypes {
|
||||
if ext == allowedType {
|
||||
isValidType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isValidType {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "不支持的文件类型",
|
||||
"details": "只支持 jpg, jpeg, png, gif, webp 格式",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件大小 (最大5MB)
|
||||
maxSize := int64(5 * 1024 * 1024)
|
||||
if file.Size > maxSize {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "文件过大",
|
||||
"details": "文件大小不能超过5MB",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 使用上传服务保存文件
|
||||
result, err := h.uploadService.UploadImage(file)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "文件上传失败",
|
||||
"details": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回上传结果
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "图片文件上传成功",
|
||||
"data": result,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteFile 删除文件
|
||||
func (h *UploadHandler) DeleteFile(c *gin.Context) {
|
||||
fileID := c.Param("file_id")
|
||||
if fileID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "File ID is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 实现实际的文件删除逻辑
|
||||
// 目前返回模拟响应
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "File deleted successfully",
|
||||
"data": gin.H{
|
||||
"file_id": fileID,
|
||||
"deleted": true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetFileInfo 获取文件信息
|
||||
func (h *UploadHandler) GetFileInfo(c *gin.Context) {
|
||||
fileID := c.Param("file_id")
|
||||
if fileID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "File ID is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 实现实际的文件信息查询逻辑
|
||||
// 目前返回模拟数据
|
||||
result := gin.H{
|
||||
"file_id": fileID,
|
||||
"filename": "example.mp3",
|
||||
"size": 1024000,
|
||||
"url": "/uploads/audio/" + fileID + ".mp3",
|
||||
"type": "audio",
|
||||
"format": "mp3",
|
||||
"upload_at": "2024-01-15T10:30:00Z",
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "File information retrieved",
|
||||
"data": result,
|
||||
})
|
||||
}
|
||||
|
||||
// GetUploadStats 获取上传统计
|
||||
func (h *UploadHandler) GetUploadStats(c *gin.Context) {
|
||||
// 获取查询参数
|
||||
days := c.DefaultQuery("days", "30")
|
||||
daysInt, err := strconv.Atoi(days)
|
||||
if err != nil || daysInt <= 0 {
|
||||
daysInt = 30
|
||||
}
|
||||
|
||||
// TODO: 实现实际的统计查询逻辑
|
||||
// 目前返回模拟数据
|
||||
stats := gin.H{
|
||||
"period_days": daysInt,
|
||||
"total_files": 156,
|
||||
"audio_files": 89,
|
||||
"image_files": 67,
|
||||
"total_size": "245.6MB",
|
||||
"average_size": "1.6MB",
|
||||
"upload_trend": []gin.H{
|
||||
{"date": "2024-01-10", "count": 12},
|
||||
{"date": "2024-01-11", "count": 8},
|
||||
{"date": "2024-01-12", "count": 15},
|
||||
{"date": "2024-01-13", "count": 10},
|
||||
{"date": "2024-01-14", "count": 18},
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Upload statistics",
|
||||
"data": stats,
|
||||
})
|
||||
}
|
||||
88
serve/internal/handler/user_handler.go
Normal file
88
serve/internal/handler/user_handler.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/service"
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
userService *service.UserService
|
||||
}
|
||||
|
||||
func NewUserHandler(userService *service.UserService) *UserHandler {
|
||||
return &UserHandler{userService: userService}
|
||||
}
|
||||
|
||||
// 用户注册
|
||||
func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "参数错误"})
|
||||
return
|
||||
}
|
||||
userID, err := h.userService.Register(req.Username, req.Email, req.Password)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": err.Error()})
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"userID": userID, "message": "注册成功"})
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
func (h *UserHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "参数错误"})
|
||||
return
|
||||
}
|
||||
token, userID, err := h.userService.Login(req.Email, req.Password)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": err.Error()})
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"token": token, "userID": userID, "message": "登录成功"})
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
func (h *UserHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.Context().Value("userID").(string)
|
||||
profile, err := h.userService.GetProfile(userID)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": err.Error()})
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(profile)
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
func (h *UserHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.Context().Value("userID").(string)
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
Avatar string `json:"avatar"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "参数错误"})
|
||||
return
|
||||
}
|
||||
if err := h.userService.UpdateProfile(userID, req.Username, req.Avatar); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": err.Error()})
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "更新成功"})
|
||||
}
|
||||
533
serve/internal/handler/vocabulary_handler.go
Normal file
533
serve/internal/handler/vocabulary_handler.go
Normal file
@@ -0,0 +1,533 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/common"
|
||||
)
|
||||
|
||||
type VocabularyHandler struct {
|
||||
vocabularyService *services.VocabularyService
|
||||
}
|
||||
|
||||
func NewVocabularyHandler(vocabularyService *services.VocabularyService) *VocabularyHandler {
|
||||
return &VocabularyHandler{vocabularyService: vocabularyService}
|
||||
}
|
||||
|
||||
// GetCategories 获取词汇分类列表
|
||||
func (h *VocabularyHandler) GetCategories(c *gin.Context) {
|
||||
level := c.Query("level")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
categories, err := h.vocabularyService.GetCategories(page, pageSize, level)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"categories": categories})
|
||||
}
|
||||
|
||||
// CreateCategory 创建词汇分类
|
||||
func (h *VocabularyHandler) CreateCategory(c *gin.Context) {
|
||||
var category models.VocabularyCategory
|
||||
if err := c.ShouldBindJSON(&category); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 将模型转换为服务所需参数
|
||||
desc := ""
|
||||
if category.Description != nil {
|
||||
desc = *category.Description
|
||||
}
|
||||
|
||||
createdCategory, err := h.vocabularyService.CreateCategory(category.Name, desc, category.Level)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"category": createdCategory})
|
||||
}
|
||||
|
||||
// GetVocabulariesByCategory 根据分类获取词汇列表
|
||||
func (h *VocabularyHandler) GetVocabulariesByCategory(c *gin.Context) {
|
||||
categoryID := c.Param("id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
level := c.Query("level")
|
||||
|
||||
vocabularies, err := h.vocabularyService.GetVocabulariesByCategory(categoryID, page, pageSize, level)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"vocabularies": vocabularies})
|
||||
}
|
||||
|
||||
// GetVocabularyByID 获取词汇详情
|
||||
func (h *VocabularyHandler) GetVocabularyByID(c *gin.Context) {
|
||||
vocabularyID := c.Param("id")
|
||||
|
||||
vocabulary, err := h.vocabularyService.GetVocabularyByID(vocabularyID)
|
||||
if err != nil {
|
||||
if common.IsBusinessError(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"vocabulary": vocabulary})
|
||||
}
|
||||
|
||||
// SearchVocabularies 搜索词汇
|
||||
func (h *VocabularyHandler) SearchVocabularies(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
level := c.Query("level")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
vocabularies, err := h.vocabularyService.SearchVocabularies(keyword, level, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"vocabularies": vocabularies})
|
||||
}
|
||||
|
||||
// GetUserVocabularyProgress 获取用户词汇学习进度
|
||||
func (h *VocabularyHandler) GetUserVocabularyProgress(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
vocabularyID := c.Param("id")
|
||||
|
||||
progress, err := h.vocabularyService.GetUserVocabularyProgress(userID, vocabularyID)
|
||||
if err != nil {
|
||||
if common.IsBusinessError(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"progress": progress})
|
||||
}
|
||||
|
||||
// UpdateUserVocabularyProgress 更新用户词汇学习进度
|
||||
func (h *VocabularyHandler) UpdateUserVocabularyProgress(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
vocabularyID := c.Param("id")
|
||||
|
||||
var updates map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
progress, err := h.vocabularyService.UpdateUserVocabularyProgress(userID, vocabularyID, updates)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"progress": progress})
|
||||
}
|
||||
|
||||
// GetUserVocabularyStats 获取用户词汇学习统计
|
||||
func (h *VocabularyHandler) GetUserVocabularyStats(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
stats, err := h.vocabularyService.GetUserVocabularyStats(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
||||
}
|
||||
|
||||
// UpdateCategory 更新词汇分类
|
||||
func (h *VocabularyHandler) UpdateCategory(c *gin.Context) {
|
||||
categoryID := c.Param("id")
|
||||
if categoryID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "分类ID不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
var updates map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updates); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
category, err := h.vocabularyService.UpdateCategory(categoryID, updates)
|
||||
if err != nil {
|
||||
if common.IsBusinessError(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"category": category})
|
||||
}
|
||||
|
||||
// DeleteCategory 删除词汇分类
|
||||
func (h *VocabularyHandler) DeleteCategory(c *gin.Context) {
|
||||
categoryID := c.Param("id")
|
||||
if categoryID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "分类ID不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.vocabularyService.DeleteCategory(categoryID); err != nil {
|
||||
if common.IsBusinessError(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "分类已删除"})
|
||||
}
|
||||
|
||||
// CreateVocabulary 创建词汇
|
||||
func (h *VocabularyHandler) CreateVocabulary(c *gin.Context) {
|
||||
type reqBody struct {
|
||||
Word string `json:"word"`
|
||||
Phonetic string `json:"phonetic"`
|
||||
Level string `json:"level"`
|
||||
Frequency int `json:"frequency"`
|
||||
CategoryID string `json:"category_id"`
|
||||
Definitions []string `json:"definitions"`
|
||||
Examples []string `json:"examples"`
|
||||
Images []string `json:"images"`
|
||||
}
|
||||
|
||||
var req reqBody
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
vocab, err := h.vocabularyService.CreateVocabulary(
|
||||
req.Word, req.Phonetic, req.Level, req.Frequency, req.CategoryID,
|
||||
req.Definitions, req.Examples, req.Images,
|
||||
)
|
||||
if err != nil {
|
||||
if common.IsBusinessError(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"vocabulary": vocab})
|
||||
}
|
||||
|
||||
// GetDailyVocabularyStats 获取每日学习单词统计
|
||||
func (h *VocabularyHandler) GetDailyVocabularyStats(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
userIDStr := strconv.FormatInt(userID, 10)
|
||||
|
||||
stats, err := h.vocabularyService.GetDailyStats(userIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
||||
}
|
||||
|
||||
// CreateVocabularyTest 创建词汇测试
|
||||
func (h *VocabularyHandler) CreateVocabularyTest(c *gin.Context) {
|
||||
type reqBody struct {
|
||||
TestType string `json:"test_type"`
|
||||
Level string `json:"level"`
|
||||
TotalWords int `json:"total_words"`
|
||||
}
|
||||
var req reqBody
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
test, err := h.vocabularyService.CreateVocabularyTest(userID, req.TestType, req.Level, req.TotalWords)
|
||||
if err != nil {
|
||||
if common.IsBusinessError(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"test": test})
|
||||
}
|
||||
|
||||
// GetVocabularyTest 获取词汇测试
|
||||
func (h *VocabularyHandler) GetVocabularyTest(c *gin.Context) {
|
||||
testID := c.Param("id")
|
||||
if testID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "测试ID不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
test, err := h.vocabularyService.GetVocabularyTest(testID)
|
||||
if err != nil {
|
||||
if common.IsBusinessError(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"test": test})
|
||||
}
|
||||
|
||||
// UpdateVocabularyTestResult 更新词汇测试结果
|
||||
func (h *VocabularyHandler) UpdateVocabularyTestResult(c *gin.Context) {
|
||||
testID := c.Param("id")
|
||||
if testID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "测试ID不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
type reqBody struct {
|
||||
CorrectWords int `json:"correct_words"`
|
||||
Score float64 `json:"score"`
|
||||
Duration int `json:"duration"`
|
||||
}
|
||||
var req reqBody
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.vocabularyService.UpdateVocabularyTestResult(testID, req.CorrectWords, req.Score, req.Duration); err != nil {
|
||||
if common.IsBusinessError(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
} else {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "测试结果更新成功"})
|
||||
}
|
||||
|
||||
// GetTodayStudyWords 获取今日学习单词
|
||||
func (h *VocabularyHandler) GetTodayStudyWords(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
|
||||
|
||||
words, err := h.vocabularyService.GetTodayStudyWords(userID, limit)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取今日单词失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 确保返回空数组而不是null
|
||||
if words == nil {
|
||||
words = []map[string]interface{}{}
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{"words": words})
|
||||
}
|
||||
|
||||
// GetStudyStatistics 获取学习统计
|
||||
func (h *VocabularyHandler) GetStudyStatistics(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
date := c.Query("date")
|
||||
|
||||
stats, err := h.vocabularyService.GetStudyStatistics(userID, date)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取学习统计失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, stats)
|
||||
}
|
||||
|
||||
// GetStudyStatisticsHistory 获取学习统计历史
|
||||
func (h *VocabularyHandler) GetStudyStatisticsHistory(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
startDate := c.Query("startDate")
|
||||
endDate := c.Query("endDate")
|
||||
|
||||
history, err := h.vocabularyService.GetStudyStatisticsHistory(userID, startDate, endDate)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] GetStudyStatisticsHistory failed: userID=%d, startDate=%s, endDate=%s, error=%v", userID, startDate, endDate, err)
|
||||
common.ErrorResponse(c, 500, "获取学习历史失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, history)
|
||||
}
|
||||
|
||||
// GetSystemVocabularyBooks 获取系统词汇书列表
|
||||
func (h *VocabularyHandler) GetSystemVocabularyBooks(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
|
||||
category := c.Query("category")
|
||||
|
||||
books, total, err := h.vocabularyService.GetSystemVocabularyBooks(page, limit, category)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取系统词汇书失败")
|
||||
return
|
||||
}
|
||||
|
||||
totalPages := (int(total) + limit - 1) / limit
|
||||
pagination := &common.Pagination{
|
||||
Page: page,
|
||||
PageSize: limit,
|
||||
Total: int(total),
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
common.PaginationSuccessResponse(c, books, pagination)
|
||||
}
|
||||
|
||||
// GetVocabularyBookCategories 获取词汇书分类列表
|
||||
func (h *VocabularyHandler) GetVocabularyBookCategories(c *gin.Context) {
|
||||
categories, err := h.vocabularyService.GetVocabularyBookCategories()
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取词汇书分类失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, categories)
|
||||
}
|
||||
|
||||
// GetVocabularyBookProgress 获取词汇书学习进度
|
||||
func (h *VocabularyHandler) GetVocabularyBookProgress(c *gin.Context) {
|
||||
bookID := c.Param("id")
|
||||
if bookID == "" {
|
||||
common.ErrorResponse(c, 400, "词汇书ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
progress, err := h.vocabularyService.GetVocabularyBookProgress(userID, bookID)
|
||||
if err != nil {
|
||||
// 如果没有进度记录,返回默认进度
|
||||
now := time.Now()
|
||||
defaultProgress := &models.UserVocabularyBookProgress{
|
||||
ID: 0,
|
||||
UserID: userID,
|
||||
BookID: bookID,
|
||||
LearnedWords: 0,
|
||||
MasteredWords: 0,
|
||||
ProgressPercentage: 0.0,
|
||||
StreakDays: 0,
|
||||
TotalStudyDays: 0,
|
||||
AverageDailyWords: 0.0,
|
||||
EstimatedCompletionDate: nil,
|
||||
IsCompleted: false,
|
||||
CompletedAt: nil,
|
||||
StartedAt: now,
|
||||
LastStudiedAt: now,
|
||||
}
|
||||
common.SuccessResponse(c, defaultProgress)
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, progress)
|
||||
}
|
||||
|
||||
// GetVocabularyBookWords 获取词汇书单词列表
|
||||
func (h *VocabularyHandler) GetVocabularyBookWords(c *gin.Context) {
|
||||
bookID := c.Param("id")
|
||||
if bookID == "" {
|
||||
common.ErrorResponse(c, 400, "词汇书ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "100"))
|
||||
|
||||
words, total, err := h.vocabularyService.GetVocabularyBookWords(bookID, page, limit)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取词汇书单词失败")
|
||||
return
|
||||
}
|
||||
|
||||
totalPages := (int(total) + limit - 1) / limit
|
||||
pagination := &common.Pagination{
|
||||
Page: page,
|
||||
PageSize: limit,
|
||||
Total: int(total),
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
common.PaginationSuccessResponse(c, words, pagination)
|
||||
}
|
||||
|
||||
// GetUserWordProgress 获取用户单词学习进度
|
||||
func (h *VocabularyHandler) GetUserWordProgress(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
wordIDStr := c.Param("id")
|
||||
|
||||
wordID, err := strconv.ParseInt(wordIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的单词ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 查询用户单词进度
|
||||
progress, err := h.vocabularyService.GetUserWordProgress(userID, wordID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取单词进度失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, progress)
|
||||
}
|
||||
|
||||
// UpdateUserWordProgress 更新用户单词学习进度
|
||||
func (h *VocabularyHandler) UpdateUserWordProgress(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
wordIDStr := c.Param("id")
|
||||
|
||||
wordID, err := strconv.ParseInt(wordIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的单词ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
IsCorrect *bool `json:"isCorrect"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的请求参数")
|
||||
return
|
||||
}
|
||||
|
||||
// 更新单词进度
|
||||
progress, err := h.vocabularyService.UpdateUserWordProgress(userID, wordID, req.Status, req.IsCorrect)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "更新单词进度失败")
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, progress)
|
||||
}
|
||||
152
serve/internal/handler/word_book_handler.go
Normal file
152
serve/internal/handler/word_book_handler.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/common"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// WordBookHandler 生词本处理器
|
||||
type WordBookHandler struct {
|
||||
wordBookService *services.WordBookService
|
||||
}
|
||||
|
||||
func NewWordBookHandler(wordBookService *services.WordBookService) *WordBookHandler {
|
||||
return &WordBookHandler{wordBookService: wordBookService}
|
||||
}
|
||||
|
||||
// ToggleFavorite 切换单词收藏状态
|
||||
// POST /api/v1/word-book/toggle/:id
|
||||
func (h *WordBookHandler) ToggleFavorite(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
wordIDStr := c.Param("id")
|
||||
|
||||
wordID, err := strconv.ParseInt(wordIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的单词ID")
|
||||
return
|
||||
}
|
||||
|
||||
isFavorite, err := h.wordBookService.ToggleFavorite(userID, wordID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "操作失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"is_favorite": isFavorite,
|
||||
"message": func() string {
|
||||
if isFavorite {
|
||||
return "已添加到生词本"
|
||||
}
|
||||
return "已从生词本移除"
|
||||
}(),
|
||||
})
|
||||
}
|
||||
|
||||
// GetFavoriteWords 获取生词本列表
|
||||
// GET /api/v1/word-book
|
||||
func (h *WordBookHandler) GetFavoriteWords(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
sortBy := c.DefaultQuery("sort_by", "created_at") // created_at, proficiency, word
|
||||
order := c.DefaultQuery("order", "desc") // asc, desc
|
||||
|
||||
words, total, err := h.wordBookService.GetFavoriteWords(userID, page, pageSize, sortBy, order)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取生词本失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"words": words,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
})
|
||||
}
|
||||
|
||||
// GetFavoriteWordsByBook 获取指定词汇书的生词本
|
||||
// GET /api/v1/word-book/books/:id
|
||||
func (h *WordBookHandler) GetFavoriteWordsByBook(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
bookID := c.Param("id")
|
||||
|
||||
words, err := h.wordBookService.GetFavoriteWordsByBook(userID, bookID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取生词本失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"words": words,
|
||||
"total": len(words),
|
||||
})
|
||||
}
|
||||
|
||||
// GetFavoriteStats 获取生词本统计信息
|
||||
// GET /api/v1/word-book/stats
|
||||
func (h *WordBookHandler) GetFavoriteStats(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
stats, err := h.wordBookService.GetFavoriteStats(userID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "获取统计信息失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, stats)
|
||||
}
|
||||
|
||||
// BatchAddToFavorite 批量添加到生词本
|
||||
// POST /api/v1/word-book/batch
|
||||
func (h *WordBookHandler) BatchAddToFavorite(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
|
||||
var req struct {
|
||||
WordIDs []int64 `json:"word_ids" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
common.ErrorResponse(c, 400, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
count, err := h.wordBookService.BatchAddToFavorite(userID, req.WordIDs)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "批量添加失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"added_count": count,
|
||||
"message": "批量添加成功",
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveFromFavorite 从生词本移除单词
|
||||
// DELETE /api/v1/word-book/:id
|
||||
func (h *WordBookHandler) RemoveFromFavorite(c *gin.Context) {
|
||||
userID := c.GetInt64("user_id")
|
||||
wordIDStr := c.Param("id")
|
||||
|
||||
wordID, err := strconv.ParseInt(wordIDStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 400, "无效的单词ID")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.wordBookService.RemoveFromFavorite(userID, wordID)
|
||||
if err != nil {
|
||||
common.ErrorResponse(c, 500, "移除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SuccessResponse(c, gin.H{
|
||||
"message": "已从生词本移除",
|
||||
})
|
||||
}
|
||||
268
serve/internal/handler/writing_handler.go
Normal file
268
serve/internal/handler/writing_handler.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services"
|
||||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
|
||||
)
|
||||
|
||||
type WritingHandler struct {
|
||||
writingService *services.WritingService
|
||||
}
|
||||
|
||||
func NewWritingHandler(writingService *services.WritingService) *WritingHandler {
|
||||
return &WritingHandler{writingService: writingService}
|
||||
}
|
||||
|
||||
// GetWritingPrompts 获取写作题目列表
|
||||
func (h *WritingHandler) GetWritingPrompts(c *gin.Context) {
|
||||
level := c.Query("level")
|
||||
category := c.Query("category")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
// 转换为 limit/offset 以匹配服务方法签名
|
||||
limit := pageSize
|
||||
offset := (page - 1) * pageSize
|
||||
prompts, err := h.writingService.GetWritingPrompts(level, category, limit, offset)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"prompts": prompts})
|
||||
}
|
||||
|
||||
// GetWritingPrompt 获取单个写作题目
|
||||
func (h *WritingHandler) GetWritingPrompt(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
prompt, err := h.writingService.GetWritingPrompt(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Writing prompt not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"prompt": prompt})
|
||||
}
|
||||
|
||||
// CreateWritingPrompt 创建写作题目
|
||||
func (h *WritingHandler) CreateWritingPrompt(c *gin.Context) {
|
||||
var prompt models.WritingPrompt
|
||||
if err := c.ShouldBindJSON(&prompt); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.writingService.CreateWritingPrompt(&prompt); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"prompt": prompt})
|
||||
}
|
||||
|
||||
// UpdateWritingPrompt 更新写作题目
|
||||
func (h *WritingHandler) UpdateWritingPrompt(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var prompt models.WritingPrompt
|
||||
if err := c.ShouldBindJSON(&prompt); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
prompt.ID = id // 确保ID匹配
|
||||
if err := h.writingService.UpdateWritingPrompt(id, &prompt); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"prompt": prompt})
|
||||
}
|
||||
|
||||
// DeleteWritingPrompt 删除写作题目
|
||||
func (h *WritingHandler) DeleteWritingPrompt(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
if err := h.writingService.DeleteWritingPrompt(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Writing prompt deleted successfully"})
|
||||
}
|
||||
|
||||
// SearchWritingPrompts 搜索写作题目
|
||||
func (h *WritingHandler) SearchWritingPrompts(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
level := c.Query("level")
|
||||
category := c.Query("category")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
// 转换为 limit/offset 以匹配服务方法签名
|
||||
limit := pageSize
|
||||
offset := (page - 1) * pageSize
|
||||
prompts, err := h.writingService.SearchWritingPrompts(keyword, level, category, limit, offset)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"prompts": prompts})
|
||||
}
|
||||
|
||||
// GetRecommendedPrompts 获取推荐写作题目
|
||||
func (h *WritingHandler) GetRecommendedPrompts(c *gin.Context) {
|
||||
userID := c.GetString("user_id") // 假设用户ID已经通过中间件设置
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "5"))
|
||||
prompts, err := h.writingService.GetRecommendedPrompts(userID, limit)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"prompts": prompts})
|
||||
}
|
||||
|
||||
// CreateWritingSubmission 创建写作提交
|
||||
func (h *WritingHandler) CreateWritingSubmission(c *gin.Context) {
|
||||
var submission models.WritingSubmission
|
||||
if err := c.ShouldBindJSON(&submission); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.writingService.CreateWritingSubmission(&submission); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"submission": submission})
|
||||
}
|
||||
|
||||
// UpdateWritingSubmission 更新写作提交
|
||||
func (h *WritingHandler) UpdateWritingSubmission(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var submission models.WritingSubmission
|
||||
if err := c.ShouldBindJSON(&submission); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
submission.ID = id // 确保ID匹配
|
||||
if err := h.writingService.UpdateWritingSubmission(id, &submission); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"submission": submission})
|
||||
}
|
||||
|
||||
// GetWritingSubmission 获取写作提交详情
|
||||
func (h *WritingHandler) GetWritingSubmission(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
submission, err := h.writingService.GetWritingSubmission(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Writing submission not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"submission": submission})
|
||||
}
|
||||
|
||||
// GetUserWritingSubmissions 获取用户写作提交列表
|
||||
func (h *WritingHandler) GetUserWritingSubmissions(c *gin.Context) {
|
||||
userID := c.GetString("user_id") // 假设用户ID已经通过中间件设置
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
// 转换为 limit/offset 以匹配服务方法签名
|
||||
limit := pageSize
|
||||
offset := (page - 1) * pageSize
|
||||
submissions, err := h.writingService.GetUserWritingSubmissions(userID, limit, offset)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": submissions})
|
||||
}
|
||||
|
||||
// SubmitWriting 提交写作作业
|
||||
func (h *WritingHandler) SubmitWriting(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req struct {
|
||||
Content string `json:"content"`
|
||||
TimeSpent int `json:"time_spent"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.writingService.SubmitWriting(id, req.Content, req.TimeSpent); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"id": id, "message": "Writing submitted successfully"})
|
||||
}
|
||||
|
||||
// GradeWriting AI批改写作
|
||||
func (h *WritingHandler) GradeWriting(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Score float64 `json:"score" binding:"required"`
|
||||
GrammarScore float64 `json:"grammar_score"`
|
||||
VocabularyScore float64 `json:"vocabulary_score"`
|
||||
CoherenceScore float64 `json:"coherence_score"`
|
||||
Feedback string `json:"feedback"`
|
||||
Suggestions string `json:"suggestions"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.writingService.GradeWriting(id, req.Score, req.GrammarScore, req.VocabularyScore, req.CoherenceScore, req.Feedback, req.Suggestions); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"id": id, "message": "Writing graded successfully"})
|
||||
}
|
||||
|
||||
// GetUserWritingStats 获取用户写作统计
|
||||
func (h *WritingHandler) GetUserWritingStats(c *gin.Context) {
|
||||
userID := c.GetString("user_id") // 假设用户ID已经通过中间件设置
|
||||
|
||||
stats, err := h.writingService.GetUserWritingStats(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
||||
}
|
||||
|
||||
// GetWritingProgress 获取写作进度
|
||||
func (h *WritingHandler) GetWritingProgress(c *gin.Context) {
|
||||
userID := c.GetString("user_id") // 假设用户ID已经通过中间件设置
|
||||
promptID := c.Param("prompt_id")
|
||||
|
||||
progress, err := h.writingService.GetWritingProgress(userID, promptID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Writing progress not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"progress": progress})
|
||||
}
|
||||
Reference in New Issue
Block a user