feat: add study session APIs and per-user review isolation

This commit is contained in:
2026-02-27 11:14:25 +08:00
parent 60ce70f532
commit eb88494c5a
4 changed files with 126 additions and 24 deletions

View File

@@ -4,6 +4,7 @@ import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@@ -168,13 +169,13 @@ func (s *WordService) DownloadAudio(url, filePath string) error {
}
// 保存单词到数据库
func (s *WordService) SaveWord(word string, youdaoResp *model.YoudaoResponse) (*model.Word, error) {
func (s *WordService) SaveWord(userID int64, word string, youdaoResp *model.YoudaoResponse) (*model.Word, error) {
var existingWord model.Word
// 检查单词是否已存在
if err := s.db.Where("word = ?", word).First(&existingWord).Error; err == nil {
// 单词已存在,更新记忆记录
s.updateMemoryRecord(existingWord.ID, true)
s.updateMemoryRecord(userID, existingWord.ID, true)
return &existingWord, nil
}
@@ -271,17 +272,17 @@ func (s *WordService) SaveWord(word string, youdaoResp *model.YoudaoResponse) (*
}
// 创建记忆记录 + 记一次"背过"
_ = s.createMemoryRecord(newWord.ID)
_ = s.updateMemoryRecord(newWord.ID, true)
_ = s.createMemoryRecord(userID, newWord.ID)
_ = s.updateMemoryRecord(userID, newWord.ID, true)
return &newWord, nil
}
// 创建记忆记录
func (s *WordService) createMemoryRecord(wordID int64) error {
func (s *WordService) createMemoryRecord(userID, wordID int64) error {
record := model.MemoryRecord{
WordID: wordID,
UserID: 1,
UserID: userID,
CorrectCount: 0,
TotalCount: 0,
MasteryLevel: 0,
@@ -290,10 +291,19 @@ func (s *WordService) createMemoryRecord(wordID int64) error {
}
// 更新记忆记录
func (s *WordService) updateMemoryRecord(wordID int64, correct bool) error {
func (s *WordService) updateMemoryRecord(userID, wordID int64, correct bool) error {
var record model.MemoryRecord
if err := s.db.Where("word_id = ?", wordID).First(&record).Error; err != nil {
return err
if err := s.db.Where("word_id = ? AND user_id = ?", wordID, userID).First(&record).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
if createErr := s.createMemoryRecord(userID, wordID); createErr != nil {
return createErr
}
if err = s.db.Where("word_id = ? AND user_id = ?", wordID, userID).First(&record).Error; err != nil {
return err
}
} else {
return err
}
}
record.TotalCount++
@@ -317,10 +327,10 @@ func (s *WordService) updateMemoryRecord(wordID int64, correct bool) error {
}
// 获取待复习单词
func (s *WordService) GetReviewWords(mode string, limit int) ([]model.MemoryRecord, error) {
func (s *WordService) GetReviewWords(userID int64, mode string, limit int) ([]model.MemoryRecord, error) {
var records []model.MemoryRecord
query := s.db.Preload("Word").Where("next_review_at <= ? OR next_review_at IS NULL", time.Now())
query := s.db.Preload("Word").Where("user_id = ? AND (next_review_at <= ? OR next_review_at IS NULL)", userID, time.Now())
if err := query.Limit(limit).Find(&records).Error; err != nil {
return nil, err
@@ -328,7 +338,7 @@ func (s *WordService) GetReviewWords(mode string, limit int) ([]model.MemoryReco
// 如果没有需要复习的,随机获取一些
if len(records) == 0 {
if err := s.db.Preload("Word").Limit(limit).Find(&records).Error; err != nil {
if err := s.db.Preload("Word").Where("user_id = ?", userID).Limit(limit).Find(&records).Error; err != nil {
return nil, err
}
}
@@ -361,9 +371,9 @@ func containsAny(def string, ans string) bool {
}
// 提交复习答案
func (s *WordService) SubmitReviewAnswer(req request.ReviewAnswerRequest) (*response.ReviewResult, error) {
func (s *WordService) SubmitReviewAnswer(userID int64, req request.ReviewAnswerRequest) (*response.ReviewResult, error) {
var record model.MemoryRecord
if err := s.db.Preload("Word").Where("id = ?", req.RecordID).First(&record).Error; err != nil {
if err := s.db.Preload("Word").Where("id = ? AND user_id = ?", req.RecordID, userID).First(&record).Error; err != nil {
return nil, err
}
@@ -387,7 +397,7 @@ func (s *WordService) SubmitReviewAnswer(req request.ReviewAnswerRequest) (*resp
}
// 更新记忆记录
s.updateMemoryRecord(record.WordID, correct)
s.updateMemoryRecord(userID, record.WordID, correct)
return &response.ReviewResult{
Word: word,
@@ -412,16 +422,16 @@ func (s *WordService) GetAllWords(limit, offset int) ([]model.Word, int64, error
}
// 获取记忆统计
func (s *WordService) GetStatistics() (map[string]interface{}, error) {
func (s *WordService) GetStatistics(userID int64) (map[string]interface{}, error) {
var totalWords int64
var masteredWords int64
var needReview int64
var todayReviewed int64
s.db.Model(&model.Word{}).Count(&totalWords)
s.db.Model(&model.MemoryRecord{}).Where("mastery_level >= 4").Count(&masteredWords)
s.db.Model(&model.MemoryRecord{}).Where("next_review_at <= ?", time.Now()).Count(&needReview)
s.db.Model(&model.MemoryRecord{}).Where("last_reviewed_at >= ?", time.Now().Format("2006-01-02")).Count(&todayReviewed)
s.db.Model(&model.MemoryRecord{}).Where("user_id = ?", userID).Count(&totalWords)
s.db.Model(&model.MemoryRecord{}).Where("user_id = ? AND mastery_level >= 4", userID).Count(&masteredWords)
s.db.Model(&model.MemoryRecord{}).Where("user_id = ? AND next_review_at <= ?", userID, time.Now()).Count(&needReview)
s.db.Model(&model.MemoryRecord{}).Where("user_id = ? AND last_reviewed_at >= ?", userID, time.Now().Format("2006-01-02")).Count(&todayReviewed)
return map[string]interface{}{
"total_words": totalWords,
@@ -430,3 +440,42 @@ func (s *WordService) GetStatistics() (map[string]interface{}, error) {
"today_reviewed": todayReviewed,
}, nil
}
func (s *WordService) CreateStudySession(userID int64, limit int) ([]model.Word, error) {
if limit <= 0 || limit > 50 {
limit = 10
}
var words []model.Word
if err := s.db.Order("created_at DESC").Limit(limit).Find(&words).Error; err != nil {
return nil, err
}
for i := range words {
_ = s.createMemoryRecord(userID, words[i].ID)
}
return words, nil
}
func (s *WordService) SubmitStudyAnswer(userID int64, req request.SubmitStudyAnswerRequest) (*response.ReviewResult, error) {
reviewReq := request.ReviewAnswerRequest{
RecordID: 0,
Answer: req.Answer,
Mode: req.Mode,
}
var record model.MemoryRecord
if err := s.db.Where("user_id = ? AND word_id = ?", userID, req.WordID).First(&record).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
if createErr := s.createMemoryRecord(userID, req.WordID); createErr != nil {
return nil, createErr
}
if err = s.db.Where("user_id = ? AND word_id = ?", userID, req.WordID).First(&record).Error; err != nil {
return nil, err
}
} else {
return nil, err
}
}
reviewReq.RecordID = record.ID
return s.SubmitReviewAnswer(userID, reviewReq)
}