package handlers import ( "net/http" "strconv" "time" "github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models" "github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/services" "github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/utils" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // WritingHandler 写作练习处理器 type WritingHandler struct { writingService *services.WritingService } // NewWritingHandler 创建写作练习处理器实例 func NewWritingHandler(writingService *services.WritingService) *WritingHandler { return &WritingHandler{ writingService: writingService, } } // ===== 请求和响应结构体 ===== // CreateWritingPromptRequest 创建写作题目请求 type CreateWritingPromptRequest struct { Title string `json:"title" binding:"required"` Prompt string `json:"prompt" binding:"required"` Instructions *string `json:"instructions"` MinWords *int `json:"min_words"` MaxWords *int `json:"max_words"` TimeLimit *int `json:"time_limit"` Level string `json:"level" binding:"required"` Category string `json:"category"` Tags *string `json:"tags"` SampleAnswer *string `json:"sample_answer"` Rubric *string `json:"rubric"` } // UpdateWritingPromptRequest 更新写作题目请求 type UpdateWritingPromptRequest struct { Title *string `json:"title"` Prompt *string `json:"prompt"` Instructions *string `json:"instructions"` MinWords *int `json:"min_words"` MaxWords *int `json:"max_words"` TimeLimit *int `json:"time_limit"` Level *string `json:"level"` Category *string `json:"category"` Tags *string `json:"tags"` SampleAnswer *string `json:"sample_answer"` Rubric *string `json:"rubric"` } // CreateWritingSubmissionRequest 创建写作提交请求 type CreateWritingSubmissionRequest struct { PromptID string `json:"prompt_id" binding:"required"` } // SubmitWritingRequest 提交写作请求 type SubmitWritingRequest struct { Content string `json:"content" binding:"required"` TimeSpent int `json:"time_spent" binding:"required"` } // GradeWritingRequest AI批改请求 type GradeWritingRequest struct { Score float64 `json:"score" binding:"required,min=0,max=100"` GrammarScore float64 `json:"grammar_score" binding:"required,min=0,max=100"` VocabScore float64 `json:"vocab_score" binding:"required,min=0,max=100"` CoherenceScore float64 `json:"coherence_score" binding:"required,min=0,max=100"` Feedback string `json:"feedback" binding:"required"` Suggestions string `json:"suggestions"` } // Response 通用响应结构 type Response struct { Message string `json:"message"` Data interface{} `json:"data,omitempty"` Error string `json:"error,omitempty"` } // ===== 写作题目管理接口 ===== // GetWritingPrompts 获取写作题目列表 // @Summary 获取写作题目列表 // @Description 获取写作题目列表,支持按难度和分类筛选 // @Tags 写作练习 // @Accept json // @Produce json // @Param difficulty query string false "难度筛选" // @Param category query string false "分类筛选" // @Param page query int false "页码" default(1) // @Param limit query int false "每页数量" default(10) // @Success 200 {object} Response // @Router /writing/prompts [get] func (h *WritingHandler) GetWritingPrompts(c *gin.Context) { difficulty := c.Query("difficulty") category := c.Query("category") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset := (page - 1) * limit prompts, err := h.writingService.GetWritingPrompts(difficulty, category, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "获取写作题目失败", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "获取写作题目成功", "data": prompts, "pagination": gin.H{ "page": page, "limit": limit, "total": len(prompts), }, }) } // GetWritingPrompt 获取单个写作题目 // @Summary 获取写作题目详情 // @Description 根据ID获取写作题目详情 // @Tags 写作练习 // @Accept json // @Produce json // @Param id path string true "题目ID" // @Success 200 {object} Response // @Router /writing/prompts/{id} [get] 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": "写作题目不存在", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "获取写作题目成功", "data": prompt, }) } // CreateWritingPrompt 创建写作题目 // @Summary 创建写作题目 // @Description 创建新的写作题目 // @Tags 写作练习 // @Accept json // @Produce json // @Param request body CreateWritingPromptRequest true "创建请求" // @Success 201 {object} Response // @Router /writing/prompts [post] func (h *WritingHandler) CreateWritingPrompt(c *gin.Context) { var req CreateWritingPromptRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误", "details": err.Error(), }) return } prompt := &models.WritingPrompt{ ID: uuid.New().String(), Title: req.Title, Prompt: req.Prompt, Instructions: req.Instructions, MinWords: req.MinWords, MaxWords: req.MaxWords, TimeLimit: req.TimeLimit, Level: req.Level, Category: req.Category, Tags: req.Tags, SampleAnswer: req.SampleAnswer, Rubric: req.Rubric, IsActive: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := h.writingService.CreateWritingPrompt(prompt); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "创建写作题目失败", "details": err.Error(), }) return } c.JSON(http.StatusCreated, gin.H{ "message": "写作题目创建成功", "data": prompt, }) } // UpdateWritingPrompt 更新写作题目 // @Summary 更新写作题目 // @Description 更新写作题目信息 // @Tags 写作练习 // @Accept json // @Produce json // @Param id path string true "题目ID" // @Param request body UpdateWritingPromptRequest true "更新请求" // @Success 200 {object} Response // @Router /writing/prompts/{id} [put] func (h *WritingHandler) UpdateWritingPrompt(c *gin.Context) { id := c.Param("id") var req UpdateWritingPromptRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误", "details": err.Error(), }) return } // 检查题目是否存在 existingPrompt, err := h.writingService.GetWritingPrompt(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{ "error": "写作题目不存在", "details": err.Error(), }) return } // 构建更新数据 updateData := &models.WritingPrompt{ UpdatedAt: time.Now(), } if req.Title != nil { updateData.Title = *req.Title } if req.Prompt != nil { updateData.Prompt = *req.Prompt } if req.Instructions != nil { updateData.Instructions = req.Instructions } if req.MinWords != nil { updateData.MinWords = req.MinWords } if req.MaxWords != nil { updateData.MaxWords = req.MaxWords } if req.TimeLimit != nil { updateData.TimeLimit = req.TimeLimit } if req.Level != nil { updateData.Level = *req.Level } if req.Category != nil { updateData.Category = *req.Category } if req.Tags != nil { updateData.Tags = req.Tags } if req.SampleAnswer != nil { updateData.SampleAnswer = req.SampleAnswer } if req.Rubric != nil { updateData.Rubric = req.Rubric } if err := h.writingService.UpdateWritingPrompt(id, updateData); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "更新写作题目失败", "details": err.Error(), }) return } // 返回更新后的题目 updatedPrompt, _ := h.writingService.GetWritingPrompt(id) c.JSON(http.StatusOK, gin.H{ "message": "写作题目更新成功", "data": updatedPrompt, "original": existingPrompt, }) } // DeleteWritingPrompt 删除写作题目 // @Summary 删除写作题目 // @Description 软删除写作题目 // @Tags 写作练习 // @Accept json // @Produce json // @Param id path string true "题目ID" // @Success 200 {object} Response // @Router /writing/prompts/{id} [delete] func (h *WritingHandler) DeleteWritingPrompt(c *gin.Context) { id := c.Param("id") // 检查题目是否存在 _, err := h.writingService.GetWritingPrompt(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{ "error": "写作题目不存在", "details": err.Error(), }) return } if err := h.writingService.DeleteWritingPrompt(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "删除写作题目失败", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "写作题目删除成功", }) } // SearchWritingPrompts 搜索写作题目 // @Summary 搜索写作题目 // @Description 根据关键词搜索写作题目 // @Tags 写作练习 // @Accept json // @Produce json // @Param keyword query string true "搜索关键词" // @Param difficulty query string false "难度筛选" // @Param category query string false "分类筛选" // @Param page query int false "页码" default(1) // @Param limit query int false "每页数量" default(10) // @Success 200 {object} Response // @Router /writing/prompts/search [get] func (h *WritingHandler) SearchWritingPrompts(c *gin.Context) { keyword := c.Query("keyword") if keyword == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "搜索关键词不能为空", }) return } difficulty := c.Query("difficulty") category := c.Query("category") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset := (page - 1) * limit prompts, err := h.writingService.SearchWritingPrompts(keyword, difficulty, category, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "搜索写作题目失败", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "搜索写作题目成功", "data": prompts, "search_params": gin.H{ "keyword": keyword, "difficulty": difficulty, "category": category, }, "pagination": gin.H{ "page": page, "limit": limit, "total": len(prompts), }, }) } // GetRecommendedPrompts 获取推荐写作题目 // @Summary 获取推荐写作题目 // @Description 根据用户历史表现推荐合适的写作题目 // @Tags 写作练习 // @Accept json // @Produce json // @Param limit query int false "推荐数量" default(5) // @Success 200 {object} Response // @Router /writing/prompts/recommendations [get] func (h *WritingHandler) GetRecommendedPrompts(c *gin.Context) { userID, ok := utils.GetUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"}) return } limit, _ := strconv.Atoi(c.DefaultQuery("limit", "5")) prompts, err := h.writingService.GetRecommendedPrompts(utils.Int64ToString(userID), limit) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "获取推荐题目失败", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "获取推荐题目成功", "data": prompts, }) } // ===== 写作提交管理接口 ===== // CreateWritingSubmission 创建写作提交 // @Summary 创建写作提交 // @Description 开始写作练习,创建写作提交记录 // @Tags 写作练习 // @Accept json // @Produce json // @Param request body CreateWritingSubmissionRequest true "创建请求" // @Success 201 {object} Response // @Router /writing/submissions [post] func (h *WritingHandler) CreateWritingSubmission(c *gin.Context) { userID, ok := utils.GetUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"}) return } var req CreateWritingSubmissionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误", "details": err.Error(), }) return } // 检查是否已有该题目的提交记录 existingSubmission, err := h.writingService.GetWritingProgress(utils.Int64ToString(userID), req.PromptID) if err == nil && existingSubmission != nil { c.JSON(http.StatusOK, gin.H{ "message": "写作提交记录已存在", "data": existingSubmission, }) return } submission := &models.WritingSubmission{ ID: uuid.New().String(), UserID: utils.Int64ToString(userID), PromptID: req.PromptID, StartedAt: time.Now(), CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := h.writingService.CreateWritingSubmission(submission); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "创建写作提交失败", "details": err.Error(), }) return } c.JSON(http.StatusCreated, gin.H{ "message": "写作提交创建成功", "data": submission, }) } // SubmitWriting 提交写作作业 // @Summary 提交写作作业 // @Description 提交完成的写作内容 // @Tags 写作练习 // @Accept json // @Produce json // @Param id path string true "提交ID" // @Param request body SubmitWritingRequest true "提交请求" // @Success 200 {object} Response // @Router /writing/submissions/{id}/submit [put] func (h *WritingHandler) SubmitWriting(c *gin.Context) { userID, ok := utils.GetUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"}) return } submissionID := c.Param("id") var req SubmitWritingRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误", "details": err.Error(), }) return } // 检查提交记录是否存在且属于当前用户 submission, err := h.writingService.GetWritingSubmission(submissionID) if err != nil { c.JSON(http.StatusNotFound, gin.H{ "error": "写作提交不存在", "details": err.Error(), }) return } if submission.UserID != utils.Int64ToString(userID) { c.JSON(http.StatusForbidden, gin.H{ "error": "无权限访问此提交记录", }) return } if submission.SubmittedAt != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "该写作已经提交,无法重复提交", }) return } if err := h.writingService.SubmitWriting(submissionID, req.Content, req.TimeSpent); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "提交写作失败", "details": err.Error(), }) return } // 返回更新后的提交记录 updatedSubmission, _ := h.writingService.GetWritingSubmission(submissionID) c.JSON(http.StatusOK, gin.H{ "message": "写作提交成功", "data": updatedSubmission, }) } // GradeWriting AI批改写作 // @Summary AI批改写作 // @Description 对提交的写作进行AI批改和评分 // @Tags 写作练习 // @Accept json // @Produce json // @Param id path string true "提交ID" // @Param request body GradeWritingRequest true "批改请求" // @Success 200 {object} Response // @Router /writing/submissions/{id}/grade [put] func (h *WritingHandler) GradeWriting(c *gin.Context) { submissionID := c.Param("id") var req GradeWritingRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误", "details": err.Error(), }) return } // 检查提交记录是否存在 submission, err := h.writingService.GetWritingSubmission(submissionID) if err != nil { c.JSON(http.StatusNotFound, gin.H{ "error": "写作提交不存在", "details": err.Error(), }) return } if submission.SubmittedAt == nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "该写作尚未提交,无法批改", }) return } if submission.GradedAt != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "该写作已经批改,无法重复批改", }) return } if err := h.writingService.GradeWriting( submissionID, req.Score, req.GrammarScore, req.VocabScore, req.CoherenceScore, req.Feedback, req.Suggestions, ); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "批改写作失败", "details": err.Error(), }) return } // 返回更新后的提交记录 updatedSubmission, _ := h.writingService.GetWritingSubmission(submissionID) c.JSON(http.StatusOK, gin.H{ "message": "写作批改成功", "data": updatedSubmission, }) } // GetWritingSubmission 获取写作提交详情 // @Summary 获取写作提交详情 // @Description 根据ID获取写作提交详情 // @Tags 写作练习 // @Accept json // @Produce json // @Param id path string true "提交ID" // @Success 200 {object} Response // @Router /writing/submissions/{id} [get] func (h *WritingHandler) GetWritingSubmission(c *gin.Context) { userID, ok := utils.GetUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"}) return } submissionID := c.Param("id") submission, err := h.writingService.GetWritingSubmission(submissionID) if err != nil { c.JSON(http.StatusNotFound, gin.H{ "error": "写作提交不存在", "details": err.Error(), }) return } // 检查权限 if submission.UserID != utils.Int64ToString(userID) { c.JSON(http.StatusForbidden, gin.H{ "error": "无权限访问此提交记录", }) return } c.JSON(http.StatusOK, gin.H{ "message": "获取写作提交成功", "data": submission, }) } // GetUserWritingSubmissions 获取用户写作提交列表 // @Summary 获取用户写作提交列表 // @Description 获取当前用户的写作提交列表 // @Tags 写作练习 // @Accept json // @Produce json // @Param page query int false "页码" default(1) // @Param limit query int false "每页数量" default(10) // @Success 200 {object} Response // @Router /writing/submissions [get] func (h *WritingHandler) GetUserWritingSubmissions(c *gin.Context) { userID, ok := utils.GetUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset := (page - 1) * limit submissions, err := h.writingService.GetUserWritingSubmissions(utils.Int64ToString(userID), limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "获取写作提交列表失败", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "获取写作提交列表成功", "data": submissions, "pagination": gin.H{ "page": page, "limit": limit, "total": len(submissions), }, }) } // ===== 写作统计和进度接口 ===== // GetWritingStats 获取用户写作统计 // @Summary 获取用户写作统计 // @Description 获取用户写作学习统计数据 // @Tags 写作练习 // @Accept json // @Produce json // @Success 200 {object} Response // @Router /writing/stats [get] func (h *WritingHandler) GetWritingStats(c *gin.Context) { userID, ok := utils.GetUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"}) return } stats, err := h.writingService.GetUserWritingStats(utils.Int64ToString(userID)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "获取写作统计失败", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "获取写作统计成功", "data": stats, }) } // GetWritingProgress 获取写作进度 // @Summary 获取写作进度 // @Description 获取用户在特定题目上的写作进度 // @Tags 写作练习 // @Accept json // @Produce json // @Param prompt_id path string true "题目ID" // @Success 200 {object} Response // @Router /writing/progress/{prompt_id} [get] func (h *WritingHandler) GetWritingProgress(c *gin.Context) { userID, ok := utils.GetUserIDFromContext(c) if !ok { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"}) return } promptID := c.Param("prompt_id") progress, err := h.writingService.GetWritingProgress(utils.Int64ToString(userID), promptID) if err != nil { c.JSON(http.StatusNotFound, gin.H{ "error": "写作进度不存在", "details": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "获取写作进度成功", "data": progress, }) }