Files
ai_english/serve/internal/services/reading_service.go

432 lines
13 KiB
Go
Raw Permalink Normal View History

2025-11-17 14:09:17 +08:00
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
}