using System.Text.Json; using ClosedXML.Excel; using MiAssessment.Admin.Business.Data; using MiAssessment.Admin.Business.Entities; using MiAssessment.Admin.Business.Models; using MiAssessment.Admin.Business.Models.AssessmentRecord; using MiAssessment.Admin.Business.Models.Common; using MiAssessment.Admin.Business.Services.Interfaces; using MiAssessment.Core.Interfaces; using MiAssessment.Core.Models; using MiAssessment.Core.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace MiAssessment.Admin.Business.Services; /// /// 测评记录服务实现 /// public class AssessmentRecordService : IAssessmentRecordService { private readonly AdminBusinessDbContext _dbContext; private readonly ILogger _logger; private readonly IRedisService _redisService; /// /// 状态名称映射 /// private static readonly Dictionary StatusNames = new() { { 1, "待测评" }, { 2, "测评中" }, { 3, "生成中" }, { 4, "已完成" }, { 5, "生成失败" } }; /// /// 学历阶段名称映射 /// private static readonly Dictionary EducationStageNames = new() { { 1, "小学及以下" }, { 2, "初中" }, { 3, "高中" }, { 4, "大专" }, { 5, "本科" }, { 6, "研究生及以上" } }; /// /// 性别名称映射 /// private static readonly Dictionary GenderNames = new() { { 1, "男" }, { 2, "女" } }; /// /// 构造函数 /// /// 数据库上下文 /// 日志记录器 /// Redis服务 public AssessmentRecordService( AdminBusinessDbContext dbContext, ILogger logger, IRedisService redisService) { _dbContext = dbContext; _logger = logger; _redisService = redisService; } /// public async Task> GetRecordListAsync(AssessmentRecordQueryRequest request) { // 构建查询,过滤软删除记录,使用 AsNoTracking 提高只读查询性能 var query = _dbContext.AssessmentRecords .AsNoTracking() .Where(r => !r.IsDeleted); // 应用过滤条件 query = ApplyRecordQueryFilters(query, request); // 获取总数 var total = await query.CountAsync(); // 分页查询,按创建时间降序排列 // 使用左连接获取关联数据(User、AssessmentType、Order) var items = await query .OrderByDescending(r => r.CreateTime) .Skip(request.Skip) .Take(request.PageSize) .Select(r => new AssessmentRecordDto { Id = r.Id, UserId = r.UserId, UserNickname = r.User != null ? r.User.Nickname : null, OrderId = r.OrderId, OrderNo = r.Order != null ? r.Order.OrderNo : null, AssessmentTypeId = r.AssessmentTypeId, AssessmentTypeName = r.AssessmentType != null ? r.AssessmentType.Name : null, Name = r.Name, Phone = r.Phone, Gender = r.Gender, GenderName = GetGenderName(r.Gender), Age = r.Age, EducationStage = r.EducationStage, EducationStageName = GetEducationStageName(r.EducationStage), Province = r.Province, City = r.City, District = r.District, Status = r.Status, StatusName = GetStatusName(r.Status), StartTime = r.StartTime, SubmitTime = r.SubmitTime, CompleteTime = r.CompleteTime, CreateTime = r.CreateTime }) .ToListAsync(); _logger.LogInformation("查询测评记录列表成功,总数: {Total}, 当前页数量: {Count}", total, items.Count); return PagedResult.Create(items, total, request.Page, request.PageSize); } /// public async Task GetRecordDetailAsync(long id) { // 查询测评记录,包含关联的用户、订单、测评类型信息 var record = await _dbContext.AssessmentRecords .AsNoTracking() .Include(r => r.User) .Include(r => r.Order) .Include(r => r.AssessmentType) .Where(r => r.Id == id && !r.IsDeleted) .FirstOrDefaultAsync(); // 如果记录不存在或已软删除,返回 null(调用方处理错误码 3241) if (record == null) { _logger.LogWarning("测评记录不存在,ID: {RecordId}", id); return null; } // 查询答案列表,按题号升序排列,并关联题目获取题目内容 var answers = await _dbContext.AssessmentAnswers .AsNoTracking() .Include(a => a.Question) .Where(a => a.RecordId == id) .OrderBy(a => a.QuestionNo) .Select(a => new AnswerDetailDto { Id = a.Id, QuestionId = a.QuestionId, QuestionNo = a.QuestionNo, QuestionContent = a.Question != null ? a.Question.Content : string.Empty, AnswerValue = a.AnswerValue, CreateTime = a.CreateTime }) .ToListAsync(); // 查询结果列表,关联分类获取分类名称和分类类型名称 var results = await _dbContext.AssessmentResults .AsNoTracking() .Include(r => r.Category) .Where(r => r.RecordId == id) .Select(r => new ResultDetailDto { Id = r.Id, CategoryId = r.CategoryId, CategoryName = r.Category != null ? r.Category.Name : string.Empty, CategoryTypeName = r.Category != null ? GetCategoryTypeName(r.Category.CategoryType) : string.Empty, Score = r.Score, MaxScore = r.MaxScore, Percentage = r.Percentage, Rank = r.Rank, StarLevel = r.StarLevel, CreateTime = r.CreateTime }) .ToListAsync(); // 构建详情 DTO var detail = new AssessmentRecordDetailDto { Id = record.Id, UserId = record.UserId, UserNickname = record.User?.Nickname, OrderId = record.OrderId, OrderNo = record.Order?.OrderNo, AssessmentTypeId = record.AssessmentTypeId, AssessmentTypeName = record.AssessmentType?.Name, Name = record.Name, Phone = record.Phone, Gender = record.Gender, GenderName = GetGenderName(record.Gender), Age = record.Age, EducationStage = record.EducationStage, EducationStageName = GetEducationStageName(record.EducationStage), Province = record.Province, City = record.City, District = record.District, Status = record.Status, StatusName = GetStatusName(record.Status), StartTime = record.StartTime, SubmitTime = record.SubmitTime, CompleteTime = record.CompleteTime, CreateTime = record.CreateTime, Answers = answers, Results = results }; _logger.LogInformation("查询测评记录详情成功,ID: {RecordId}, 答案数: {AnswerCount}, 结果数: {ResultCount}", id, answers.Count, results.Count); return detail; } /// public async Task GetRecordReportAsync(long id) { // 查询测评记录,包含关联的用户、测评类型信息 var record = await _dbContext.AssessmentRecords .AsNoTracking() .Include(r => r.User) .Include(r => r.AssessmentType) .Where(r => r.Id == id && !r.IsDeleted) .FirstOrDefaultAsync(); // 如果记录不存在或已软删除,返回 null(调用方处理错误码 3241) if (record == null) { _logger.LogWarning("测评记录不存在,ID: {RecordId}", id); return null; } // 如果记录状态不是 4(已完成),返回 null(调用方处理错误码 3242) if (record.Status != 4) { _logger.LogWarning("测评报告尚未生成,记录ID: {RecordId}, 当前状态: {Status}", id, record.Status); return null; } // 查询结果列表,关联分类获取分类信息 var results = await _dbContext.AssessmentResults .AsNoTracking() .Include(r => r.Category) .Where(r => r.RecordId == id) .ToListAsync(); // 获取所有分类ID,用于查询父分类(分类类型) var categoryIds = results.Select(r => r.CategoryId).Distinct().ToList(); // 查询所有相关分类 var categories = await _dbContext.ReportCategories .AsNoTracking() .Where(c => categoryIds.Contains(c.Id) && !c.IsDeleted) .ToListAsync(); // 获取所有父分类ID(分类类型) var parentIds = categories.Where(c => c.ParentId > 0).Select(c => c.ParentId).Distinct().ToList(); // 查询父分类(分类类型) var parentCategories = await _dbContext.ReportCategories .AsNoTracking() .Where(c => parentIds.Contains(c.Id) && !c.IsDeleted) .ToDictionaryAsync(c => c.Id, c => c); // 查询结论,根据分类ID和星级匹配 // 优先从 assessment_record_conclusions 读取记录级别结论 var recordConclusions = await _dbContext.AssessmentRecordConclusions .AsNoTracking() .Where(c => c.RecordId == id && !c.IsDeleted) .ToListAsync(); // 构建结论字典,key 为 CategoryId // 如果有记录级别结论,使用记录级别的;否则回退到模板结论 Dictionary conclusionDict; if (recordConclusions.Count > 0) { // 使用记录级别结论 conclusionDict = recordConclusions .GroupBy(c => c.CategoryId) .ToDictionary( g => g.Key, g => ((long?)g.First().Id, (string?)g.First().Content)); } else { // 回退到模板结论 var conclusions = await _dbContext.ReportConclusions .AsNoTracking() .Where(c => categoryIds.Contains(c.CategoryId) && !c.IsDeleted) .ToListAsync(); // 需要根据星级匹配结论类型 conclusionDict = new Dictionary(); var conclusionsByKey = conclusions .GroupBy(c => (c.CategoryId, c.ConclusionType)) .ToDictionary(g => g.Key, g => g.First().Content); // 为每个结果匹配模板结论 foreach (var result in results) { var conclusionType = MapStarLevelToConclusionType(result.StarLevel); if (conclusionsByKey.TryGetValue((result.CategoryId, conclusionType), out var content)) { conclusionDict[result.CategoryId] = (null, content); } } } // 按分类类型分组结果 var resultGroups = results .Where(r => r.Category != null) .GroupBy(r => { var category = categories.FirstOrDefault(c => c.Id == r.CategoryId); if (category == null) return (0L, "未知"); // 如果有父分类,使用父分类作为分组依据 if (category.ParentId > 0 && parentCategories.TryGetValue(category.ParentId, out var parent)) { return (parent.Id, parent.Name); } // 否则使用分类类型名称 return (category.Id, GetCategoryTypeName(category.CategoryType)); }) .Select(g => new ReportCategoryGroup { CategoryTypeId = g.Key.Item1, CategoryTypeName = g.Key.Item2, Items = g.Select(r => { var category = categories.FirstOrDefault(c => c.Id == r.CategoryId); conclusionDict.TryGetValue(r.CategoryId, out var conclusionData); return new ReportCategoryItem { CategoryId = r.CategoryId, CategoryName = category?.Name ?? string.Empty, Score = r.Score, MaxScore = r.MaxScore, Percentage = r.Percentage, StarLevel = r.StarLevel, ConclusionId = conclusionData.conclusionId, ConclusionContent = conclusionData.content }; }).ToList() }) .ToList(); // 构建报告 DTO var report = new AssessmentReportDto { Id = record.Id, UserId = record.UserId, UserNickname = record.User?.Nickname, AssessmentTypeId = record.AssessmentTypeId, AssessmentTypeName = record.AssessmentType?.Name, Name = record.Name, Phone = record.Phone, Gender = record.Gender, GenderName = GetGenderName(record.Gender), Age = record.Age, EducationStage = record.EducationStage, EducationStageName = GetEducationStageName(record.EducationStage), Province = record.Province, City = record.City, District = record.District, Status = record.Status, StatusName = GetStatusName(record.Status), StartTime = record.StartTime, SubmitTime = record.SubmitTime, CompleteTime = record.CompleteTime, CreateTime = record.CreateTime, ResultGroups = resultGroups }; _logger.LogInformation("查询测评报告成功,ID: {RecordId}, 分组数: {GroupCount}", id, resultGroups.Count); return report; } /// /// 导出数量上限 /// private const int ExportMaxCount = 10000; /// public async Task ExportRecordsAsync(AssessmentRecordQueryRequest request) { // 构建查询,过滤软删除记录 var query = _dbContext.AssessmentRecords .AsNoTracking() .Where(r => !r.IsDeleted); // 应用与列表查询相同的过滤条件 (Requirements 4.1) query = ApplyRecordQueryFilters(query, request); // 获取总数,检查是否超过导出上限 (Requirements 4.3) var total = await query.CountAsync(); if (total > ExportMaxCount) { _logger.LogWarning("导出数据量过大,总数: {Total}, 上限: {MaxCount}", total, ExportMaxCount); throw new BusinessException(ErrorCodes.ExportDataTooLarge, "导出数据量过大,请缩小查询范围"); } // 查询所有匹配记录,按创建时间降序排列 // 包含关联数据(User、AssessmentType)用于获取用户昵称和测评类型名称 var records = await query .OrderByDescending(r => r.CreateTime) .Select(r => new { r.Id, UserNickname = r.User != null ? r.User.Nickname : null, AssessmentTypeName = r.AssessmentType != null ? r.AssessmentType.Name : null, r.Name, r.Phone, r.Gender, r.Age, r.EducationStage, r.Province, r.City, r.District, r.Status, r.StartTime, r.SubmitTime, r.CompleteTime, r.CreateTime }) .ToListAsync(); // 使用 ClosedXML 生成 Excel 文件 (Requirements 4.4) using var workbook = new XLWorkbook(); var worksheet = workbook.Worksheets.Add("测评记录"); // 添加表头行 (Requirements 4.2) var headers = new[] { "记录ID", "用户昵称", "测评类型", "姓名", "手机号", "性别", "年龄", "学历阶段", "省份", "城市", "区县", "状态", "开始时间", "提交时间", "完成时间", "创建时间" }; for (var i = 0; i < headers.Length; i++) { worksheet.Cell(1, i + 1).Value = headers[i]; } // 设置表头样式 var headerRow = worksheet.Row(1); headerRow.Style.Font.Bold = true; headerRow.Style.Fill.BackgroundColor = XLColor.LightGray; // 添加数据行 var rowIndex = 2; foreach (var record in records) { worksheet.Cell(rowIndex, 1).Value = record.Id; worksheet.Cell(rowIndex, 2).Value = record.UserNickname ?? string.Empty; worksheet.Cell(rowIndex, 3).Value = record.AssessmentTypeName ?? string.Empty; worksheet.Cell(rowIndex, 4).Value = record.Name; worksheet.Cell(rowIndex, 5).Value = record.Phone; worksheet.Cell(rowIndex, 6).Value = GetGenderName(record.Gender); worksheet.Cell(rowIndex, 7).Value = record.Age; worksheet.Cell(rowIndex, 8).Value = GetEducationStageName(record.EducationStage); worksheet.Cell(rowIndex, 9).Value = record.Province; worksheet.Cell(rowIndex, 10).Value = record.City; worksheet.Cell(rowIndex, 11).Value = record.District; worksheet.Cell(rowIndex, 12).Value = GetStatusName(record.Status); worksheet.Cell(rowIndex, 13).Value = record.StartTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? string.Empty; worksheet.Cell(rowIndex, 14).Value = record.SubmitTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? string.Empty; worksheet.Cell(rowIndex, 15).Value = record.CompleteTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? string.Empty; worksheet.Cell(rowIndex, 16).Value = record.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"); rowIndex++; } // 自动调整列宽 worksheet.Columns().AdjustToContents(); // 将工作簿保存到内存流并返回字节数组 using var stream = new MemoryStream(); workbook.SaveAs(stream); _logger.LogInformation("导出测评记录成功,总数: {Total}", records.Count); return stream.ToArray(); } /// public async Task RegenerateReportAsync(long recordId) { // 查找记录,过滤软删除 var record = await _dbContext.AssessmentRecords .FirstOrDefaultAsync(r => r.Id == recordId && !r.IsDeleted); if (record == null) { throw new BusinessException(ErrorCodes.AssessmentRecordNotFound, "测评记录不存在"); } // 校验状态:仅允许状态为 3(生成中)或 5(生成失败)的记录重新生成 if (record.Status != 3 && record.Status != 5) { throw new BusinessException(ErrorCodes.InvalidOperation, "当前状态不允许重新生成"); } // 重置状态为 3(生成中) record.Status = 3; record.UpdateTime = DateTime.Now; // 清除已有的测评结果数据 var existingResults = await _dbContext.AssessmentResults .Where(r => r.RecordId == recordId) .ToListAsync(); if (existingResults.Count > 0) { _dbContext.AssessmentResults.RemoveRange(existingResults); } // 构造队列消息并入队 var message = new ReportQueueMessage { RecordId = recordId, RetryCount = 0, EnqueueTime = DateTime.Now }; var json = JsonSerializer.Serialize(message); await _redisService.ListLeftPushAsync(ReportQueueProducer.ReportQueueKey, json); await _dbContext.SaveChangesAsync(); _logger.LogInformation("重新生成报告已入队,记录ID: {RecordId}", recordId); } /// public async Task BatchRegenerateReportAsync(List recordIds) { // 校验参数 if (recordIds == null || recordIds.Count == 0) { throw new BusinessException(ErrorCodes.ParamError, "记录ID列表不能为空"); } var successCount = 0; var skippedCount = 0; foreach (var recordId in recordIds) { try { // 查找记录,过滤软删除 var record = await _dbContext.AssessmentRecords .FirstOrDefaultAsync(r => r.Id == recordId && !r.IsDeleted); // 记录不存在,跳过 if (record == null) { _logger.LogWarning("批量重新生成:记录不存在,ID: {RecordId}", recordId); skippedCount++; continue; } // 状态不符,跳过 if (record.Status != 3 && record.Status != 5) { _logger.LogWarning("批量重新生成:状态不符,ID: {RecordId}, 状态: {Status}", recordId, record.Status); skippedCount++; continue; } // 重置状态为 3(生成中) record.Status = 3; record.UpdateTime = DateTime.Now; // 清除已有的测评结果数据 var existingResults = await _dbContext.AssessmentResults .Where(r => r.RecordId == recordId) .ToListAsync(); if (existingResults.Count > 0) { _dbContext.AssessmentResults.RemoveRange(existingResults); } // 构造队列消息并入队 var message = new ReportQueueMessage { RecordId = recordId, RetryCount = 0, EnqueueTime = DateTime.Now }; var json = JsonSerializer.Serialize(message); await _redisService.ListLeftPushAsync(ReportQueueProducer.ReportQueueKey, json); successCount++; } catch (Exception ex) { _logger.LogError(ex, "批量重新生成:处理失败,ID: {RecordId}", recordId); skippedCount++; } } // 统一保存所有变更 await _dbContext.SaveChangesAsync(); _logger.LogInformation("批量重新生成完成,成功: {SuccessCount}, 跳过: {SkippedCount}", successCount, skippedCount); return new BatchRegenerateResult { SuccessCount = successCount, SkippedCount = skippedCount }; } /// public async Task DeleteRecordAsync(long id) { var record = await _dbContext.AssessmentRecords .FirstOrDefaultAsync(r => r.Id == id && !r.IsDeleted); if (record == null) { throw new BusinessException(ErrorCodes.AssessmentRecordNotFound, "测评记录不存在"); } // 软删除 record.IsDeleted = true; record.UpdateTime = DateTime.Now; await _dbContext.SaveChangesAsync(); _logger.LogInformation("测评记录已删除,ID: {RecordId}", id); } /// public async Task UpdateRecordConclusionAsync(UpdateRecordConclusionRequest request) { var conclusion = await _dbContext.AssessmentRecordConclusions .FirstOrDefaultAsync(c => c.Id == request.Id && !c.IsDeleted); if (conclusion == null) { throw new BusinessException(ErrorCodes.ConclusionNotFound, "结论记录不存在"); } conclusion.Content = request.Content; conclusion.UpdateTime = DateTime.Now; await _dbContext.SaveChangesAsync(); _logger.LogInformation("更新测评记录结论成功,结论ID: {ConclusionId}, 记录ID: {RecordId}", request.Id, conclusion.RecordId); } #region 私有方法 /// /// 分类类型名称映射 /// private static readonly Dictionary CategoryTypeNames = new() { { 1, "八大智能" }, { 2, "个人特质" }, { 3, "细分能力" }, { 4, "先天学习类型" }, { 5, "学习关键能力" }, { 6, "科学大脑类型" }, { 7, "性格类型" }, { 8, "未来发展能力" } }; /// /// 应用测评记录查询过滤条件 /// /// 查询 /// 请求 /// 过滤后的查询 private static IQueryable ApplyRecordQueryFilters( IQueryable query, AssessmentRecordQueryRequest request) { // 按用户ID筛选 if (request.UserId.HasValue) { query = query.Where(r => r.UserId == request.UserId.Value); } // 按测评类型ID筛选 if (request.AssessmentTypeId.HasValue) { query = query.Where(r => r.AssessmentTypeId == request.AssessmentTypeId.Value); } // 按状态筛选 if (request.Status.HasValue) { query = query.Where(r => r.Status == request.Status.Value); } // 按创建时间范围筛选 - 开始日期 if (request.StartDate.HasValue) { query = query.Where(r => r.CreateTime >= request.StartDate.Value); } // 按创建时间范围筛选 - 结束日期(包含当天) if (request.EndDate.HasValue) { var endDate = request.EndDate.Value.AddDays(1); query = query.Where(r => r.CreateTime < endDate); } return query; } /// /// 获取状态名称 /// /// 状态值 /// 状态名称 private static string GetStatusName(int status) { return StatusNames.TryGetValue(status, out var name) ? name : "未知"; } /// /// 获取学历阶段名称 /// /// 学历阶段值 /// 学历阶段名称 private static string GetEducationStageName(int educationStage) { return EducationStageNames.TryGetValue(educationStage, out var name) ? name : "未知"; } /// /// 获取性别名称 /// /// 性别值 /// 性别名称 private static string GetGenderName(int gender) { return GenderNames.TryGetValue(gender, out var name) ? name : "未知"; } /// /// 获取分类类型名称 /// /// 分类类型值 /// 分类类型名称 private static string GetCategoryTypeName(int categoryType) { return CategoryTypeNames.TryGetValue(categoryType, out var name) ? name : "未知"; } /// /// 将星级映射到结论类型 /// StarLevel (1-5) 映射到 ConclusionType (1-4): /// StarLevel 5 → ConclusionType 1 (最强) /// StarLevel 4 → ConclusionType 2 (较强) /// StarLevel 3 → ConclusionType 2 (较强) /// StarLevel 2 → ConclusionType 3 (较弱) /// StarLevel 1 → ConclusionType 4 (最弱) /// /// 星级(1-5) /// 结论类型(1-4) private static int MapStarLevelToConclusionType(int starLevel) { return starLevel switch { 5 => 1, // 最强 4 => 2, // 较强 3 => 2, // 较强 2 => 3, // 较弱 1 => 4, // 最弱 _ => 2 // 默认较强 }; } #endregion }