449 lines
13 KiB
Go
449 lines
13 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"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/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"
|
|
)
|
|
|
|
// SpeakingHandler 口语练习处理器
|
|
type SpeakingHandler struct {
|
|
speakingService *services.SpeakingService
|
|
}
|
|
|
|
// NewSpeakingHandler 创建口语练习处理器实例
|
|
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", "20"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 20
|
|
}
|
|
|
|
scenarios, total, err := h.speakingService.GetSpeakingScenarios(level, category, page, pageSize)
|
|
if err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "获取口语场景列表失败")
|
|
return
|
|
}
|
|
|
|
response := gin.H{
|
|
"scenarios": scenarios,
|
|
"pagination": gin.H{
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"total": total,
|
|
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
},
|
|
}
|
|
|
|
common.SuccessResponse(c, response)
|
|
}
|
|
|
|
// GetSpeakingScenario 获取单个口语场景
|
|
func (h *SpeakingHandler) GetSpeakingScenario(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "场景ID不能为空")
|
|
return
|
|
}
|
|
|
|
scenario, err := h.speakingService.GetSpeakingScenario(id)
|
|
if err != nil {
|
|
common.ErrorResponse(c, http.StatusNotFound, "口语场景不存在")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, scenario)
|
|
}
|
|
|
|
// CreateSpeakingScenarioRequest 创建口语场景请求
|
|
type CreateSpeakingScenarioRequest struct {
|
|
Title string `json:"title" binding:"required,max=200"`
|
|
Description string `json:"description" binding:"required"`
|
|
Context *string `json:"context"`
|
|
Level string `json:"level" binding:"required,oneof=beginner intermediate advanced"`
|
|
Category string `json:"category" binding:"max=50"`
|
|
Tags *string `json:"tags"`
|
|
Dialogue *string `json:"dialogue"`
|
|
KeyPhrases *string `json:"key_phrases"`
|
|
}
|
|
|
|
// CreateSpeakingScenario 创建口语场景
|
|
func (h *SpeakingHandler) CreateSpeakingScenario(c *gin.Context) {
|
|
var req CreateSpeakingScenarioRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "请求参数错误")
|
|
return
|
|
}
|
|
|
|
scenario := &models.SpeakingScenario{
|
|
ID: utils.GenerateUUID(),
|
|
Title: req.Title,
|
|
Description: req.Description,
|
|
Context: req.Context,
|
|
Level: req.Level,
|
|
Category: req.Category,
|
|
Tags: req.Tags,
|
|
Dialogue: req.Dialogue,
|
|
KeyPhrases: req.KeyPhrases,
|
|
IsActive: true,
|
|
}
|
|
|
|
if err := h.speakingService.CreateSpeakingScenario(scenario); err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "创建口语场景失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, scenario)
|
|
}
|
|
|
|
// UpdateSpeakingScenarioRequest 更新口语场景请求
|
|
type UpdateSpeakingScenarioRequest struct {
|
|
Title *string `json:"title" binding:"omitempty,max=200"`
|
|
Description *string `json:"description"`
|
|
Context *string `json:"context"`
|
|
Level *string `json:"level" binding:"omitempty,oneof=beginner intermediate advanced"`
|
|
Category *string `json:"category" binding:"omitempty,max=50"`
|
|
Tags *string `json:"tags"`
|
|
Dialogue *string `json:"dialogue"`
|
|
KeyPhrases *string `json:"key_phrases"`
|
|
}
|
|
|
|
// UpdateSpeakingScenario 更新口语场景
|
|
func (h *SpeakingHandler) UpdateSpeakingScenario(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "场景ID不能为空")
|
|
return
|
|
}
|
|
|
|
var req UpdateSpeakingScenarioRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "请求参数错误")
|
|
return
|
|
}
|
|
|
|
updateData := &models.SpeakingScenario{}
|
|
if req.Title != nil {
|
|
updateData.Title = *req.Title
|
|
}
|
|
if req.Description != nil {
|
|
updateData.Description = *req.Description
|
|
}
|
|
if req.Context != nil {
|
|
updateData.Context = req.Context
|
|
}
|
|
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.Dialogue != nil {
|
|
updateData.Dialogue = req.Dialogue
|
|
}
|
|
if req.KeyPhrases != nil {
|
|
updateData.KeyPhrases = req.KeyPhrases
|
|
}
|
|
|
|
if err := h.speakingService.UpdateSpeakingScenario(id, updateData); err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "更新口语场景失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, gin.H{"message": "更新成功"})
|
|
}
|
|
|
|
// DeleteSpeakingScenario 删除口语场景
|
|
func (h *SpeakingHandler) DeleteSpeakingScenario(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "场景ID不能为空")
|
|
return
|
|
}
|
|
|
|
if err := h.speakingService.DeleteSpeakingScenario(id); err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "删除口语场景失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, gin.H{"message": "删除成功"})
|
|
}
|
|
|
|
// 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", "20"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 20
|
|
}
|
|
|
|
scenarios, total, err := h.speakingService.SearchSpeakingScenarios(keyword, level, category, page, pageSize)
|
|
if err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "搜索口语场景失败")
|
|
return
|
|
}
|
|
|
|
response := gin.H{
|
|
"scenarios": scenarios,
|
|
"pagination": gin.H{
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"total": total,
|
|
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
},
|
|
}
|
|
|
|
common.SuccessResponse(c, response)
|
|
}
|
|
|
|
// GetRecommendedScenarios 获取推荐的口语场景
|
|
func (h *SpeakingHandler) GetRecommendedScenarios(c *gin.Context) {
|
|
userIDInt, exists := utils.GetUserIDFromContext(c)
|
|
if !exists {
|
|
common.ErrorResponse(c, http.StatusUnauthorized, "用户未认证")
|
|
return
|
|
}
|
|
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
|
if limit < 1 || limit > 50 {
|
|
limit = 10
|
|
}
|
|
|
|
userID := utils.Int64ToString(userIDInt)
|
|
scenarios, err := h.speakingService.GetRecommendedScenarios(userID, limit)
|
|
if err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "获取推荐场景失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, scenarios)
|
|
}
|
|
|
|
// ==================== 口语练习记录管理 ====================
|
|
|
|
// CreateSpeakingRecordRequest 创建口语练习记录请求
|
|
type CreateSpeakingRecordRequest struct {
|
|
ScenarioID string `json:"scenario_id" binding:"required"`
|
|
}
|
|
|
|
// CreateSpeakingRecord 创建口语练习记录
|
|
func (h *SpeakingHandler) CreateSpeakingRecord(c *gin.Context) {
|
|
userIDInt, exists := utils.GetUserIDFromContext(c)
|
|
if !exists {
|
|
common.ErrorResponse(c, http.StatusUnauthorized, "用户未认证")
|
|
return
|
|
}
|
|
|
|
var req CreateSpeakingRecordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "请求参数错误")
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
record := &models.SpeakingRecord{
|
|
ID: utils.GenerateUUID(),
|
|
UserID: utils.Int64ToString(userIDInt),
|
|
ScenarioID: req.ScenarioID,
|
|
StartedAt: now,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
if err := h.speakingService.CreateSpeakingRecord(record); err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "创建口语练习记录失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, record)
|
|
}
|
|
|
|
// GetUserSpeakingRecords 获取用户的口语练习记录
|
|
func (h *SpeakingHandler) GetUserSpeakingRecords(c *gin.Context) {
|
|
userIDInt, exists := utils.GetUserIDFromContext(c)
|
|
if !exists {
|
|
common.ErrorResponse(c, http.StatusUnauthorized, "用户未认证")
|
|
return
|
|
}
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 20
|
|
}
|
|
|
|
userID := utils.Int64ToString(userIDInt)
|
|
records, total, err := h.speakingService.GetUserSpeakingRecords(userID, page, pageSize)
|
|
if err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "获取口语练习记录失败")
|
|
return
|
|
}
|
|
|
|
response := gin.H{
|
|
"records": records,
|
|
"pagination": gin.H{
|
|
"page": page,
|
|
"page_size": pageSize,
|
|
"total": total,
|
|
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
},
|
|
}
|
|
|
|
common.SuccessResponse(c, response)
|
|
}
|
|
|
|
// GetSpeakingRecord 获取单个口语练习记录
|
|
func (h *SpeakingHandler) GetSpeakingRecord(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "记录ID不能为空")
|
|
return
|
|
}
|
|
|
|
record, err := h.speakingService.GetSpeakingRecord(id)
|
|
if err != nil {
|
|
common.ErrorResponse(c, http.StatusNotFound, "口语练习记录不存在")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, record)
|
|
}
|
|
|
|
// SubmitSpeakingRequest 提交口语练习请求
|
|
type SubmitSpeakingRequest struct {
|
|
AudioURL string `json:"audio_url" binding:"required"`
|
|
Transcript string `json:"transcript"`
|
|
}
|
|
|
|
// SubmitSpeaking 提交口语练习
|
|
func (h *SpeakingHandler) SubmitSpeaking(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "记录ID不能为空")
|
|
return
|
|
}
|
|
|
|
var req SubmitSpeakingRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "请求参数错误")
|
|
return
|
|
}
|
|
|
|
if err := h.speakingService.SubmitSpeaking(id, req.AudioURL, req.Transcript); err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "提交口语练习失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, gin.H{"message": "提交成功"})
|
|
}
|
|
|
|
// GradeSpeakingRequest 评分口语练习请求
|
|
type GradeSpeakingRequest struct {
|
|
PronunciationScore float64 `json:"pronunciation_score" binding:"required,min=0,max=100"`
|
|
FluencyScore float64 `json:"fluency_score" binding:"required,min=0,max=100"`
|
|
AccuracyScore float64 `json:"accuracy_score" binding:"required,min=0,max=100"`
|
|
OverallScore float64 `json:"overall_score" binding:"required,min=0,max=100"`
|
|
Feedback string `json:"feedback"`
|
|
Suggestions string `json:"suggestions"`
|
|
}
|
|
|
|
// GradeSpeaking 评分口语练习
|
|
func (h *SpeakingHandler) GradeSpeaking(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if id == "" {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "记录ID不能为空")
|
|
return
|
|
}
|
|
|
|
var req GradeSpeakingRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "请求参数错误")
|
|
return
|
|
}
|
|
|
|
if err := h.speakingService.GradeSpeaking(id, req.PronunciationScore, req.FluencyScore, req.AccuracyScore, req.OverallScore, req.Feedback, req.Suggestions); err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "评分口语练习失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, gin.H{"message": "评分成功"})
|
|
}
|
|
|
|
// ==================== 口语学习统计和进度 ====================
|
|
|
|
// GetSpeakingStats 获取口语学习统计
|
|
func (h *SpeakingHandler) GetSpeakingStats(c *gin.Context) {
|
|
userIDInt, exists := utils.GetUserIDFromContext(c)
|
|
if !exists {
|
|
common.ErrorResponse(c, http.StatusUnauthorized, "用户未认证")
|
|
return
|
|
}
|
|
|
|
userID := utils.Int64ToString(userIDInt)
|
|
stats, err := h.speakingService.GetUserSpeakingStats(userID)
|
|
if err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "获取口语学习统计失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, stats)
|
|
}
|
|
|
|
// GetSpeakingProgress 获取口语学习进度
|
|
func (h *SpeakingHandler) GetSpeakingProgress(c *gin.Context) {
|
|
userIDInt, exists := utils.GetUserIDFromContext(c)
|
|
if !exists {
|
|
common.ErrorResponse(c, http.StatusUnauthorized, "用户未认证")
|
|
return
|
|
}
|
|
|
|
scenarioID := c.Param("scenario_id")
|
|
if scenarioID == "" {
|
|
common.ErrorResponse(c, http.StatusBadRequest, "场景ID不能为空")
|
|
return
|
|
}
|
|
|
|
userID := utils.Int64ToString(userIDInt)
|
|
progress, err := h.speakingService.GetSpeakingProgress(userID, scenarioID)
|
|
if err != nil {
|
|
common.ErrorResponse(c, http.StatusInternalServerError, "获取口语学习进度失败")
|
|
return
|
|
}
|
|
|
|
common.SuccessResponse(c, progress)
|
|
} |