using MiAssessment.Core.Interfaces; using MiAssessment.Model.Data; using MiAssessment.Model.Models.Assessment; using MiAssessment.Model.Models.Common; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace MiAssessment.Core.Services; /// /// 测评服务实现 /// /// /// 提供测评模块的核心业务功能,包括: /// - 测评介绍获取 /// - 题目列表查询 /// - 答案提交处理 /// - 报告状态查询 /// - 测评结果获取 /// - 邀请码验证 /// - 往期测评记录查询 /// public class AssessmentService : IAssessmentService { private readonly MiAssessmentDbContext _dbContext; private readonly ILogger _logger; private readonly ReportGenerationService _reportGenerationService; /// /// 构造函数 /// /// 数据库上下文 /// 日志记录器 /// 报告生成服务 public AssessmentService( MiAssessmentDbContext dbContext, ILogger logger, ReportGenerationService reportGenerationService) { _dbContext = dbContext; _logger = logger; _reportGenerationService = reportGenerationService; } /// public async Task GetIntroAsync(long typeId) { _logger.LogDebug("获取测评介绍,typeId: {TypeId}", typeId); var assessmentType = await _dbContext.AssessmentTypes .AsNoTracking() .Where(a => a.Id == typeId && !a.IsDeleted) .Select(a => new AssessmentIntroDto { ImageUrl = a.DetailImageUrl, Content = a.IntroContent ?? string.Empty, ContentType = "html", Price = a.Price }) .FirstOrDefaultAsync(); if (assessmentType == null) { _logger.LogWarning("测评类型不存在,typeId: {TypeId}", typeId); } else { _logger.LogDebug("获取测评介绍成功,typeId: {TypeId}", typeId); } return assessmentType; } /// public async Task> GetQuestionListAsync(long typeId) { _logger.LogDebug("获取题目列表,typeId: {TypeId}", typeId); var questions = await _dbContext.Questions .AsNoTracking() .Where(q => q.AssessmentTypeId == typeId && q.Status == 1 && !q.IsDeleted) .OrderBy(q => q.QuestionNo) .Select(q => new QuestionDto { Id = q.Id, QuestionNo = q.QuestionNo, Content = q.Content }) .ToListAsync(); _logger.LogDebug("获取到 {Count} 道题目,typeId: {TypeId}", questions.Count, typeId); return questions; } /// public async Task GetResultStatusAsync(long userId, long recordId) { _logger.LogDebug("查询报告状态,userId: {UserId}, recordId: {RecordId}", userId, recordId); var record = await _dbContext.AssessmentRecords .AsNoTracking() .Where(r => r.Id == recordId && r.UserId == userId && !r.IsDeleted) .Select(r => new ResultStatusDto { Status = r.Status, IsCompleted = r.Status == 4 }) .FirstOrDefaultAsync(); if (record == null) { _logger.LogWarning("测评记录不存在或不属于当前用户,userId: {UserId}, recordId: {RecordId}", userId, recordId); } else { _logger.LogDebug("查询报告状态成功,status: {Status}, isCompleted: {IsCompleted}", record.Status, record.IsCompleted); } return record; } /// public async Task> GetHistoryListAsync(long userId, int page, int pageSize) { _logger.LogDebug("获取往期测评列表,userId: {UserId}, page: {Page}, pageSize: {PageSize}", userId, page, pageSize); // 确保分页参数有效 if (page < 1) page = 1; if (pageSize < 1) pageSize = 20; if (pageSize > 100) pageSize = 100; var query = _dbContext.AssessmentRecords .AsNoTracking() .Where(r => r.UserId == userId && !r.IsDeleted); // 获取总数 var total = await query.CountAsync(); // 分页查询(使用左连接避免缺少关联数据时丢失记录) var records = await query .OrderByDescending(r => r.CreateTime) .Skip((page - 1) * pageSize) .Take(pageSize) .GroupJoin( _dbContext.Orders.AsNoTracking(), r => r.OrderId, o => o.Id, (r, orders) => new { Record = r, Orders = orders } ) .SelectMany( ro => ro.Orders.DefaultIfEmpty(), (ro, o) => new { ro.Record, Order = o } ) .GroupJoin( _dbContext.AssessmentTypes.AsNoTracking(), ro => ro.Record.AssessmentTypeId, at => at.Id, (ro, types) => new { ro.Record, ro.Order, Types = types } ) .SelectMany( rot => rot.Types.DefaultIfEmpty(), (rot, at) => new AssessmentHistoryDto { Id = rot.Record.Id, OrderNo = rot.Order != null ? rot.Order.OrderNo : "", AssessmentName = at != null ? at.Name : "多元智能测评", Name = rot.Record.Name, AssessmentTypeId = rot.Record.AssessmentTypeId, Status = rot.Record.Status, StatusText = GetStatusText(rot.Record.Status), TestDate = rot.Record.CreateTime.ToString("yyyy-MM-dd") } ) .ToListAsync(); _logger.LogDebug("获取到 {Count} 条往期测评记录,总数: {Total}", records.Count, total); return PagedResult.Create(records, total, page, pageSize); } /// public async Task SubmitAnswersAsync(long userId, SubmitAnswersRequest request) { _logger.LogDebug("提交测评答案,userId: {UserId}, recordId: {RecordId}, answerCount: {AnswerCount}", userId, request.RecordId, request.Answers?.Count ?? 0); // 参数验证 if (request.Answers == null || request.Answers.Count == 0) { _logger.LogWarning("答案列表为空,userId: {UserId}, recordId: {RecordId}", userId, request.RecordId); throw new InvalidOperationException("答案列表不能为空"); } // 查询测评记录 var record = await _dbContext.AssessmentRecords .Where(r => r.Id == request.RecordId && !r.IsDeleted) .FirstOrDefaultAsync(); // 验证记录是否存在 if (record == null) { _logger.LogWarning("测评记录不存在,recordId: {RecordId}", request.RecordId); throw new InvalidOperationException("测评记录不存在"); } // 验证记录归属当前用户 if (record.UserId != userId) { _logger.LogWarning("测评记录不属于当前用户,userId: {UserId}, recordUserId: {RecordUserId}, recordId: {RecordId}", userId, record.UserId, request.RecordId); throw new UnauthorizedAccessException("无权限访问该测评记录"); } // 验证记录状态(只有待测评或测评中状态才能提交答案) if (record.Status != 1 && record.Status != 2) { _logger.LogWarning("测评记录状态不允许提交答案,status: {Status}, recordId: {RecordId}", record.Status, request.RecordId); throw new InvalidOperationException("当前测评状态不允许提交答案"); } // 获取该测评类型的题目数量 var questionCount = await _dbContext.Questions .Where(q => q.AssessmentTypeId == record.AssessmentTypeId && q.Status == 1 && !q.IsDeleted) .CountAsync(); // 验证答案数量与题目数量匹配 if (request.Answers.Count != questionCount) { _logger.LogWarning("答案数量与题目数量不匹配,answerCount: {AnswerCount}, questionCount: {QuestionCount}, recordId: {RecordId}", request.Answers.Count, questionCount, request.RecordId); throw new InvalidOperationException($"答案数量({request.Answers.Count})与题目数量({questionCount})不匹配"); } // 获取所有有效题目ID用于验证 var validQuestionIds = await _dbContext.Questions .Where(q => q.AssessmentTypeId == record.AssessmentTypeId && q.Status == 1 && !q.IsDeleted) .Select(q => new { q.Id, q.QuestionNo }) .ToDictionaryAsync(q => q.Id, q => q.QuestionNo); // 验证所有答案的题目ID是否有效 foreach (var answer in request.Answers) { if (!validQuestionIds.ContainsKey(answer.QuestionId)) { _logger.LogWarning("无效的题目ID,questionId: {QuestionId}, recordId: {RecordId}", answer.QuestionId, request.RecordId); throw new InvalidOperationException($"无效的题目ID: {answer.QuestionId}"); } } // 使用事务保存答案 using var transaction = await _dbContext.Database.BeginTransactionAsync(); try { // 删除已有的答案(如果有的话,支持重新提交) var existingAnswers = await _dbContext.AssessmentAnswers .Where(a => a.RecordId == request.RecordId) .ToListAsync(); if (existingAnswers.Any()) { _dbContext.AssessmentAnswers.RemoveRange(existingAnswers); _logger.LogDebug("删除已有答案,count: {Count}, recordId: {RecordId}", existingAnswers.Count, request.RecordId); } // 创建新的答案记录 var now = DateTime.Now; var answers = request.Answers.Select(a => new MiAssessment.Model.Entities.AssessmentAnswer { RecordId = request.RecordId, QuestionId = a.QuestionId, QuestionNo = validQuestionIds[a.QuestionId], AnswerValue = a.AnswerValue, CreateTime = now }).ToList(); await _dbContext.AssessmentAnswers.AddRangeAsync(answers); // 更新测评记录状态为"生成中" record.Status = 3; // 3=生成中 record.SubmitTime = now; record.UpdateTime = now; await _dbContext.SaveChangesAsync(); await transaction.CommitAsync(); _logger.LogInformation("测评答案提交成功,userId: {UserId}, recordId: {RecordId}, answerCount: {AnswerCount}", userId, request.RecordId, answers.Count); // 触发报告生成 try { await _reportGenerationService.GenerateReportAsync(request.RecordId); } catch (Exception ex) { _logger.LogError(ex, "报告生成失败,recordId: {RecordId}", request.RecordId); // 报告生成失败不影响答案提交的返回,状态保持为3(生成中) } return new SubmitAnswersResponse { Success = true, EstimatedTime = 30 // 预计30秒生成报告 }; } catch (Exception ex) { await transaction.RollbackAsync(); _logger.LogError(ex, "提交测评答案失败,userId: {UserId}, recordId: {RecordId}", userId, request.RecordId); throw; } } /// public async Task GetResultAsync(long userId, long recordId) { _logger.LogDebug("获取测评结果,userId: {UserId}, recordId: {RecordId}", userId, recordId); // 1. 查询测评记录,验证归属和完成状态 var record = await _dbContext.AssessmentRecords .AsNoTracking() .Where(r => r.Id == recordId && !r.IsDeleted) .FirstOrDefaultAsync(); // 记录不存在 if (record == null) { _logger.LogWarning("测评记录不存在,recordId: {RecordId}", recordId); return null; } // 验证记录归属当前用户(Requirements 4.5) if (record.UserId != userId) { _logger.LogWarning("测评记录不属于当前用户,userId: {UserId}, recordUserId: {RecordUserId}, recordId: {RecordId}", userId, record.UserId, recordId); return null; } // 验证记录已完成(Status=4表示已完成)(Requirements 4.3) if (record.Status != 4) { _logger.LogWarning("测评记录未完成,status: {Status}, recordId: {RecordId}", record.Status, recordId); return null; } // 2. 获取测评类型信息 var assessmentType = await _dbContext.AssessmentTypes .AsNoTracking() .Where(at => at.Id == record.AssessmentTypeId && !at.IsDeleted) .FirstOrDefaultAsync(); if (assessmentType == null) { _logger.LogWarning("测评类型不存在,assessmentTypeId: {AssessmentTypeId}", record.AssessmentTypeId); return null; } // 3. 获取所有报告分类 var categories = await _dbContext.ReportCategories .AsNoTracking() .Where(c => c.AssessmentTypeId == record.AssessmentTypeId && !c.IsDeleted) .OrderBy(c => c.Sort) .ToListAsync(); // 4. 获取该记录的所有测评结果 var results = await _dbContext.AssessmentResults .AsNoTracking() .Where(r => r.RecordId == recordId) .ToListAsync(); // 5. 组装报告数据 var resultDto = new AssessmentResultDto { Id = record.Id, TypeId = record.AssessmentTypeId, AssessmentName = assessmentType.Name, Name = record.Name, TestDate = record.CompleteTime?.ToString("yyyy-MM-dd") ?? record.CreateTime.ToString("yyyy-MM-dd"), Intelligences = new List(), Traits = new List(), Abilities = new List(), InnateLearningTypes = new List(), KeyLearningAbilities = new List(), BrainTypes = new List(), PersonalityTypes = new List(), FutureDevelopmentAbilities = new List(), Suggestions = new List() }; // 6. 按分类类型组装数据 foreach (var result in results) { var category = categories.FirstOrDefault(c => c.Id == result.CategoryId); if (category == null) continue; switch (category.CategoryType) { case 1: // 八大智能 resultDto.Intelligences.Add(new IntelligenceDto { Name = category.Name, Code = category.Code, Score = result.Score, Percentage = result.Percentage, StarLevel = result.StarLevel, Level = GetLevelText(result.StarLevel) }); break; case 2: // 个人特质 resultDto.Traits.Add(new TraitDto { Name = category.Name, Code = category.Code, Score = result.Score, Percentage = result.Percentage, StarLevel = result.StarLevel }); break; case 3: // 细分能力 resultDto.Abilities.Add(new AbilityDto { Name = category.Name, Code = category.Code, Score = result.Score, Percentage = result.Percentage, StarLevel = result.StarLevel, Level = GetLevelText(result.StarLevel) }); break; case 4: // 先天学习类型 resultDto.InnateLearningTypes.Add(new CategoryResultDto { Name = category.Name, Code = category.Code, Score = result.Score, Percentage = result.Percentage, StarLevel = result.StarLevel, Rank = result.Rank }); break; case 5: // 学习关键能力 resultDto.KeyLearningAbilities.Add(new CategoryResultDto { Name = category.Name, Code = category.Code, Score = result.Score, Percentage = result.Percentage, StarLevel = result.StarLevel, Rank = result.Rank }); break; case 6: // 科学大脑类型 resultDto.BrainTypes.Add(new CategoryResultDto { Name = category.Name, Code = category.Code, Score = result.Score, Percentage = result.Percentage, StarLevel = result.StarLevel, Rank = result.Rank }); break; case 7: // 性格类型 resultDto.PersonalityTypes.Add(new CategoryResultDto { Name = category.Name, Code = category.Code, Score = result.Score, Percentage = result.Percentage, StarLevel = result.StarLevel, Rank = result.Rank }); break; case 8: // 未来关键发展能力 resultDto.FutureDevelopmentAbilities.Add(new CategoryResultDto { Name = category.Name, Code = category.Code, Score = result.Score, Percentage = result.Percentage, StarLevel = result.StarLevel, Rank = result.Rank }); break; } } // 7. 按得分排序各分类结果 resultDto.Intelligences = resultDto.Intelligences.OrderByDescending(i => i.Score).ToList(); resultDto.Traits = resultDto.Traits.OrderByDescending(t => t.Score).ToList(); resultDto.Abilities = resultDto.Abilities.OrderByDescending(a => a.Score).ToList(); resultDto.InnateLearningTypes = resultDto.InnateLearningTypes.OrderBy(i => i.Rank).ToList(); resultDto.KeyLearningAbilities = resultDto.KeyLearningAbilities.OrderBy(k => k.Rank).ToList(); resultDto.BrainTypes = resultDto.BrainTypes.OrderBy(b => b.Rank).ToList(); resultDto.PersonalityTypes = resultDto.PersonalityTypes.OrderBy(p => p.Rank).ToList(); resultDto.FutureDevelopmentAbilities = resultDto.FutureDevelopmentAbilities.OrderBy(f => f.Rank).ToList(); // 8. 生成综合评价(基于八大智能的最高分和最低分) if (resultDto.Intelligences.Count > 0) { var topIntelligence = resultDto.Intelligences.First(); var bottomIntelligence = resultDto.Intelligences.Last(); resultDto.Summary = $"您的{topIntelligence.Name}表现突出,建议在此领域继续发展。同时可以关注{bottomIntelligence.Name}的提升。"; } _logger.LogDebug("获取测评结果成功,recordId: {RecordId}, intelligenceCount: {IntelligenceCount}, traitCount: {TraitCount}, abilityCount: {AbilityCount}", recordId, resultDto.Intelligences.Count, resultDto.Traits.Count, resultDto.Abilities.Count); return resultDto; } /// /// 根据星级获取等级文本 /// /// 星级(1-5) /// 等级文本 private static string GetLevelText(int starLevel) { return starLevel switch { 5 => "优秀", 4 => "良好", 3 => "中等", 2 => "一般", 1 => "较弱", _ => "未知" }; } /// public async Task VerifyInviteCodeAsync(string code) { _logger.LogDebug("验证邀请码,code: {Code}", code); // 参数验证 if (string.IsNullOrWhiteSpace(code)) { _logger.LogWarning("邀请码为空"); return new VerifyInviteCodeResponse { IsValid = false, ErrorMessage = "邀请码不能为空" }; } // 查询邀请码 var inviteCode = await _dbContext.InviteCodes .AsNoTracking() .Where(ic => ic.Code == code.Trim().ToUpper() && !ic.IsDeleted) .FirstOrDefaultAsync(); // 邀请码不存在 if (inviteCode == null) { _logger.LogWarning("邀请码不存在,code: {Code}", code); return new VerifyInviteCodeResponse { IsValid = false, ErrorMessage = "邀请码有误,请重新输入" }; } // 邀请码已使用(Status=3表示已使用) if (inviteCode.Status == 3) { _logger.LogWarning("邀请码已被使用,code: {Code}", code); return new VerifyInviteCodeResponse { IsValid = false, ErrorMessage = "邀请码已被使用" }; } // 邀请码未分配(Status=1表示未分配,不能使用) if (inviteCode.Status == 1) { _logger.LogWarning("邀请码未分配,code: {Code}", code); return new VerifyInviteCodeResponse { IsValid = false, ErrorMessage = "邀请码有误,请重新输入" }; } // 验证通过(Status=2表示已分配,可以使用) _logger.LogDebug("邀请码验证通过,code: {Code}, id: {Id}", code, inviteCode.Id); return new VerifyInviteCodeResponse { IsValid = true, InviteCodeId = inviteCode.Id }; } /// /// 获取状态文本 /// /// 状态值 /// 状态文本 private static string GetStatusText(int status) { return status switch { 1 => "待测评", 2 => "测评中", 3 => "生成中", 4 => "已完成", _ => "未知" }; } /// public async Task> GetScoreOptionsAsync(long typeId) { _logger.LogDebug("获取评分标准选项,typeId: {TypeId}", typeId); var options = await _dbContext.ScoreOptions .AsNoTracking() .Where(s => s.AssessmentTypeId == typeId && s.Status == 1 && !s.IsDeleted) .OrderBy(s => s.Sort) .ThenBy(s => s.Score) .Select(s => new ScoreOptionDto { Score = s.Score, Label = s.Label, Description = s.Description }) .ToListAsync(); return options; } /// public async Task GetPendingRecordAsync(long userId, long typeId) { _logger.LogDebug("查询进行中的测评记录,userId: {UserId}, typeId: {TypeId}", userId, typeId); var record = await _dbContext.AssessmentRecords .AsNoTracking() .Where(r => r.UserId == userId && r.AssessmentTypeId == typeId && (r.Status == 1 || r.Status == 2) && !r.IsDeleted) .OrderByDescending(r => r.CreateTime) .Select(r => new PendingRecordDto { RecordId = r.Id, OrderId = r.OrderId, AssessmentTypeId = r.AssessmentTypeId, Name = r.Name, Phone = r.Phone, Gender = r.Gender, Age = r.Age, EducationStage = r.EducationStage, Province = r.Province, City = r.City, District = r.District, Status = r.Status, CreateTime = r.CreateTime }) .FirstOrDefaultAsync(); if (record != null) { _logger.LogInformation("找到进行中的测评记录,userId: {UserId}, recordId: {RecordId}, status: {Status}", userId, record.RecordId, record.Status); } return record; } }