Files
ai_english/serve/internal/services/reading_service.go
2025-11-17 14:09:17 +08:00

432 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"database/sql"
"errors"
"time"
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ReadingService 阅读理解服务
type ReadingService struct {
db *gorm.DB
}
// NewReadingService 创建阅读理解服务实例
func NewReadingService(db *gorm.DB) *ReadingService {
return &ReadingService{db: db}
}
// ===== 阅读材料管理 =====
// GetReadingMaterials 获取阅读材料列表
func (s *ReadingService) GetReadingMaterials(level, category string, page, pageSize int) ([]models.ReadingMaterial, int64, error) {
var materials []models.ReadingMaterial
var total int64
query := s.db.Model(&models.ReadingMaterial{}).Where("is_active = ?", true)
// 按难度级别筛选
if level != "" {
query = query.Where("level = ?", level)
}
// 按分类筛选
if category != "" {
query = query.Where("category = ?", category)
}
// 获取总数
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页查询
offset := (page - 1) * pageSize
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&materials).Error; err != nil {
return nil, 0, err
}
return materials, total, nil
}
// GetReadingMaterial 获取单个阅读材料
func (s *ReadingService) GetReadingMaterial(id string) (*models.ReadingMaterial, error) {
var material models.ReadingMaterial
if err := s.db.Where("id = ? AND is_active = ?", id, true).First(&material).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("阅读材料不存在")
}
return nil, err
}
return &material, nil
}
// CreateReadingMaterial 创建阅读材料
func (s *ReadingService) CreateReadingMaterial(material *models.ReadingMaterial) error {
material.ID = uuid.New().String()
material.CreatedAt = time.Now()
material.UpdatedAt = time.Now()
material.IsActive = true
return s.db.Create(material).Error
}
// UpdateReadingMaterial 更新阅读材料
func (s *ReadingService) UpdateReadingMaterial(id string, updates map[string]interface{}) error {
updates["updated_at"] = time.Now()
result := s.db.Model(&models.ReadingMaterial{}).Where("id = ?", id).Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("阅读材料不存在")
}
return nil
}
// DeleteReadingMaterial 删除阅读材料(软删除)
func (s *ReadingService) DeleteReadingMaterial(id string) error {
result := s.db.Model(&models.ReadingMaterial{}).Where("id = ?", id).Update("is_active", false)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("阅读材料不存在")
}
return nil
}
// SearchReadingMaterials 搜索阅读材料
func (s *ReadingService) SearchReadingMaterials(keyword string, level, category string, page, pageSize int) ([]models.ReadingMaterial, int64, error) {
var materials []models.ReadingMaterial
var total int64
query := s.db.Model(&models.ReadingMaterial{}).Where("is_active = ?", true)
// 关键词搜索
if keyword != "" {
query = query.Where("title LIKE ? OR content LIKE ? OR summary LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
}
// 按难度级别筛选
if level != "" {
query = query.Where("level = ?", level)
}
// 按分类筛选
if category != "" {
query = query.Where("category = ?", category)
}
// 获取总数
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页查询
offset := (page - 1) * pageSize
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&materials).Error; err != nil {
return nil, 0, err
}
return materials, total, nil
}
// ===== 阅读记录管理 =====
// CreateReadingRecord 创建阅读记录
func (s *ReadingService) CreateReadingRecord(record *models.ReadingRecord) error {
record.ID = uuid.New().String()
record.StartedAt = time.Now()
record.CreatedAt = time.Now()
record.UpdatedAt = time.Now()
return s.db.Create(record).Error
}
// UpdateReadingRecord 更新阅读记录
func (s *ReadingService) UpdateReadingRecord(id string, updates map[string]interface{}) error {
updates["updated_at"] = time.Now()
result := s.db.Model(&models.ReadingRecord{}).Where("id = ?", id).Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("阅读记录不存在")
}
return nil
}
// GetUserReadingRecords 获取用户阅读记录
func (s *ReadingService) GetUserReadingRecords(userID string, page, pageSize int) ([]models.ReadingRecord, int64, error) {
var records []models.ReadingRecord
var total int64
query := s.db.Model(&models.ReadingRecord{}).Where("user_id = ?", userID)
// 获取总数
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页查询,预加载材料信息
offset := (page - 1) * pageSize
if err := query.Preload("Material").Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
return nil, 0, err
}
return records, total, nil
}
// GetReadingRecord 获取单个阅读记录
func (s *ReadingService) GetReadingRecord(id string) (*models.ReadingRecord, error) {
var record models.ReadingRecord
if err := s.db.Preload("Material").Where("id = ?", id).First(&record).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("阅读记录不存在")
}
return nil, err
}
return &record, nil
}
// GetReadingProgress 获取用户对特定材料的阅读进度
func (s *ReadingService) GetReadingProgress(userID, materialID string) (*models.ReadingRecord, error) {
var record models.ReadingRecord
if err := s.db.Where("user_id = ? AND material_id = ?", userID, materialID).First(&record).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 没有阅读记录
}
return nil, err
}
return &record, nil
}
// ===== 阅读统计 =====
// ReadingStats 阅读统计结构
type ReadingStats struct {
TotalMaterials int64 `json:"total_materials"` // 总阅读材料数
CompletedMaterials int64 `json:"completed_materials"` // 已完成材料数
TotalReadingTime int64 `json:"total_reading_time"` // 总阅读时间(秒)
AverageScore float64 `json:"average_score"` // 平均理解得分
AverageSpeed float64 `json:"average_speed"` // 平均阅读速度(词/分钟)
ContinuousDays int `json:"continuous_days"` // 连续阅读天数
LevelStats []LevelStat `json:"level_stats"` // 各难度级别统计
}
// LevelStat 难度级别统计
type LevelStat struct {
Level string `json:"level"`
CompletedCount int64 `json:"completed_count"`
AverageScore float64 `json:"average_score"`
AverageSpeed float64 `json:"average_speed"`
}
// GetUserReadingStats 获取用户阅读统计
func (s *ReadingService) GetUserReadingStats(userID string) (*ReadingStats, error) {
stats := &ReadingStats{}
// 获取总阅读材料数
if err := s.db.Model(&models.ReadingMaterial{}).Where("is_active = ?", true).Count(&stats.TotalMaterials).Error; err != nil {
return nil, err
}
// 获取已完成材料数
if err := s.db.Model(&models.ReadingRecord{}).Where("user_id = ? AND completed_at IS NOT NULL", userID).Count(&stats.CompletedMaterials).Error; err != nil {
return nil, err
}
// 获取总阅读时间
var totalTime sql.NullInt64
if err := s.db.Model(&models.ReadingRecord{}).Where("user_id = ?", userID).Select("SUM(reading_time)").Scan(&totalTime).Error; err != nil {
return nil, err
}
if totalTime.Valid {
stats.TotalReadingTime = totalTime.Int64
}
// 获取平均理解得分
var avgScore sql.NullFloat64
if err := s.db.Model(&models.ReadingRecord{}).Where("user_id = ? AND comprehension_score IS NOT NULL", userID).Select("AVG(comprehension_score)").Scan(&avgScore).Error; err != nil {
return nil, err
}
if avgScore.Valid {
stats.AverageScore = avgScore.Float64
}
// 获取平均阅读速度
var avgSpeed sql.NullFloat64
if err := s.db.Model(&models.ReadingRecord{}).Where("user_id = ? AND reading_speed IS NOT NULL", userID).Select("AVG(reading_speed)").Scan(&avgSpeed).Error; err != nil {
return nil, err
}
if avgSpeed.Valid {
stats.AverageSpeed = avgSpeed.Float64
}
// 计算连续阅读天数
continuousDays, err := s.calculateContinuousReadingDays(userID)
if err != nil {
return nil, err
}
stats.ContinuousDays = continuousDays
// 获取各难度级别统计
levelStats, err := s.getLevelStats(userID)
if err != nil {
return nil, err
}
stats.LevelStats = levelStats
return stats, nil
}
// calculateContinuousReadingDays 计算连续阅读天数
func (s *ReadingService) calculateContinuousReadingDays(userID string) (int, error) {
// 获取最近的阅读记录日期
var dates []time.Time
if err := s.db.Model(&models.ReadingRecord{}).Where("user_id = ?", userID).Select("DATE(created_at) as date").Group("DATE(created_at)").Order("date DESC").Limit(365).Scan(&dates).Error; err != nil {
return 0, err
}
if len(dates) == 0 {
return 0, nil
}
// 计算连续天数
continuousDays := 1
today := time.Now().Truncate(24 * time.Hour)
lastDate := dates[0].Truncate(24 * time.Hour)
// 如果最后一次阅读不是今天或昨天连续天数为0
if lastDate.Before(today.AddDate(0, 0, -1)) {
return 0, nil
}
for i := 1; i < len(dates); i++ {
currentDate := dates[i].Truncate(24 * time.Hour)
expectedDate := lastDate.AddDate(0, 0, -1)
if currentDate.Equal(expectedDate) {
continuousDays++
lastDate = currentDate
} else {
break
}
}
return continuousDays, nil
}
// getLevelStats 获取各难度级别统计
func (s *ReadingService) getLevelStats(userID string) ([]LevelStat, error) {
var levelStats []LevelStat
query := `
SELECT
m.level,
COUNT(r.id) as completed_count,
AVG(r.comprehension_score) as average_score,
AVG(r.reading_speed) as average_speed
FROM ai_reading_records r
JOIN ai_reading_materials m ON r.material_id = m.id
WHERE r.user_id = ? AND r.completed_at IS NOT NULL
GROUP BY m.level
`
if err := s.db.Raw(query, userID).Scan(&levelStats).Error; err != nil {
return nil, err
}
return levelStats, nil
}
// GetRecommendedMaterials 获取推荐阅读材料
func (s *ReadingService) GetRecommendedMaterials(userID string, limit int) ([]models.ReadingMaterial, error) {
// 获取用户最近的阅读记录,分析偏好
var userLevel string
var userCategory string
// 获取用户最常阅读的难度级别
if err := s.db.Raw(`
SELECT m.level
FROM ai_reading_records r
JOIN ai_reading_materials m ON r.material_id = m.id
WHERE r.user_id = ?
GROUP BY m.level
ORDER BY COUNT(*) DESC
LIMIT 1
`, userID).Scan(&userLevel).Error; err != nil {
userLevel = "intermediate" // 默认中级
}
// 获取用户最常阅读的分类
if err := s.db.Raw(`
SELECT m.category
FROM ai_reading_records r
JOIN ai_reading_materials m ON r.material_id = m.id
WHERE r.user_id = ?
GROUP BY m.category
ORDER BY COUNT(*) DESC
LIMIT 1
`, userID).Scan(&userCategory).Error; err != nil {
userCategory = "" // 不限制分类
}
// 获取用户未读过的材料
var materials []models.ReadingMaterial
query := s.db.Model(&models.ReadingMaterial{}).Where(`
is_active = ? AND id NOT IN (
SELECT material_id FROM ai_reading_records WHERE user_id = ?
)
`, true, userID)
// 优先推荐相同难度级别的材料
if userLevel != "" {
query = query.Where("level = ?", userLevel)
}
// 如果有偏好分类,优先推荐
if userCategory != "" {
query = query.Where("category = ?", userCategory)
}
if err := query.Order("created_at DESC").Limit(limit).Find(&materials).Error; err != nil {
return nil, err
}
// 如果推荐材料不足,补充其他材料
if len(materials) < limit {
var additionalMaterials []models.ReadingMaterial
remaining := limit - len(materials)
// 获取已推荐材料的ID列表
excludeIDs := make([]string, len(materials))
for i, m := range materials {
excludeIDs[i] = m.ID
}
additionalQuery := s.db.Model(&models.ReadingMaterial{}).Where(`
is_active = ? AND id NOT IN (
SELECT material_id FROM ai_reading_records WHERE user_id = ?
)
`, true, userID)
if len(excludeIDs) > 0 {
additionalQuery = additionalQuery.Where("id NOT IN ?", excludeIDs)
}
if err := additionalQuery.Order("created_at DESC").Limit(remaining).Find(&additionalMaterials).Error; err != nil {
return materials, nil // 返回已有的推荐
}
materials = append(materials, additionalMaterials...)
}
return materials, nil
}