639 lines
17 KiB
Go
639 lines
17 KiB
Go
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"
|
|
)
|
|
|
|
// ReadingHandler 阅读理解处理器
|
|
type ReadingHandler struct {
|
|
readingService *services.ReadingService
|
|
}
|
|
|
|
// NewReadingHandler 创建阅读理解处理器实例
|
|
func NewReadingHandler(readingService *services.ReadingService) *ReadingHandler {
|
|
return &ReadingHandler{
|
|
readingService: readingService,
|
|
}
|
|
}
|
|
|
|
// ===== 请求结构体定义 =====
|
|
|
|
// CreateReadingMaterialRequest 创建阅读材料请求
|
|
type CreateReadingMaterialRequest struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Content string `json:"content" binding:"required"`
|
|
Summary string `json:"summary"`
|
|
Level string `json:"level" binding:"required,oneof=beginner intermediate advanced"`
|
|
Category string `json:"category" binding:"required"`
|
|
WordCount int `json:"word_count"`
|
|
Tags string `json:"tags"`
|
|
Source string `json:"source"`
|
|
Author string `json:"author"`
|
|
}
|
|
|
|
// UpdateReadingMaterialRequest 更新阅读材料请求
|
|
type UpdateReadingMaterialRequest struct {
|
|
Title *string `json:"title"`
|
|
Content *string `json:"content"`
|
|
Summary *string `json:"summary"`
|
|
Level *string `json:"level"`
|
|
Category *string `json:"category"`
|
|
WordCount *int `json:"word_count"`
|
|
Tags *string `json:"tags"`
|
|
Source *string `json:"source"`
|
|
Author *string `json:"author"`
|
|
}
|
|
|
|
// CreateReadingRecordRequest 创建阅读记录请求
|
|
type CreateReadingRecordRequest struct {
|
|
MaterialID string `json:"material_id" binding:"required"`
|
|
}
|
|
|
|
// UpdateReadingRecordRequest 更新阅读记录请求
|
|
type UpdateReadingRecordRequest struct {
|
|
ReadingTime *int `json:"reading_time"`
|
|
ComprehensionScore *float64 `json:"comprehension_score"`
|
|
ReadingSpeed *float64 `json:"reading_speed"`
|
|
Notes *string `json:"notes"`
|
|
CompletedAt *time.Time `json:"completed_at"`
|
|
}
|
|
|
|
// ===== 阅读材料管理接口 =====
|
|
|
|
// GetReadingMaterials 获取阅读材料列表
|
|
// @Summary 获取阅读材料列表
|
|
// @Description 获取阅读材料列表,支持按难度级别和分类筛选
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param level query string false "难度级别"
|
|
// @Param category query string false "分类"
|
|
// @Param page query int false "页码" default(1)
|
|
// @Param page_size query int false "每页数量" default(10)
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/materials [get]
|
|
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"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 10
|
|
}
|
|
|
|
materials, total, err := h.readingService.GetReadingMaterials(level, category, page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "获取阅读材料失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"data": materials,
|
|
"pagination": gin.H{
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"total": total,
|
|
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetReadingMaterial 获取单个阅读材料
|
|
// @Summary 获取单个阅读材料
|
|
// @Description 根据ID获取阅读材料详情
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "材料ID"
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/materials/{id} [get]
|
|
func (h *ReadingHandler) GetReadingMaterial(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "材料ID不能为空"})
|
|
return
|
|
}
|
|
|
|
material, err := h.readingService.GetReadingMaterial(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"error": "阅读材料不存在",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": material})
|
|
}
|
|
|
|
// CreateReadingMaterial 创建阅读材料
|
|
// @Summary 创建阅读材料
|
|
// @Description 创建新的阅读材料
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body CreateReadingMaterialRequest true "创建请求"
|
|
// @Success 201 {object} Response
|
|
// @Router /reading/materials [post]
|
|
func (h *ReadingHandler) CreateReadingMaterial(c *gin.Context) {
|
|
var req CreateReadingMaterialRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "请求参数错误",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
material := &models.ReadingMaterial{
|
|
Title: req.Title,
|
|
Content: req.Content,
|
|
Summary: &req.Summary,
|
|
WordCount: req.WordCount,
|
|
Level: req.Level,
|
|
Category: req.Category,
|
|
Tags: &req.Tags,
|
|
Source: &req.Source,
|
|
Author: &req.Author,
|
|
IsActive: true,
|
|
}
|
|
|
|
if err := h.readingService.CreateReadingMaterial(material); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "创建阅读材料失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"message": "阅读材料创建成功",
|
|
"data": material,
|
|
})
|
|
}
|
|
|
|
// UpdateReadingMaterial 更新阅读材料
|
|
// @Summary 更新阅读材料
|
|
// @Description 更新阅读材料信息
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "材料ID"
|
|
// @Param request body UpdateReadingMaterialRequest true "更新请求"
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/materials/{id} [put]
|
|
func (h *ReadingHandler) UpdateReadingMaterial(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "材料ID不能为空"})
|
|
return
|
|
}
|
|
|
|
var req UpdateReadingMaterialRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "请求参数错误",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
updates := make(map[string]interface{})
|
|
if req.Title != nil {
|
|
updates["title"] = *req.Title
|
|
}
|
|
if req.Content != nil {
|
|
updates["content"] = *req.Content
|
|
}
|
|
if req.Summary != nil {
|
|
updates["summary"] = *req.Summary
|
|
}
|
|
if req.Level != nil {
|
|
updates["level"] = *req.Level
|
|
}
|
|
if req.Category != nil {
|
|
updates["category"] = *req.Category
|
|
}
|
|
if req.WordCount != nil {
|
|
updates["word_count"] = *req.WordCount
|
|
}
|
|
if req.Tags != nil {
|
|
updates["tags"] = *req.Tags
|
|
}
|
|
if req.Source != nil {
|
|
updates["source"] = *req.Source
|
|
}
|
|
if req.Author != nil {
|
|
updates["author"] = *req.Author
|
|
}
|
|
|
|
if len(updates) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "没有提供更新字段"})
|
|
return
|
|
}
|
|
|
|
if err := h.readingService.UpdateReadingMaterial(id, updates); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "更新阅读材料失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "阅读材料更新成功"})
|
|
}
|
|
|
|
// DeleteReadingMaterial 删除阅读材料
|
|
// @Summary 删除阅读材料
|
|
// @Description 软删除阅读材料
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "材料ID"
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/materials/{id} [delete]
|
|
func (h *ReadingHandler) DeleteReadingMaterial(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "材料ID不能为空"})
|
|
return
|
|
}
|
|
|
|
if err := h.readingService.DeleteReadingMaterial(id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "删除阅读材料失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "阅读材料删除成功"})
|
|
}
|
|
|
|
// SearchReadingMaterials 搜索阅读材料
|
|
// @Summary 搜索阅读材料
|
|
// @Description 根据关键词搜索阅读材料
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param keyword query string true "搜索关键词"
|
|
// @Param level query string false "难度级别"
|
|
// @Param category query string false "分类"
|
|
// @Param page query int false "页码" default(1)
|
|
// @Param page_size query int false "每页数量" default(10)
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/materials/search [get]
|
|
func (h *ReadingHandler) SearchReadingMaterials(c *gin.Context) {
|
|
keyword := c.Query("keyword")
|
|
if keyword == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "搜索关键词不能为空"})
|
|
return
|
|
}
|
|
|
|
level := c.Query("level")
|
|
category := c.Query("category")
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 10
|
|
}
|
|
|
|
materials, total, err := h.readingService.SearchReadingMaterials(keyword, level, category, page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "搜索阅读材料失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"data": materials,
|
|
"pagination": gin.H{
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"total": total,
|
|
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
},
|
|
})
|
|
}
|
|
|
|
// ===== 阅读记录管理接口 =====
|
|
|
|
// CreateReadingRecord 创建阅读记录
|
|
// @Summary 创建阅读记录
|
|
// @Description 开始阅读材料,创建阅读记录
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body CreateReadingRecordRequest true "创建请求"
|
|
// @Success 201 {object} Response
|
|
// @Router /reading/records [post]
|
|
func (h *ReadingHandler) CreateReadingRecord(c *gin.Context) {
|
|
userID, ok := utils.GetUserIDFromContext(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"})
|
|
return
|
|
}
|
|
|
|
var req CreateReadingRecordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "请求参数错误",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// 检查是否已有该材料的阅读记录
|
|
existingRecord, err := h.readingService.GetReadingProgress(utils.Int64ToString(userID), req.MaterialID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "检查阅读记录失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if existingRecord != nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "阅读记录已存在",
|
|
"data": existingRecord,
|
|
})
|
|
return
|
|
}
|
|
|
|
record := &models.ReadingRecord{
|
|
UserID: utils.Int64ToString(userID),
|
|
MaterialID: req.MaterialID,
|
|
}
|
|
|
|
if err := h.readingService.CreateReadingRecord(record); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "创建阅读记录失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"message": "阅读记录创建成功",
|
|
"data": record,
|
|
})
|
|
}
|
|
|
|
// UpdateReadingRecord 更新阅读记录
|
|
// @Summary 更新阅读记录
|
|
// @Description 更新阅读进度和成绩
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "记录ID"
|
|
// @Param request body UpdateReadingRecordRequest true "更新请求"
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/records/{id} [put]
|
|
func (h *ReadingHandler) UpdateReadingRecord(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "记录ID不能为空"})
|
|
return
|
|
}
|
|
|
|
var req UpdateReadingRecordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "请求参数错误",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
updates := make(map[string]interface{})
|
|
if req.ReadingTime != nil {
|
|
updates["reading_time"] = *req.ReadingTime
|
|
}
|
|
if req.ComprehensionScore != nil {
|
|
updates["comprehension_score"] = *req.ComprehensionScore
|
|
}
|
|
if req.ReadingSpeed != nil {
|
|
updates["reading_speed"] = *req.ReadingSpeed
|
|
}
|
|
if req.Notes != nil {
|
|
updates["notes"] = *req.Notes
|
|
}
|
|
if req.CompletedAt != nil {
|
|
updates["completed_at"] = *req.CompletedAt
|
|
}
|
|
|
|
if len(updates) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "没有提供更新字段"})
|
|
return
|
|
}
|
|
|
|
if err := h.readingService.UpdateReadingRecord(id, updates); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "更新阅读记录失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "阅读记录更新成功"})
|
|
}
|
|
|
|
// GetUserReadingRecords 获取用户阅读记录
|
|
// @Summary 获取用户阅读记录
|
|
// @Description 获取当前用户的阅读记录列表
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param page query int false "页码" default(1)
|
|
// @Param page_size query int false "每页数量" default(10)
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/records [get]
|
|
func (h *ReadingHandler) GetUserReadingRecords(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"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 10
|
|
}
|
|
|
|
records, total, err := h.readingService.GetUserReadingRecords(utils.Int64ToString(userID), page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "获取阅读记录失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"data": records,
|
|
"pagination": gin.H{
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"total": total,
|
|
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetReadingRecord 获取单个阅读记录
|
|
// @Summary 获取单个阅读记录
|
|
// @Description 根据ID获取阅读记录详情
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "记录ID"
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/records/{id} [get]
|
|
func (h *ReadingHandler) GetReadingRecord(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "记录ID不能为空"})
|
|
return
|
|
}
|
|
|
|
record, err := h.readingService.GetReadingRecord(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"error": "阅读记录不存在",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": record})
|
|
}
|
|
|
|
// GetReadingProgress 获取阅读进度
|
|
// @Summary 获取阅读进度
|
|
// @Description 获取用户对特定材料的阅读进度
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param material_id path string true "材料ID"
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/progress/{material_id} [get]
|
|
func (h *ReadingHandler) GetReadingProgress(c *gin.Context) {
|
|
userID, ok := utils.GetUserIDFromContext(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"})
|
|
return
|
|
}
|
|
|
|
materialID := c.Param("material_id")
|
|
if materialID == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "材料ID不能为空"})
|
|
return
|
|
}
|
|
|
|
record, err := h.readingService.GetReadingProgress(utils.Int64ToString(userID), materialID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "获取阅读进度失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if record == nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"data": nil,
|
|
"message": "暂无阅读记录",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": record})
|
|
}
|
|
|
|
// ===== 阅读统计接口 =====
|
|
|
|
// GetReadingStats 获取阅读统计
|
|
// @Summary 获取阅读统计
|
|
// @Description 获取用户阅读统计信息
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/stats [get]
|
|
func (h *ReadingHandler) GetReadingStats(c *gin.Context) {
|
|
userID, ok := utils.GetUserIDFromContext(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户未认证"})
|
|
return
|
|
}
|
|
|
|
stats, err := h.readingService.GetUserReadingStats(utils.Int64ToString(userID))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "获取阅读统计失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": stats})
|
|
}
|
|
|
|
// GetRecommendedMaterials 获取推荐阅读材料
|
|
// @Summary 获取推荐阅读材料
|
|
// @Description 根据用户阅读历史推荐合适的阅读材料
|
|
// @Tags 阅读理解
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param limit query int false "推荐数量" default(5)
|
|
// @Success 200 {object} Response
|
|
// @Router /reading/recommendations [get]
|
|
func (h *ReadingHandler) GetRecommendedMaterials(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"))
|
|
if limit < 1 || limit > 20 {
|
|
limit = 5
|
|
}
|
|
|
|
materials, err := h.readingService.GetRecommendedMaterials(utils.Int64ToString(userID), limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "获取推荐材料失败",
|
|
"details": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": materials})
|
|
} |