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;
}
}