init
This commit is contained in:
449
serve/api/handlers/speaking_handler.go
Normal file
449
serve/api/handlers/speaking_handler.go
Normal file
@@ -0,0 +1,449 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user