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
}