Files
memora/memora-api/internal/handler/word.go
wsy182 613ce02e9a feat(layout): 更新应用布局和UI组件样式
- 重构App.vue中的侧边栏布局,更新Logo设计为带有标识和副标题的新样式
- 调整顶部导航栏,增加标题区域显示当前路由标题和日期
- 修改菜单项配置,更新导航标签为更直观的中文描述
- 在Home.vue中替换原有的仪表板为新的Hero卡片和项目进展展示
- 更新Memory.vue中的学习界面,添加学习计划设置和多阶段学习模式
- 集成新的API端点路径,将baseURL从/api调整为/api/v1
- 调整整体视觉风格,包括颜色主题、字体家族和响应式布局
- 更新数据库模型以支持词库功能,添加相关的数据迁移和种子数据
- 调整认证系统的用户ID类型从整型到字符串的变更
- 更改前端构建工具从npm到pnpm,并更新相应的Dockerfile配置
2026-02-27 16:16:57 +08:00

196 lines
4.7 KiB
Go

package handler
import (
"fmt"
"net/http"
"strconv"
"strings"
"memora-api/internal/request"
"memora-api/internal/service"
"github.com/gin-gonic/gin"
)
type WordHandler struct {
wordService *service.WordService
}
func userIDFromContext(c *gin.Context) string {
v, ok := c.Get("user_id")
if !ok {
return ""
}
uid, ok := v.(string)
if !ok {
return ""
}
return uid
}
func NewWordHandler(wordService *service.WordService) *WordHandler {
return &WordHandler{wordService: wordService}
}
// 添加单词(记忆模式)
func (h *WordHandler) AddWord(c *gin.Context) {
var req request.AddWordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
youdaoResp, err := h.wordService.QueryWord(req.Word)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败: " + err.Error()})
return
}
if youdaoResp.ErrorCode != "0" && youdaoResp.ErrorCode != "" {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("有道API错误: %s", youdaoResp.ErrorCode)})
return
}
word, err := h.wordService.SaveWord(userIDFromContext(c), req.Word, youdaoResp)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "单词添加成功",
"data": word,
})
}
// 获取复习单词列表
func (h *WordHandler) GetReviewWords(c *gin.Context) {
mode := c.DefaultQuery("mode", "spelling")
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
words, err := h.wordService.GetReviewWords(userIDFromContext(c), mode, limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": words})
}
// 提交复习答案
func (h *WordHandler) SubmitReview(c *gin.Context) {
var req request.ReviewAnswerRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
result, err := h.wordService.SubmitReviewAnswer(userIDFromContext(c), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": result})
}
// 获取所有单词
func (h *WordHandler) GetWords(c *gin.Context) {
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
query := strings.TrimSpace(c.DefaultQuery("q", ""))
words, total, err := h.wordService.GetAllWords(limit, offset, query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"data": words,
"total": total,
})
}
func (h *WordHandler) GetWordByID(c *gin.Context) {
id := strings.TrimSpace(c.Param("id"))
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "非法单词ID"})
return
}
word, err := h.wordService.GetWordByID(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "单词不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"data": word})
}
// 获取统计信息
func (h *WordHandler) GetStatistics(c *gin.Context) {
stats, err := h.wordService.GetStatistics(userIDFromContext(c))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": stats})
}
// 获取音频文件
func (h *WordHandler) GetAudio(c *gin.Context) {
word := c.Query("word")
audioType := c.DefaultQuery("type", "uk")
if word == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少word参数"})
return
}
word = strings.TrimSpace(word)
if word == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "word为空"})
return
}
for _, r := range word {
if !(r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z') {
c.JSON(http.StatusBadRequest, gin.H{"error": "word包含非法字符"})
return
}
}
filename := word + "_" + audioType + ".mp3"
path := "./audio/" + filename
c.File(path)
}
func (h *WordHandler) CreateStudySession(c *gin.Context) {
var req request.CreateStudySessionRequest
_ = c.ShouldBindJSON(&req)
words, err := h.wordService.CreateStudySession(userIDFromContext(c), req.Limit)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": words})
}
func (h *WordHandler) SubmitStudyAnswer(c *gin.Context) {
var req request.SubmitStudyAnswerRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
result, err := h.wordService.SubmitStudyAnswer(userIDFromContext(c), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": result})
}