1318 lines
39 KiB
Go
1318 lines
39 KiB
Go
package services
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"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/interfaces"
|
||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
|
||
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/utils"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type VocabularyService struct {
|
||
db *gorm.DB
|
||
}
|
||
|
||
func NewVocabularyService(db *gorm.DB) *VocabularyService {
|
||
return &VocabularyService{db: db}
|
||
}
|
||
|
||
// 显式实现 VocabularyServiceInterface 接口
|
||
var _ interfaces.VocabularyServiceInterface = (*VocabularyService)(nil)
|
||
|
||
// GetCategories 获取词汇分类列表
|
||
func (s *VocabularyService) GetCategories(page, pageSize int, level string) (*common.PaginationData, error) {
|
||
var categories []*models.VocabularyCategory
|
||
var total int64
|
||
|
||
query := s.db.Model(&models.VocabularyCategory{})
|
||
if level != "" {
|
||
query = query.Where("level = ?", level)
|
||
}
|
||
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
offset := utils.CalculateOffset(page, pageSize)
|
||
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&categories).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
totalPages := utils.CalculateTotalPages(int(total), pageSize)
|
||
|
||
return &common.PaginationData{
|
||
Items: categories,
|
||
Pagination: &common.Pagination{
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
Total: int(total),
|
||
TotalPages: totalPages,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// CreateCategory 创建词汇分类
|
||
func (s *VocabularyService) CreateCategory(name, description, level string) (*models.VocabularyCategory, error) {
|
||
// 检查分类名称是否已存在
|
||
var existingCategory models.VocabularyCategory
|
||
if err := s.db.Where("name = ?", name).First(&existingCategory).Error; err == nil {
|
||
return nil, common.NewBusinessError(common.ErrCodeUserExists, "分类名称已存在")
|
||
} else if err != gorm.ErrRecordNotFound {
|
||
return nil, err
|
||
}
|
||
|
||
category := &models.VocabularyCategory{
|
||
ID: utils.GenerateUUID(),
|
||
Name: name,
|
||
Description: &description,
|
||
Level: level,
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
if err := s.db.Create(category).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return category, nil
|
||
}
|
||
|
||
// UpdateCategory 更新词汇分类
|
||
func (s *VocabularyService) UpdateCategory(categoryID string, updates map[string]interface{}) (*models.VocabularyCategory, error) {
|
||
var category models.VocabularyCategory
|
||
if err := s.db.Where("id = ?", categoryID).First(&category).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
return nil, common.NewBusinessError(common.ErrCodeCategoryNotFound, "分类不存在")
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// 如果更新名称,检查是否重复
|
||
if name, ok := updates["name"]; ok {
|
||
var existingCategory models.VocabularyCategory
|
||
if err := s.db.Where("name = ? AND id != ?", name, categoryID).First(&existingCategory).Error; err == nil {
|
||
return nil, common.NewBusinessError(common.ErrCodeUserExists, "分类名称已存在")
|
||
} else if err != gorm.ErrRecordNotFound {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
updates["updated_at"] = time.Now()
|
||
if err := s.db.Model(&category).Updates(updates).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &category, nil
|
||
}
|
||
|
||
// DeleteCategory 删除词汇分类
|
||
func (s *VocabularyService) DeleteCategory(categoryID string) error {
|
||
// 检查分类是否存在
|
||
var category models.VocabularyCategory
|
||
if err := s.db.Where("id = ?", categoryID).First(&category).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
return common.NewBusinessError(common.ErrCodeCategoryNotFound, "分类不存在")
|
||
}
|
||
return err
|
||
}
|
||
|
||
// 检查是否有词汇使用该分类
|
||
var count int64
|
||
if err := s.db.Model(&models.Vocabulary{}).Where("category_id = ?", categoryID).Count(&count).Error; err != nil {
|
||
return err
|
||
}
|
||
if count > 0 {
|
||
return common.NewBusinessError(common.ErrCodeBadRequest, "该分类下还有词汇,无法删除")
|
||
}
|
||
|
||
if err := s.db.Delete(&category).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetVocabulariesByCategory 根据分类获取词汇列表
|
||
func (s *VocabularyService) GetVocabulariesByCategory(categoryID string, page, pageSize int, level string) (*common.PaginationData, error) {
|
||
offset := utils.CalculateOffset(page, pageSize)
|
||
|
||
query := s.db.Where("category_id = ?", categoryID)
|
||
if level != "" {
|
||
query = query.Where("level = ?", level)
|
||
}
|
||
|
||
// 获取总数
|
||
var total int64
|
||
if err := query.Model(&models.Vocabulary{}).Count(&total).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 获取词汇列表
|
||
var vocabularies []models.Vocabulary
|
||
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&vocabularies).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
totalPages := utils.CalculateTotalPages(int(total), pageSize)
|
||
|
||
return &common.PaginationData{
|
||
Items: vocabularies,
|
||
Pagination: &common.Pagination{
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
Total: int(total),
|
||
TotalPages: totalPages,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// GetVocabularyByID 根据ID获取词汇详情
|
||
func (s *VocabularyService) GetVocabularyByID(vocabularyID string) (*models.Vocabulary, error) {
|
||
var vocabulary models.Vocabulary
|
||
if err := s.db.Where("id = ?", vocabularyID).First(&vocabulary).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
return nil, common.NewBusinessError(common.ErrCodeVocabularyNotFound, "词汇不存在")
|
||
}
|
||
return nil, err
|
||
}
|
||
return &vocabulary, nil
|
||
}
|
||
|
||
// CreateVocabulary 创建词汇
|
||
func (s *VocabularyService) CreateVocabulary(word, phonetic, level string, frequency int, categoryID string, definitions, examples, images []string) (*models.Vocabulary, error) {
|
||
// 检查词汇是否已存在
|
||
var existingVocabulary models.Vocabulary
|
||
if err := s.db.Where("word = ?", word).First(&existingVocabulary).Error; err == nil {
|
||
return nil, common.NewBusinessError(common.ErrCodeWordExists, "词汇已存在")
|
||
} else if err != gorm.ErrRecordNotFound {
|
||
return nil, err
|
||
}
|
||
|
||
// 检查分类是否存在
|
||
var category models.VocabularyCategory
|
||
if err := s.db.Where("id = ?", categoryID).First(&category).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
return nil, common.NewBusinessError(common.ErrCodeCategoryNotFound, "分类不存在")
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// 创建词汇
|
||
vocabulary := &models.Vocabulary{
|
||
Word: word,
|
||
Level: level,
|
||
Frequency: frequency,
|
||
IsActive: true,
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
// 设置音标(可选)
|
||
if phonetic != "" {
|
||
vocabulary.Phonetic = &phonetic
|
||
}
|
||
|
||
// 开始事务
|
||
tx := s.db.Begin()
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
tx.Rollback()
|
||
}
|
||
}()
|
||
|
||
// 创建词汇记录
|
||
if err := tx.Create(vocabulary).Error; err != nil {
|
||
tx.Rollback()
|
||
return nil, err
|
||
}
|
||
|
||
// 关联分类
|
||
if err := tx.Model(vocabulary).Association("Categories").Append(&category); err != nil {
|
||
tx.Rollback()
|
||
return nil, err
|
||
}
|
||
|
||
// 创建定义
|
||
for i, def := range definitions {
|
||
definition := &models.VocabularyDefinition{
|
||
VocabularyID: vocabulary.ID,
|
||
PartOfSpeech: "noun", // 默认词性
|
||
Definition: def,
|
||
Translation: def, // 暂时用定义作为翻译
|
||
SortOrder: i,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
if err := tx.Create(definition).Error; err != nil {
|
||
tx.Rollback()
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// 创建例句
|
||
for i, ex := range examples {
|
||
example := &models.VocabularyExample{
|
||
VocabularyID: vocabulary.ID,
|
||
Example: ex,
|
||
Translation: ex, // 暂时用例句作为翻译
|
||
SortOrder: i,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
if err := tx.Create(example).Error; err != nil {
|
||
tx.Rollback()
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// 创建图片(暂时跳过,因为VocabularyImage结构需要更新)
|
||
_ = images // 避免未使用警告
|
||
/*
|
||
for i, img := range images {
|
||
image := &models.VocabularyImage{
|
||
VocabularyID: vocabulary.ID,
|
||
ImageURL: img,
|
||
SortOrder: i,
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
if err := tx.Create(image).Error; err != nil {
|
||
tx.Rollback()
|
||
return nil, err
|
||
}
|
||
}
|
||
*/
|
||
|
||
// 提交事务
|
||
if err := tx.Commit().Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return vocabulary, nil
|
||
}
|
||
|
||
// UpdateVocabulary 更新词汇
|
||
func (s *VocabularyService) UpdateVocabulary(id string, vocabulary *models.Vocabulary) error {
|
||
return s.db.Model(&models.Vocabulary{}).Where("id = ?", id).Updates(vocabulary).Error
|
||
}
|
||
|
||
// DeleteVocabulary 删除词汇
|
||
func (s *VocabularyService) DeleteVocabulary(id string) error {
|
||
return s.db.Delete(&models.Vocabulary{}, id).Error
|
||
}
|
||
|
||
// GetUserVocabularyProgress 获取用户词汇学习进度
|
||
func (s *VocabularyService) GetUserVocabularyProgress(userID int64, vocabularyID string) (*models.UserVocabularyProgress, error) {
|
||
var progress models.UserVocabularyProgress
|
||
if err := s.db.Where("user_id = ? AND vocabulary_id = ?", userID, vocabularyID).First(&progress).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
return nil, common.NewBusinessError(common.ErrCodeProgressNotFound, "学习进度不存在")
|
||
}
|
||
return nil, err
|
||
}
|
||
return &progress, nil
|
||
}
|
||
|
||
// UpdateUserVocabularyProgress 更新用户词汇学习进度
|
||
func (s *VocabularyService) UpdateUserVocabularyProgress(userID int64, vocabularyID string, updates map[string]interface{}) (*models.UserVocabularyProgress, error) {
|
||
// 查找或创建进度记录
|
||
var progress models.UserVocabularyProgress
|
||
err := s.db.Where("user_id = ? AND vocabulary_id = ?", userID, vocabularyID).First(&progress).Error
|
||
|
||
if err == gorm.ErrRecordNotFound {
|
||
// 创建新的进度记录
|
||
now := time.Now()
|
||
progress = models.UserVocabularyProgress{
|
||
ID: 0, // 让数据库自动生成
|
||
UserID: userID,
|
||
VocabularyID: vocabularyID,
|
||
StudyCount: 1,
|
||
LastStudiedAt: &now,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
}
|
||
|
||
// 应用更新
|
||
if masteryLevel, ok := updates["mastery_level"].(int); ok {
|
||
progress.MasteryLevel = masteryLevel
|
||
}
|
||
|
||
if err := s.db.Create(&progress).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &progress, nil
|
||
} else if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 更新现有进度记录
|
||
now := time.Now()
|
||
updateData := map[string]interface{}{
|
||
"study_count": progress.StudyCount + 1,
|
||
"last_studied_at": &now,
|
||
"updated_at": now,
|
||
}
|
||
|
||
// 合并传入的更新数据
|
||
for key, value := range updates {
|
||
updateData[key] = value
|
||
}
|
||
|
||
if err := s.db.Model(&progress).Updates(updateData).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 重新查询更新后的记录
|
||
if err := s.db.Where("user_id = ? AND vocabulary_id = ?", userID, vocabularyID).First(&progress).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &progress, nil
|
||
}
|
||
|
||
// GetUserVocabularyStats 获取用户词汇学习统计
|
||
func (s *VocabularyService) GetUserVocabularyStats(userID int64) (map[string]interface{}, error) {
|
||
stats := make(map[string]interface{})
|
||
|
||
// 总学习词汇数
|
||
var totalStudied int64
|
||
if err := s.db.Table("ai_user_word_progress").Where("user_id = ?", userID).Count(&totalStudied).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
stats["total_studied"] = totalStudied
|
||
|
||
// 掌握程度统计(基于proficiency字段:0-100)
|
||
var masteryStats []struct {
|
||
Level string `json:"level"`
|
||
Count int64 `json:"count"`
|
||
}
|
||
if err := s.db.Table("ai_user_word_progress").
|
||
Select("CASE WHEN proficiency >= 80 THEN 'mastered' WHEN proficiency >= 60 THEN 'familiar' WHEN proficiency >= 40 THEN 'learning' ELSE 'new' END as level, COUNT(*) as count").
|
||
Where("user_id = ?", userID).
|
||
Group("level").
|
||
Scan(&masteryStats).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
stats["mastery_stats"] = masteryStats
|
||
|
||
// 学习准确率
|
||
var accuracyResult struct {
|
||
TotalCorrect int64 `json:"total_correct"`
|
||
TotalWrong int64 `json:"total_wrong"`
|
||
}
|
||
if err := s.db.Table("ai_user_word_progress").
|
||
Select("COALESCE(SUM(correct_count), 0) as total_correct, COALESCE(SUM(wrong_count), 0) as total_wrong").
|
||
Where("user_id = ?", userID).
|
||
Scan(&accuracyResult).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
totalAttempts := accuracyResult.TotalCorrect + accuracyResult.TotalWrong
|
||
if totalAttempts > 0 {
|
||
stats["accuracy_rate"] = float64(accuracyResult.TotalCorrect) / float64(totalAttempts) * 100
|
||
} else {
|
||
stats["accuracy_rate"] = 0.0
|
||
}
|
||
|
||
// 最近学习的词汇
|
||
var recentVocabularies []models.Vocabulary
|
||
if err := s.db.Table("ai_vocabulary v").
|
||
Joins("JOIN ai_user_word_progress uwp ON v.id = uwp.vocabulary_id").
|
||
Where("uwp.user_id = ?", userID).
|
||
Order("uwp.last_studied_at DESC").
|
||
Limit(5).
|
||
Find(&recentVocabularies).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
stats["recent_vocabularies"] = recentVocabularies
|
||
|
||
return stats, nil
|
||
}
|
||
|
||
// GetTodayStudyWords 获取今日学习单词(包含完整的释义和例句)
|
||
func (s *VocabularyService) GetTodayStudyWords(userID int64, limit int) ([]map[string]interface{}, error) {
|
||
var words []map[string]interface{}
|
||
|
||
// 查询最近学习的单词(按最后学习时间排序,获取最近学习的词)
|
||
query := `
|
||
SELECT
|
||
v.id,
|
||
v.word,
|
||
COALESCE(v.phonetic, '') as phonetic,
|
||
COALESCE(v.phonetic_us, '') as phonetic_us,
|
||
COALESCE(v.phonetic_uk, '') as phonetic_uk,
|
||
COALESCE(v.audio_url, '') as audio_url,
|
||
COALESCE(v.audio_us_url, '') as audio_us_url,
|
||
COALESCE(v.audio_uk_url, '') as audio_uk_url,
|
||
COALESCE(v.level, '') as level,
|
||
v.difficulty_level,
|
||
v.frequency,
|
||
uwp.proficiency as mastery_level,
|
||
uwp.study_count as review_count,
|
||
uwp.next_review_at
|
||
FROM ai_vocabulary v
|
||
INNER JOIN ai_user_word_progress uwp ON v.id = uwp.vocabulary_id
|
||
WHERE uwp.user_id = ?
|
||
AND v.is_active = 1
|
||
ORDER BY uwp.last_studied_at DESC
|
||
LIMIT ?
|
||
`
|
||
|
||
rows, err := s.db.Raw(query, userID, limit).Rows()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var vocabularyIDs []int64
|
||
tempWords := make(map[int64]map[string]interface{})
|
||
|
||
for rows.Next() {
|
||
var (
|
||
id, reviewCount, difficultyLevel, frequency int64
|
||
word, phonetic, phoneticUs, phoneticUk, audioUrl, audioUsUrl, audioUkUrl, level string
|
||
masteryLevel int
|
||
nextReviewAt *time.Time
|
||
)
|
||
|
||
if err := rows.Scan(&id, &word, &phonetic, &phoneticUs, &phoneticUk, &audioUrl, &audioUsUrl, &audioUkUrl, &level, &difficultyLevel, &frequency, &masteryLevel, &reviewCount, &nextReviewAt); err != nil {
|
||
continue
|
||
}
|
||
|
||
vocabularyIDs = append(vocabularyIDs, id)
|
||
tempWords[id] = map[string]interface{}{
|
||
"id": fmt.Sprintf("%d", id),
|
||
"word": word,
|
||
"phonetic": phonetic,
|
||
"phonetic_us": phoneticUs,
|
||
"phonetic_uk": phoneticUk,
|
||
"audio_url": audioUrl,
|
||
"audio_us_url": audioUsUrl,
|
||
"audio_uk_url": audioUkUrl,
|
||
"difficulty": s.mapDifficultyLevel(int(difficultyLevel)),
|
||
"frequency": int(frequency),
|
||
"mastery_level": masteryLevel,
|
||
"review_count": reviewCount,
|
||
"next_review_at": nextReviewAt,
|
||
"definitions": []map[string]interface{}{},
|
||
"examples": []map[string]interface{}{},
|
||
"created_at": time.Now(),
|
||
"updated_at": time.Now(),
|
||
}
|
||
}
|
||
|
||
// 批量获取释义
|
||
if len(vocabularyIDs) > 0 {
|
||
s.loadDefinitionsForWords(vocabularyIDs, tempWords)
|
||
s.loadExamplesForWords(vocabularyIDs, tempWords)
|
||
}
|
||
|
||
// 按原始顺序返回
|
||
for _, id := range vocabularyIDs {
|
||
words = append(words, tempWords[id])
|
||
}
|
||
|
||
// 如果没有需要复习的,返回一些新单词
|
||
if len(words) == 0 {
|
||
newWordsQuery := `
|
||
SELECT
|
||
v.id,
|
||
v.word,
|
||
COALESCE(v.phonetic, '') as phonetic,
|
||
COALESCE(v.phonetic_us, '') as phonetic_us,
|
||
COALESCE(v.phonetic_uk, '') as phonetic_uk,
|
||
COALESCE(v.audio_url, '') as audio_url,
|
||
COALESCE(v.audio_us_url, '') as audio_us_url,
|
||
COALESCE(v.audio_uk_url, '') as audio_uk_url,
|
||
COALESCE(v.level, '') as level,
|
||
v.difficulty_level,
|
||
v.frequency
|
||
FROM ai_vocabulary v
|
||
WHERE v.is_active = 1
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM ai_user_word_progress uvp
|
||
WHERE uvp.vocabulary_id = v.id AND uvp.user_id = ?
|
||
)
|
||
ORDER BY v.frequency DESC, RAND()
|
||
LIMIT ?
|
||
`
|
||
|
||
rows, err := s.db.Raw(newWordsQuery, userID, limit).Rows()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var newVocabularyIDs []int64
|
||
newTempWords := make(map[int64]map[string]interface{})
|
||
|
||
for rows.Next() {
|
||
var (
|
||
id, difficultyLevel, frequency int64
|
||
word, phonetic, phoneticUs, phoneticUk, audioUrl, audioUsUrl, audioUkUrl, level string
|
||
)
|
||
|
||
if err := rows.Scan(&id, &word, &phonetic, &phoneticUs, &phoneticUk, &audioUrl, &audioUsUrl, &audioUkUrl, &level, &difficultyLevel, &frequency); err != nil {
|
||
fmt.Printf("err:%+v", err)
|
||
continue
|
||
}
|
||
|
||
newVocabularyIDs = append(newVocabularyIDs, id)
|
||
newTempWords[id] = map[string]interface{}{
|
||
"id": fmt.Sprintf("%d", id),
|
||
"word": word,
|
||
"phonetic": phonetic,
|
||
"phonetic_us": phoneticUs,
|
||
"phonetic_uk": phoneticUk,
|
||
"audio_url": audioUrl,
|
||
"audio_us_url": audioUsUrl,
|
||
"audio_uk_url": audioUkUrl,
|
||
"difficulty": s.mapDifficultyLevel(int(difficultyLevel)),
|
||
"frequency": int(frequency),
|
||
"mastery_level": 0,
|
||
"review_count": 0,
|
||
"definitions": []map[string]interface{}{},
|
||
"examples": []map[string]interface{}{},
|
||
"created_at": time.Now(),
|
||
"updated_at": time.Now(),
|
||
}
|
||
}
|
||
|
||
// 批量获取释义和例句
|
||
if len(newVocabularyIDs) > 0 {
|
||
s.loadDefinitionsForWords(newVocabularyIDs, newTempWords)
|
||
s.loadExamplesForWords(newVocabularyIDs, newTempWords)
|
||
}
|
||
|
||
// 按原始顺序返回
|
||
for _, id := range newVocabularyIDs {
|
||
words = append(words, newTempWords[id])
|
||
}
|
||
}
|
||
|
||
return words, nil
|
||
}
|
||
|
||
// GetStudyStatistics 获取学习统计
|
||
func (s *VocabularyService) GetStudyStatistics(userID int64, date string) (map[string]interface{}, error) {
|
||
stats := make(map[string]interface{})
|
||
|
||
// 如果指定日期没有数据,使用最近一次学习的日期
|
||
var actualDate string
|
||
dateCheckQuery := `
|
||
SELECT DATE_FORMAT(DATE(last_studied_at), '%Y-%m-%d') as study_date
|
||
FROM ai_user_word_progress
|
||
WHERE user_id = ? AND DATE(last_studied_at) <= ?
|
||
ORDER BY last_studied_at DESC
|
||
LIMIT 1
|
||
`
|
||
if err := s.db.Raw(dateCheckQuery, userID, date).Scan(&actualDate).Error; err != nil {
|
||
// 如果没有任何学习记录,使用传入的日期
|
||
actualDate = date
|
||
}
|
||
|
||
// 今日学习单词数(去重)
|
||
var wordsStudied int64
|
||
todayQuery := `
|
||
SELECT COUNT(DISTINCT vocabulary_id)
|
||
FROM ai_user_word_progress
|
||
WHERE user_id = ? AND DATE(last_studied_at) = ?
|
||
`
|
||
if err := s.db.Raw(todayQuery, userID, actualDate).Scan(&wordsStudied).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 今日新学单词数(第一次学习)
|
||
var newWordsLearned int64
|
||
newWordsQuery := `
|
||
SELECT COUNT(*)
|
||
FROM ai_user_word_progress
|
||
WHERE user_id = ?
|
||
AND DATE(first_studied_at) = ?
|
||
`
|
||
if err := s.db.Raw(newWordsQuery, userID, actualDate).Scan(&newWordsLearned).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 今日复习单词数
|
||
var wordsReviewed int64
|
||
reviewQuery := `
|
||
SELECT COUNT(*)
|
||
FROM ai_user_word_progress
|
||
WHERE user_id = ?
|
||
AND DATE(last_studied_at) = ?
|
||
AND study_count > 1
|
||
`
|
||
if err := s.db.Raw(reviewQuery, userID, actualDate).Scan(&wordsReviewed).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 今日掌握单词数
|
||
var wordsMastered int64
|
||
masteredQuery := `
|
||
SELECT COUNT(*)
|
||
FROM ai_user_word_progress
|
||
WHERE user_id = ?
|
||
AND DATE(mastered_at) = ?
|
||
AND status = 'mastered'
|
||
`
|
||
if err := s.db.Raw(masteredQuery, userID, actualDate).Scan(&wordsMastered).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 今日正确和错误次数
|
||
var correctAnswers, wrongAnswers int64
|
||
answersQuery := `
|
||
SELECT
|
||
COALESCE(SUM(correct_count), 0) as correct,
|
||
COALESCE(SUM(wrong_count), 0) as wrong
|
||
FROM ai_user_word_progress
|
||
WHERE user_id = ?
|
||
AND DATE(last_studied_at) = ?
|
||
`
|
||
row := s.db.Raw(answersQuery, userID, actualDate).Row()
|
||
if err := row.Scan(&correctAnswers, &wrongAnswers); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 计算准确率
|
||
totalAnswers := correctAnswers + wrongAnswers
|
||
averageAccuracy := 0.0
|
||
if totalAnswers > 0 {
|
||
averageAccuracy = float64(correctAnswers) / float64(totalAnswers)
|
||
}
|
||
|
||
// 构建返回数据(匹配前端StudyStatistics模型)
|
||
stats["id"] = fmt.Sprintf("stats_%d_%s", userID, actualDate)
|
||
stats["user_id"] = fmt.Sprintf("%d", userID)
|
||
stats["date"] = actualDate
|
||
stats["session_count"] = 1
|
||
stats["words_studied"] = wordsStudied
|
||
stats["new_words_learned"] = newWordsLearned
|
||
stats["words_reviewed"] = wordsReviewed
|
||
stats["words_mastered"] = wordsMastered
|
||
stats["total_study_time_seconds"] = int(wordsStudied) * 30 // 估算学习时间
|
||
stats["correct_answers"] = correctAnswers
|
||
stats["wrong_answers"] = wrongAnswers
|
||
stats["average_accuracy"] = averageAccuracy
|
||
stats["experience_gained"] = int(wordsStudied) * 5
|
||
stats["points_gained"] = int(wordsStudied) * 2
|
||
stats["streak_days"] = 1
|
||
|
||
return stats, nil
|
||
}
|
||
|
||
// GetStudyStatisticsHistory 获取学习统计历史
|
||
func (s *VocabularyService) GetStudyStatisticsHistory(userID int64, startDate, endDate string) ([]map[string]interface{}, error) {
|
||
var history []map[string]interface{}
|
||
|
||
log.Printf("[DEBUG] GetStudyStatisticsHistory: userID=%d, startDate=%s, endDate=%s", userID, startDate, endDate)
|
||
|
||
// 按日期分组统计学习数据
|
||
query := `
|
||
SELECT
|
||
DATE(last_studied_at) as date,
|
||
COUNT(DISTINCT vocabulary_id) as words_studied,
|
||
SUM(CASE WHEN DATE(first_studied_at) = DATE(last_studied_at) THEN 1 ELSE 0 END) as new_words_learned,
|
||
SUM(CASE WHEN study_count > 1 THEN 1 ELSE 0 END) as words_reviewed,
|
||
SUM(CASE WHEN status = 'mastered' AND DATE(mastered_at) = DATE(last_studied_at) THEN 1 ELSE 0 END) as words_mastered,
|
||
SUM(correct_count) as correct_answers,
|
||
SUM(wrong_count) as wrong_answers
|
||
FROM ai_user_word_progress
|
||
WHERE user_id = ?
|
||
AND DATE(last_studied_at) BETWEEN ? AND ?
|
||
GROUP BY DATE(last_studied_at)
|
||
ORDER BY DATE(last_studied_at) ASC
|
||
`
|
||
|
||
rows, err := s.db.Raw(query, userID, startDate, endDate).Rows()
|
||
if err != nil {
|
||
log.Printf("[ERROR] GetStudyStatisticsHistory query failed: %v", err)
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
for rows.Next() {
|
||
var (
|
||
date time.Time
|
||
wordsStudied, newWordsLearned, wordsReviewed, wordsMastered int64
|
||
correctAnswers, wrongAnswers int64
|
||
)
|
||
|
||
if err := rows.Scan(&date, &wordsStudied, &newWordsLearned, &wordsReviewed, &wordsMastered, &correctAnswers, &wrongAnswers); err != nil {
|
||
log.Printf("[ERROR] GetStudyStatisticsHistory scan failed: %v", err)
|
||
continue
|
||
}
|
||
|
||
// 格式化日期为 YYYY-MM-DD 字符串
|
||
dateStr := date.Format("2006-01-02")
|
||
|
||
// 计算准确率
|
||
totalAnswers := correctAnswers + wrongAnswers
|
||
averageAccuracy := 0.0
|
||
if totalAnswers > 0 {
|
||
averageAccuracy = float64(correctAnswers) / float64(totalAnswers)
|
||
}
|
||
|
||
// 构建返回数据(匹配前端StudyStatistics模型)
|
||
history = append(history, map[string]interface{}{
|
||
"id": fmt.Sprintf("stats_%d_%s", userID, dateStr),
|
||
"user_id": fmt.Sprintf("%d", userID),
|
||
"date": dateStr,
|
||
"session_count": 1,
|
||
"words_studied": wordsStudied,
|
||
"new_words_learned": newWordsLearned,
|
||
"words_reviewed": wordsReviewed,
|
||
"words_mastered": wordsMastered,
|
||
"total_study_time_seconds": int(wordsStudied) * 30,
|
||
"correct_answers": correctAnswers,
|
||
"wrong_answers": wrongAnswers,
|
||
"average_accuracy": averageAccuracy,
|
||
"experience_gained": int(wordsStudied) * 5,
|
||
"points_gained": int(wordsStudied) * 2,
|
||
"streak_days": 1,
|
||
})
|
||
}
|
||
|
||
return history, nil
|
||
}
|
||
|
||
// GetVocabularyTest 获取词汇测试
|
||
func (s *VocabularyService) GetVocabularyTest(testID string) (*models.VocabularyTest, error) {
|
||
var test models.VocabularyTest
|
||
if err := s.db.Where("id = ?", testID).First(&test).Error; err != nil {
|
||
if err == gorm.ErrRecordNotFound {
|
||
return nil, common.NewBusinessError(common.ErrCodeTestNotFound, "测试不存在")
|
||
}
|
||
return nil, err
|
||
}
|
||
return &test, nil
|
||
}
|
||
|
||
// CreateVocabularyTest 创建词汇测试
|
||
func (s *VocabularyService) CreateVocabularyTest(userID int64, testType, level string, totalWords int) (*models.VocabularyTest, error) {
|
||
test := &models.VocabularyTest{
|
||
ID: 0, // 让数据库自动生成
|
||
UserID: userID,
|
||
TestType: testType,
|
||
Level: level,
|
||
TotalWords: totalWords,
|
||
StartedAt: time.Now(),
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
if err := s.db.Create(test).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return test, nil
|
||
}
|
||
|
||
// UpdateVocabularyTestResult 更新词汇测试结果
|
||
func (s *VocabularyService) UpdateVocabularyTestResult(testID string, correctWords int, score float64, duration int) error {
|
||
now := time.Now()
|
||
updates := map[string]interface{}{
|
||
"correct_words": correctWords,
|
||
"score": score,
|
||
"duration": duration,
|
||
"completed_at": &now,
|
||
"updated_at": now,
|
||
}
|
||
|
||
result := s.db.Model(&models.VocabularyTest{}).Where("id = ?", testID).Updates(updates)
|
||
if result.Error != nil {
|
||
return result.Error
|
||
}
|
||
|
||
if result.RowsAffected == 0 {
|
||
return common.NewBusinessError(common.ErrCodeTestNotFound, "测试不存在")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// SearchVocabularies 搜索词汇
|
||
func (s *VocabularyService) SearchVocabularies(keyword string, level string, page, pageSize int) (*common.PaginationData, error) {
|
||
offset := utils.CalculateOffset(page, pageSize)
|
||
|
||
query := s.db.Model(&models.Vocabulary{})
|
||
|
||
// 关键词搜索
|
||
if keyword != "" {
|
||
query = query.Where("word LIKE ? OR phonetic LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
|
||
}
|
||
|
||
// 级别过滤
|
||
if level != "" {
|
||
query = query.Where("level = ?", level)
|
||
}
|
||
|
||
// 只查询启用的词汇
|
||
query = query.Where("is_active = ?", true)
|
||
|
||
// 获取总数
|
||
var total int64
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 获取词汇列表
|
||
var vocabularies []models.Vocabulary
|
||
if err := query.Preload("Definitions").Preload("Examples").Preload("Images").Preload("Categories").
|
||
Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&vocabularies).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
totalPages := utils.CalculateTotalPages(int(total), pageSize)
|
||
|
||
return &common.PaginationData{
|
||
Items: vocabularies,
|
||
Pagination: &common.Pagination{
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
Total: int(total),
|
||
TotalPages: totalPages,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// GetDailyStats 获取每日学习统计
|
||
func (s *VocabularyService) GetDailyStats(userID string) (map[string]interface{}, error) {
|
||
var stats struct {
|
||
WordsLearned int `json:"wordsLearned"`
|
||
StudyTimeMinutes int `json:"studyTimeMinutes"`
|
||
}
|
||
|
||
// 查询今日学习单词数量(今天有复习记录的单词)
|
||
if err := s.db.Raw(`
|
||
SELECT COUNT(DISTINCT vocabulary_id) AS wordsLearned
|
||
FROM ai_user_vocabulary_progress
|
||
WHERE user_id = ? AND DATE(last_reviewed_at) = CURDATE()
|
||
`, userID).Scan(&stats.WordsLearned).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 查询今日学习时间(根据复习次数估算,每次复习约2分钟)
|
||
var reviewCount int
|
||
if err := s.db.Raw(`
|
||
SELECT COALESCE(SUM(review_count), 0) AS review_count
|
||
FROM ai_user_vocabulary_progress
|
||
WHERE user_id = ? AND DATE(last_reviewed_at) = CURDATE()
|
||
`, userID).Scan(&reviewCount).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
stats.StudyTimeMinutes = reviewCount * 2 // 估算学习时间
|
||
|
||
return map[string]interface{}{
|
||
"wordsLearned": stats.WordsLearned,
|
||
"studyTimeMinutes": stats.StudyTimeMinutes,
|
||
}, nil
|
||
}
|
||
|
||
// GetUserLearningProgress 获取用户学习进度
|
||
func (s *VocabularyService) GetUserLearningProgress(userID string, page, limit int) ([]map[string]interface{}, int64, error) {
|
||
var progressList []map[string]interface{}
|
||
var total int64
|
||
|
||
// 查询用户在各个分类的学习进度
|
||
query := `
|
||
SELECT
|
||
vc.id,
|
||
vc.name as title,
|
||
vc.category,
|
||
vc.level,
|
||
COUNT(DISTINCT v.id) as total_words,
|
||
COUNT(DISTINCT CASE WHEN uvp.mastery_level >= 3 THEN uvp.vocabulary_id END) as learned_words,
|
||
MAX(uvp.last_review_date) as last_study_date
|
||
FROM vocabulary_categories vc
|
||
LEFT JOIN vocabulary v ON v.category_id = vc.id
|
||
LEFT JOIN user_vocabulary_progress uvp ON uvp.vocabulary_id = v.id AND uvp.user_id = ?
|
||
GROUP BY vc.id, vc.name, vc.category, vc.level
|
||
HAVING total_words > 0
|
||
ORDER BY last_study_date DESC NULLS LAST
|
||
`
|
||
|
||
// 获取总数
|
||
countQuery := `
|
||
SELECT COUNT(*) FROM (
|
||
SELECT vc.id
|
||
FROM vocabulary_categories vc
|
||
LEFT JOIN vocabulary v ON v.category_id = vc.id
|
||
GROUP BY vc.id
|
||
HAVING COUNT(DISTINCT v.id) > 0
|
||
) as subquery
|
||
`
|
||
if err := s.db.Raw(countQuery).Scan(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
// 分页查询
|
||
offset := (page - 1) * limit
|
||
rows, err := s.db.Raw(query+" LIMIT ? OFFSET ?", userID, limit, offset).Rows()
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
for rows.Next() {
|
||
var (
|
||
id string
|
||
title string
|
||
category string
|
||
level string
|
||
totalWords int
|
||
learnedWords int
|
||
lastStudyDate *time.Time
|
||
)
|
||
|
||
if err := rows.Scan(&id, &title, &category, &level, &totalWords, &learnedWords, &lastStudyDate); err != nil {
|
||
continue
|
||
}
|
||
|
||
progress := float64(0)
|
||
if totalWords > 0 {
|
||
progress = float64(learnedWords) / float64(totalWords) * 100
|
||
}
|
||
|
||
item := map[string]interface{}{
|
||
"id": id,
|
||
"title": title,
|
||
"category": category,
|
||
"level": level,
|
||
"total_words": totalWords,
|
||
"learned_words": learnedWords,
|
||
"progress": progress,
|
||
"last_study_date": lastStudyDate,
|
||
}
|
||
|
||
progressList = append(progressList, item)
|
||
}
|
||
|
||
return progressList, total, nil
|
||
}
|
||
|
||
// loadDefinitionsForWords 批量加载单词的释义
|
||
func (s *VocabularyService) loadDefinitionsForWords(vocabularyIDs []int64, words map[int64]map[string]interface{}) {
|
||
var definitions []struct {
|
||
VocabularyID int64 `gorm:"column:vocabulary_id"`
|
||
PartOfSpeech string `gorm:"column:part_of_speech"`
|
||
DefinitionEn string `gorm:"column:definition_en"`
|
||
DefinitionCn string `gorm:"column:definition_cn"`
|
||
}
|
||
|
||
if err := s.db.Table("ai_vocabulary_definitions").
|
||
Where("vocabulary_id IN ?", vocabularyIDs).
|
||
Order("vocabulary_id, sort_order").
|
||
Find(&definitions).Error; err != nil {
|
||
return
|
||
}
|
||
|
||
for _, def := range definitions {
|
||
if word, ok := words[def.VocabularyID]; ok {
|
||
defs := word["definitions"].([]map[string]interface{})
|
||
defs = append(defs, map[string]interface{}{
|
||
"type": def.PartOfSpeech,
|
||
"definition": def.DefinitionEn,
|
||
"translation": def.DefinitionCn,
|
||
})
|
||
word["definitions"] = defs
|
||
}
|
||
}
|
||
}
|
||
|
||
// loadExamplesForWords 批量加载单词的例句
|
||
func (s *VocabularyService) loadExamplesForWords(vocabularyIDs []int64, words map[int64]map[string]interface{}) {
|
||
var examples []struct {
|
||
VocabularyID int64 `gorm:"column:vocabulary_id"`
|
||
SentenceEn string `gorm:"column:sentence_en"`
|
||
SentenceCn string `gorm:"column:sentence_cn"`
|
||
}
|
||
|
||
if err := s.db.Table("ai_vocabulary_examples").
|
||
Where("vocabulary_id IN ?", vocabularyIDs).
|
||
Order("vocabulary_id, sort_order").
|
||
Limit(len(vocabularyIDs) * 3). // 每个单词最多3个例句
|
||
Find(&examples).Error; err != nil {
|
||
return
|
||
}
|
||
|
||
for _, ex := range examples {
|
||
if word, ok := words[ex.VocabularyID]; ok {
|
||
exs := word["examples"].([]map[string]interface{})
|
||
exs = append(exs, map[string]interface{}{
|
||
"sentence": ex.SentenceEn,
|
||
"translation": ex.SentenceCn,
|
||
})
|
||
word["examples"] = exs
|
||
}
|
||
}
|
||
}
|
||
|
||
// mapDifficultyLevel 映射难度等级
|
||
func (s *VocabularyService) mapDifficultyLevel(level int) string {
|
||
switch level {
|
||
case 1:
|
||
return "beginner"
|
||
case 2:
|
||
return "elementary"
|
||
case 3:
|
||
return "intermediate"
|
||
case 4:
|
||
return "advanced"
|
||
case 5:
|
||
return "expert"
|
||
default:
|
||
return "intermediate"
|
||
}
|
||
}
|
||
|
||
// GetSystemVocabularyBooks 获取系统词汇书列表
|
||
func (s *VocabularyService) GetSystemVocabularyBooks(page, limit int, category string) ([]models.VocabularyBook, int64, error) {
|
||
var books []models.VocabularyBook
|
||
var total int64
|
||
|
||
query := s.db.Model(&models.VocabularyBook{}).Where("is_system = ? AND is_active = ?", true, true)
|
||
|
||
// 如果指定了分类,添加分类过滤
|
||
if category != "" {
|
||
query = query.Where("category = ?", category)
|
||
}
|
||
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
offset := (page - 1) * limit
|
||
if err := query.Offset(offset).Limit(limit).Order("sort_order ASC, created_at DESC").Find(&books).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
return books, total, nil
|
||
}
|
||
|
||
// GetVocabularyBookCategories 获取词汇书分类列表
|
||
func (s *VocabularyService) GetVocabularyBookCategories() ([]map[string]interface{}, error) {
|
||
var results []struct {
|
||
Category string
|
||
Count int64
|
||
}
|
||
|
||
// 查询所有分类及其词汇书数量
|
||
if err := s.db.Model(&models.VocabularyBook{}).
|
||
Select("category, COUNT(*) as count").
|
||
Where("is_system = ? AND is_active = ?", true, true).
|
||
Group("category").
|
||
Order("MIN(sort_order)").
|
||
Find(&results).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 转换为返回格式
|
||
categories := make([]map[string]interface{}, 0, len(results))
|
||
for _, result := range results {
|
||
categories = append(categories, map[string]interface{}{
|
||
"name": result.Category,
|
||
"count": result.Count,
|
||
})
|
||
}
|
||
|
||
return categories, nil
|
||
}
|
||
|
||
// GetVocabularyBookProgress 获取词汇书学习进度
|
||
func (s *VocabularyService) GetVocabularyBookProgress(userID int64, bookID string) (*models.UserVocabularyBookProgress, error) {
|
||
var progress models.UserVocabularyBookProgress
|
||
|
||
// 查询进度表
|
||
err := s.db.Where("user_id = ? AND book_id = ?", userID, bookID).
|
||
First(&progress).Error
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &progress, nil
|
||
}
|
||
|
||
// GetVocabularyBookWords 获取词汇书单词列表
|
||
func (s *VocabularyService) GetVocabularyBookWords(bookID string, page, limit int) ([]models.VocabularyBookWord, int64, error) {
|
||
var bookWords []models.VocabularyBookWord
|
||
var total int64
|
||
|
||
query := s.db.Model(&models.VocabularyBookWord{}).Where("book_id = ?", bookID)
|
||
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
offset := (page - 1) * limit
|
||
if err := query.Offset(offset).
|
||
Limit(limit).
|
||
Order("sort_order ASC, id ASC").
|
||
Find(&bookWords).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
// 手动加载词汇数据
|
||
for i := range bookWords {
|
||
var vocab models.Vocabulary
|
||
|
||
// 将vocabulary_id从string转换为int64(因为数据库表类型不一致)
|
||
// ai_vocabulary_book_words.vocabulary_id 是 varchar
|
||
// ai_vocabulary.id 是 bigint
|
||
vocabularyIDInt64, err := strconv.ParseInt(bookWords[i].VocabularyID, 10, 64)
|
||
if err != nil {
|
||
// 如果转换失败,跳过这个单词
|
||
continue
|
||
}
|
||
|
||
if err := s.db.Where("id = ?", vocabularyIDInt64).First(&vocab).Error; err != nil {
|
||
continue
|
||
}
|
||
|
||
// 加载释义
|
||
s.db.Where("vocabulary_id = ?", vocab.ID).Find(&vocab.Definitions)
|
||
|
||
// 加载例句
|
||
s.db.Where("vocabulary_id = ?", vocab.ID).Find(&vocab.Examples)
|
||
|
||
bookWords[i].Vocabulary = &vocab
|
||
}
|
||
|
||
return bookWords, total, nil
|
||
}
|
||
|
||
// GetUserWordProgress 获取用户单词学习进度
|
||
func (s *VocabularyService) GetUserWordProgress(userID int64, wordID int64) (map[string]interface{}, error) {
|
||
// 查询用户单词进度记录
|
||
var progress models.UserWordProgress
|
||
err := s.db.Where("user_id = ? AND vocabulary_id = ?", userID, wordID).First(&progress).Error
|
||
|
||
if err == gorm.ErrRecordNotFound {
|
||
// 如果没有记录,返回默认进度
|
||
now := time.Now()
|
||
return map[string]interface{}{
|
||
"id": "0",
|
||
"userId": fmt.Sprint(userID),
|
||
"wordId": fmt.Sprint(wordID),
|
||
"status": "not_started",
|
||
"studyCount": 0,
|
||
"correctCount": 0,
|
||
"wrongCount": 0,
|
||
"proficiency": 0,
|
||
"nextReviewAt": nil,
|
||
"reviewInterval": 1,
|
||
"firstStudiedAt": now,
|
||
"lastStudiedAt": now,
|
||
"masteredAt": nil,
|
||
}, nil
|
||
}
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 返回进度数据
|
||
return map[string]interface{}{
|
||
"id": fmt.Sprint(progress.ID),
|
||
"userId": fmt.Sprint(progress.UserID),
|
||
"wordId": fmt.Sprint(progress.VocabularyID),
|
||
"status": progress.Status,
|
||
"studyCount": progress.StudyCount,
|
||
"correctCount": progress.CorrectCount,
|
||
"wrongCount": progress.WrongCount,
|
||
"proficiency": progress.Proficiency,
|
||
"nextReviewAt": progress.NextReviewAt,
|
||
"reviewInterval": progress.ReviewInterval,
|
||
"firstStudiedAt": progress.FirstStudiedAt,
|
||
"lastStudiedAt": progress.LastStudiedAt,
|
||
"masteredAt": progress.MasteredAt,
|
||
}, nil
|
||
}
|
||
|
||
// UpdateUserWordProgress 更新用户单词学习进度
|
||
func (s *VocabularyService) UpdateUserWordProgress(userID int64, wordID int64, status string, isCorrect *bool) (map[string]interface{}, error) {
|
||
var progress models.UserWordProgress
|
||
err := s.db.Where("user_id = ? AND vocabulary_id = ?", userID, wordID).First(&progress).Error
|
||
|
||
now := time.Now()
|
||
|
||
if err == gorm.ErrRecordNotFound {
|
||
// 创建新记录
|
||
progress = models.UserWordProgress{
|
||
UserID: userID,
|
||
VocabularyID: wordID,
|
||
Status: status,
|
||
StudyCount: 1,
|
||
CorrectCount: 0,
|
||
WrongCount: 0,
|
||
Proficiency: 0,
|
||
ReviewInterval: 1,
|
||
FirstStudiedAt: now,
|
||
LastStudiedAt: now,
|
||
}
|
||
|
||
if isCorrect != nil && *isCorrect {
|
||
progress.CorrectCount = 1
|
||
progress.Proficiency = 20
|
||
} else if isCorrect != nil {
|
||
progress.WrongCount = 1
|
||
}
|
||
|
||
if err := s.db.Create(&progress).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
} else if err != nil {
|
||
return nil, err
|
||
} else {
|
||
// 更新现有记录
|
||
progress.Status = status
|
||
progress.StudyCount++
|
||
progress.LastStudiedAt = now
|
||
|
||
if isCorrect != nil && *isCorrect {
|
||
progress.CorrectCount++
|
||
// 根据正确率更新熟练度
|
||
accuracy := float64(progress.CorrectCount) / float64(progress.StudyCount)
|
||
progress.Proficiency = int(accuracy * 100)
|
||
|
||
// 如果熟练度达到80%,标记为已掌握
|
||
if progress.Proficiency >= 80 && progress.Status != "mastered" {
|
||
progress.Status = "mastered"
|
||
progress.MasteredAt = &now
|
||
}
|
||
} else if isCorrect != nil {
|
||
progress.WrongCount++
|
||
// 降低熟练度
|
||
if progress.Proficiency > 10 {
|
||
progress.Proficiency -= 10
|
||
}
|
||
}
|
||
|
||
if err := s.db.Save(&progress).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// 返回更新后的进度
|
||
return map[string]interface{}{
|
||
"id": fmt.Sprint(progress.ID),
|
||
"userId": fmt.Sprint(progress.UserID),
|
||
"wordId": fmt.Sprint(progress.VocabularyID),
|
||
"status": progress.Status,
|
||
"studyCount": progress.StudyCount,
|
||
"correctCount": progress.CorrectCount,
|
||
"wrongCount": progress.WrongCount,
|
||
"proficiency": progress.Proficiency,
|
||
"nextReviewAt": progress.NextReviewAt,
|
||
"reviewInterval": progress.ReviewInterval,
|
||
"firstStudiedAt": progress.FirstStudiedAt,
|
||
"lastStudiedAt": progress.LastStudiedAt,
|
||
"masteredAt": progress.MasteredAt,
|
||
}, nil
|
||
}
|