|
|
|
|
@ -0,0 +1,569 @@
|
|
|
|
|
package com.ruoyi.basic.service.impl;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
|
import cn.hutool.extra.servlet.ServletUtil;
|
|
|
|
|
import cn.hutool.http.HttpUtil;
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
|
|
import com.ruoyi.basic.domain.dto.AnswerSubmitDTO;
|
|
|
|
|
import com.ruoyi.basic.domain.dto.ConstitutionScoreDTO;
|
|
|
|
|
import com.ruoyi.basic.domain.model.*;
|
|
|
|
|
import com.ruoyi.basic.domain.question.*;
|
|
|
|
|
import com.ruoyi.basic.mapper.*;
|
|
|
|
|
import com.ruoyi.basic.service.QuestionRuleService;
|
|
|
|
|
import com.ruoyi.basic.service.QuestionnaireService;
|
|
|
|
|
import com.ruoyi.common.utils.SecurityUtils;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.cache.annotation.Cacheable;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
@Service
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class QuestionnaireServiceImpl extends ServiceImpl<QuestionAnswerSheetMapper, QuestionAnswerSheet>
|
|
|
|
|
implements QuestionnaireService {
|
|
|
|
|
|
|
|
|
|
private final QuestionSurveyMapper surveyMapper;
|
|
|
|
|
private final QuestionConstitutionTypeMapper constitutionTypeMapper;
|
|
|
|
|
private final QuestionQuestionMapper questionMapper;
|
|
|
|
|
private final QuestionOptionMapper optionMapper;
|
|
|
|
|
private final QuestionAnswerDetailMapper answerDetailMapper;
|
|
|
|
|
private final QuestionConstitutionScoreMapper constitutionScoreMapper;
|
|
|
|
|
private final QuestionRuleService ruleService;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
|
|
public AnswerResultVO submitAnswers(AnswerSubmitDTO dto, HttpServletRequest request) {
|
|
|
|
|
// 1. 校验问卷
|
|
|
|
|
QuestionSurvey survey = surveyMapper.selectById(dto.getSurveyId());
|
|
|
|
|
if (survey == null || survey.getStatus() != 1) {
|
|
|
|
|
throw new RuntimeException("问卷不存在或未发布");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 保存答卷主表
|
|
|
|
|
QuestionAnswerSheet sheet = new QuestionAnswerSheet();
|
|
|
|
|
sheet.setSurveyId(dto.getSurveyId());
|
|
|
|
|
sheet.setUserId(SecurityUtils.getAppLoginUser().getAppUserId());
|
|
|
|
|
sheet.setSessionId(StrUtil.blankToDefault(dto.getSessionId(), UUID.randomUUID().toString()));
|
|
|
|
|
sheet.setSubmitTime(LocalDateTime.now());
|
|
|
|
|
sheet.setIpAddress(ServletUtil.getClientIP(request));
|
|
|
|
|
this.save(sheet);
|
|
|
|
|
Long sheetId = sheet.getId();
|
|
|
|
|
|
|
|
|
|
// 3. 批量保存答案明细
|
|
|
|
|
List<QuestionAnswerDetail> details = dto.getAnswers().stream()
|
|
|
|
|
.map(item -> {
|
|
|
|
|
QuestionAnswerDetail detail = new QuestionAnswerDetail();
|
|
|
|
|
detail.setSheetId(sheetId);
|
|
|
|
|
detail.setQuestionId(item.getQuestionId());
|
|
|
|
|
detail.setOptionId(item.getOptionId());
|
|
|
|
|
return detail;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
answerDetailMapper.insertBatch(details);
|
|
|
|
|
|
|
|
|
|
// 4. 计算各模块总分
|
|
|
|
|
List<QuestionConstitutionScore> scores = computeConstitutionScores(sheetId);
|
|
|
|
|
if (CollUtil.isNotEmpty(scores)) {
|
|
|
|
|
constitutionScoreMapper.insertBatch(scores);
|
|
|
|
|
} else {
|
|
|
|
|
scores = Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. 获取所有模块信息(用于构建上下文和VO)
|
|
|
|
|
// 获取所有模块信息
|
|
|
|
|
List<QuestionConstitutionType> constitutions = constitutionTypeMapper.selectList(null);
|
|
|
|
|
Map<Long, QuestionConstitutionType> constitutionMap = constitutions.stream()
|
|
|
|
|
.collect(Collectors.toMap(QuestionConstitutionType::getId, c -> c));
|
|
|
|
|
// 构建得分映射(体质ID -> 总分)
|
|
|
|
|
Map<Long, Integer> scoreById = scores.stream()
|
|
|
|
|
.collect(Collectors.toMap(QuestionConstitutionScore::getConstitutionId, QuestionConstitutionScore::getTotalScore));
|
|
|
|
|
|
|
|
|
|
// 动态规则匹配
|
|
|
|
|
// AnswerResultVO result = determineResultByRules(scoreById, constitutionMap, dto.getSurveyId());
|
|
|
|
|
AnswerResultVO result = determineResult(scores,sheetId);
|
|
|
|
|
|
|
|
|
|
// 5.1 更新答卷结果
|
|
|
|
|
sheet.setResultType(result.getResultType());
|
|
|
|
|
sheet.setResultText(result.getResultText());
|
|
|
|
|
this.updateById(sheet);
|
|
|
|
|
// 更新答卷、返回结果
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取问卷完整信息(带缓存)
|
|
|
|
|
* @return 问卷VO
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
// @Cacheable(value = "questionnaire", unless = "#result == null")
|
|
|
|
|
public QuestionnaireVO getQuestionnaire() {
|
|
|
|
|
// 1. 查询问卷基本信息
|
|
|
|
|
// QuestionSurvey survey = surveyMapper.selectById(surveyId);
|
|
|
|
|
QuestionSurvey survey = surveyMapper.selectOne(new QueryWrapper<QuestionSurvey>()
|
|
|
|
|
.eq("status", 1)
|
|
|
|
|
.orderByDesc("create_time")
|
|
|
|
|
.last("LIMIT 1")
|
|
|
|
|
);
|
|
|
|
|
if (survey == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
Long surveyId=survey.getId();
|
|
|
|
|
// 2. 查询该问卷下的所有体质类型(按sort_order排序)
|
|
|
|
|
List<QuestionConstitutionType> constitutions = constitutionTypeMapper.selectList(
|
|
|
|
|
new LambdaQueryWrapper<QuestionConstitutionType>()
|
|
|
|
|
.eq(QuestionConstitutionType::getSurveyId, surveyId)
|
|
|
|
|
.orderByAsc(QuestionConstitutionType::getSortOrder)
|
|
|
|
|
);
|
|
|
|
|
if (CollUtil.isEmpty(constitutions)) {
|
|
|
|
|
QuestionnaireVO vo = new QuestionnaireVO();
|
|
|
|
|
vo.setSurveyId(surveyId);
|
|
|
|
|
vo.setTitle(survey.getTitle());
|
|
|
|
|
vo.setDescription(survey.getDescription());
|
|
|
|
|
vo.setConstitutions(Collections.emptyList());
|
|
|
|
|
return vo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 查询所有体质对应的题目(批量,避免N+1)
|
|
|
|
|
List<Long> constitutionIds = constitutions.stream()
|
|
|
|
|
.map(QuestionConstitutionType::getId)
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
List<QuestionQuestion> allQuestions = questionMapper.selectList(
|
|
|
|
|
new LambdaQueryWrapper<QuestionQuestion>()
|
|
|
|
|
.in(QuestionQuestion::getConstitutionId, constitutionIds)
|
|
|
|
|
.orderByAsc(QuestionQuestion::getConstitutionId, QuestionQuestion::getSerialNumber)
|
|
|
|
|
);
|
|
|
|
|
if (CollUtil.isEmpty(allQuestions)) {
|
|
|
|
|
// 没有题目则返回空列表
|
|
|
|
|
QuestionnaireVO vo = new QuestionnaireVO();
|
|
|
|
|
vo.setSurveyId(surveyId);
|
|
|
|
|
vo.setTitle(survey.getTitle());
|
|
|
|
|
vo.setDescription(survey.getDescription());
|
|
|
|
|
vo.setConstitutions(buildConstitutionVOs(constitutions, Collections.emptyList(), Collections.emptyList()));
|
|
|
|
|
return vo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 查询所有题目的选项(每道题固定3个选项,也可批量查)
|
|
|
|
|
List<Long> questionIds = allQuestions.stream()
|
|
|
|
|
.map(QuestionQuestion::getId)
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
List<QuestionOption> allOptions = optionMapper.selectList(
|
|
|
|
|
new LambdaQueryWrapper<QuestionOption>()
|
|
|
|
|
.in(QuestionOption::getQuestionId, questionIds)
|
|
|
|
|
.orderByAsc(QuestionOption::getQuestionId, QuestionOption::getSortOrder)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 5. 构建 Map 结构便于组装
|
|
|
|
|
Map<Long, List<QuestionQuestion>> questionsByConstitutionId = allQuestions.stream()
|
|
|
|
|
.collect(Collectors.groupingBy(QuestionQuestion::getConstitutionId));
|
|
|
|
|
Map<Long, List<QuestionOption>> optionsByQuestionId = allOptions.stream()
|
|
|
|
|
.collect(Collectors.groupingBy(QuestionOption::getQuestionId));
|
|
|
|
|
|
|
|
|
|
// 6. 组装 VO
|
|
|
|
|
List<QuestionnaireVO.ConstitutionVO> constitutionVOs = constitutions.stream().map(ct -> {
|
|
|
|
|
QuestionnaireVO.ConstitutionVO ctVO = new QuestionnaireVO.ConstitutionVO();
|
|
|
|
|
ctVO.setConstitutionId(ct.getId());
|
|
|
|
|
ctVO.setName(ct.getName());
|
|
|
|
|
ctVO.setAlias(ct.getAlias());
|
|
|
|
|
ctVO.setDescription(ct.getDescription());
|
|
|
|
|
ctVO.setSortOrder(ct.getSortOrder());
|
|
|
|
|
|
|
|
|
|
List<QuestionQuestion> questions = questionsByConstitutionId.getOrDefault(ct.getId(), Collections.emptyList());
|
|
|
|
|
List<QuestionnaireVO.QuestionVO> questionVOs = questions.stream().map(q -> {
|
|
|
|
|
QuestionnaireVO.QuestionVO qVO = new QuestionnaireVO.QuestionVO();
|
|
|
|
|
qVO.setQuestionId(q.getId());
|
|
|
|
|
qVO.setSerialNumber(q.getSerialNumber());
|
|
|
|
|
qVO.setContent(q.getContent());
|
|
|
|
|
qVO.setFullScore(q.getFullScore());
|
|
|
|
|
|
|
|
|
|
List<QuestionOption> options = optionsByQuestionId.getOrDefault(q.getId(),Collections.emptyList());
|
|
|
|
|
List<QuestionnaireVO.OptionVO> optionVOs = options.stream().map(opt -> {
|
|
|
|
|
QuestionnaireVO.OptionVO optVO = new QuestionnaireVO.OptionVO();
|
|
|
|
|
optVO.setOptionId(opt.getId());
|
|
|
|
|
optVO.setOptionText(opt.getOptionText());
|
|
|
|
|
optVO.setScore(opt.getScore());
|
|
|
|
|
optVO.setSortOrder(opt.getSortOrder());
|
|
|
|
|
return optVO;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
qVO.setOptions(optionVOs);
|
|
|
|
|
return qVO;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
ctVO.setQuestions(questionVOs);
|
|
|
|
|
return ctVO;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
|
QuestionnaireVO vo = new QuestionnaireVO();
|
|
|
|
|
vo.setSurveyId(surveyId);
|
|
|
|
|
vo.setTitle(survey.getTitle());
|
|
|
|
|
vo.setDescription(survey.getDescription());
|
|
|
|
|
vo.setConstitutions(constitutionVOs);
|
|
|
|
|
return vo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public AnswerResultVO getResultBySheetId() {
|
|
|
|
|
QuestionAnswerSheet sheet = this.getOne(new QueryWrapper<QuestionAnswerSheet>()
|
|
|
|
|
.eq("app_user_id",SecurityUtils.getAppLoginUser().getAppUserId() )
|
|
|
|
|
.orderByDesc("submit_time")
|
|
|
|
|
.last("LIMIT 1")
|
|
|
|
|
);
|
|
|
|
|
if (sheet == null) {
|
|
|
|
|
throw new RuntimeException("答卷不存在");
|
|
|
|
|
}
|
|
|
|
|
Long sheetId=sheet.getId();
|
|
|
|
|
List<QuestionConstitutionScore> scores = constitutionScoreMapper.selectList(
|
|
|
|
|
new LambdaQueryWrapper<QuestionConstitutionScore>().eq(QuestionConstitutionScore::getSheetId, sheetId));
|
|
|
|
|
List<QuestionConstitutionType> constitutions = constitutionTypeMapper.selectList(null);
|
|
|
|
|
Map<Long, QuestionConstitutionType> constitutionMap = constitutions.stream()
|
|
|
|
|
.collect(Collectors.toMap(QuestionConstitutionType::getId, c -> c));
|
|
|
|
|
AnswerResultVO vo = new AnswerResultVO();
|
|
|
|
|
vo.setResultType(sheet.getResultType());
|
|
|
|
|
vo.setResultText(sheet.getResultText());
|
|
|
|
|
List<ConstitutionScoreDTO> dtoList = scores.stream().map(score -> {
|
|
|
|
|
QuestionConstitutionType ct = constitutionMap.get(score.getConstitutionId());
|
|
|
|
|
ConstitutionScoreDTO dto = new ConstitutionScoreDTO();
|
|
|
|
|
dto.setConstitutionAlias(ct.getAlias());
|
|
|
|
|
dto.setConstitutionName(ct.getName());
|
|
|
|
|
dto.setTotalScore(score.getTotalScore());
|
|
|
|
|
return dto;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
vo.setConstitutionScores(dtoList);
|
|
|
|
|
return vo;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 计算答卷中各体质的得分
|
|
|
|
|
*/
|
|
|
|
|
private List<QuestionConstitutionScore> computeConstitutionScores(Long sheetId) {
|
|
|
|
|
// 查询该答卷的所有答案
|
|
|
|
|
List<QuestionAnswerDetail> details =answerDetailMapper.getBySheetId(sheetId);
|
|
|
|
|
if (CollUtil.isEmpty(details)) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
// 构建题目->选项分值的映射
|
|
|
|
|
Map<Long, Integer> optionScoreMap = details.stream()
|
|
|
|
|
.collect(Collectors.toMap(QuestionAnswerDetail::getQuestionId,
|
|
|
|
|
d -> d.getScore()));
|
|
|
|
|
|
|
|
|
|
// 按体质分组计算总分
|
|
|
|
|
Map<Long, Integer> constitutionScoreMap = new HashMap<>();
|
|
|
|
|
for (QuestionAnswerDetail detail : details) {
|
|
|
|
|
Long questionId = detail.getQuestionId();
|
|
|
|
|
QuestionQuestion question = questionMapper.selectById(questionId);
|
|
|
|
|
Long constitutionId = question.getConstitutionId();
|
|
|
|
|
Integer score = optionScoreMap.getOrDefault(questionId, 0);
|
|
|
|
|
constitutionScoreMap.merge(constitutionId, score, Integer::sum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 转为实体列表
|
|
|
|
|
return constitutionScoreMap.entrySet().stream()
|
|
|
|
|
.map(entry -> {
|
|
|
|
|
QuestionConstitutionScore score = new QuestionConstitutionScore();
|
|
|
|
|
score.setSheetId(sheetId);
|
|
|
|
|
score.setConstitutionId(entry.getKey());
|
|
|
|
|
score.setTotalScore(entry.getValue());
|
|
|
|
|
return score;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据得分规则判定体质结果
|
|
|
|
|
*/
|
|
|
|
|
private AnswerResultVO determineResult(List<QuestionConstitutionScore> scores, Long sheetId) {
|
|
|
|
|
// 获取所有体质信息(含 alias, name, isBalanced)
|
|
|
|
|
List<QuestionConstitutionType> constitutions = constitutionTypeMapper.selectList(null);
|
|
|
|
|
Map<Long, QuestionConstitutionType> constitutionMap = constitutions.stream()
|
|
|
|
|
.collect(Collectors.toMap(QuestionConstitutionType::getId, c -> c));
|
|
|
|
|
|
|
|
|
|
// 构建体质别名 -> 总分
|
|
|
|
|
Map<String, Integer> scoreMap = new HashMap<>();
|
|
|
|
|
for (QuestionConstitutionScore score : scores) {
|
|
|
|
|
QuestionConstitutionType ct = constitutionMap.get(score.getConstitutionId());
|
|
|
|
|
if (ct != null) {
|
|
|
|
|
scoreMap.put(ct.getAlias(), score.getTotalScore());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Integer pingheScore = scoreMap.getOrDefault("pinghe", 0);
|
|
|
|
|
// 偏颇体质(排除平和)
|
|
|
|
|
Map<String, Integer> biasedScores = new HashMap<>(scoreMap);
|
|
|
|
|
biasedScores.remove("pinghe");
|
|
|
|
|
|
|
|
|
|
// 规则1: 平和体质(总分≥8 且所有偏颇<6)
|
|
|
|
|
boolean isPurePinghe = pingheScore >= 8 && biasedScores.values().stream().allMatch(v -> v < 6);
|
|
|
|
|
if (isPurePinghe) {
|
|
|
|
|
return buildResult("pinghe", "恭喜您,您的体质为“平和体质”,非常完美。请联系您的馆主或教练,领取训练及生活方案,继续保持哦。", scores, constitutionMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 规则2: 某偏颇体质总分 ≥ 8
|
|
|
|
|
Optional<String> highKey = biasedScores.entrySet().stream()
|
|
|
|
|
.filter(e -> e.getValue() >= 8) .map(Map.Entry::getKey).findFirst();
|
|
|
|
|
if (highKey.isPresent()) {
|
|
|
|
|
String alias = highKey.get();
|
|
|
|
|
String name = constitutionMap.values().stream()
|
|
|
|
|
.filter(ct -> ct.getAlias().equals(alias)).findFirst().get().getName();
|
|
|
|
|
String text = String.format("亲爱的伽人您好,您的体质为典型%s。请联系您的馆主或教练,领取训练及生活方案,快速调整哦。", name);
|
|
|
|
|
return buildResult(alias, text, scores, constitutionMap);
|
|
|
|
|
}
|
|
|
|
|
// 规则4: 两种及以上偏颇体质分数 ≥ 7
|
|
|
|
|
long count = biasedScores.values().stream().filter(v -> v >= 7).count();
|
|
|
|
|
if (count >= 2) {
|
|
|
|
|
String text = "亲爱的伽人您好,您的体质为复合型体质典型,建议综合调理。请联系您的馆主或教练,领取训练及生活方案,快速调整哦。";
|
|
|
|
|
return buildResult("multiple", text, scores, constitutionMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 规则3: 某偏颇体质总分在 6~7 之间
|
|
|
|
|
Optional<String> midKey = biasedScores.entrySet().stream()
|
|
|
|
|
.filter(e -> e.getValue() >= 6 && e.getValue() <= 7) .map(Map.Entry::getKey).findFirst();
|
|
|
|
|
if (midKey.isPresent()) {
|
|
|
|
|
String alias = midKey.get();
|
|
|
|
|
String name = constitutionMap.values().stream()
|
|
|
|
|
.filter(ct -> ct.getAlias().equals(alias)).findFirst().get().getName().substring(0,2);
|
|
|
|
|
String text = String.format("亲爱的伽人您好,您的体质为%s倾向体质。请联系您的馆主或教练,领取训练及生活方案,快速调整哦。", name);
|
|
|
|
|
return buildResult(alias + "_tendency", text, scores, constitutionMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 默认兜底(一般不会触发)
|
|
|
|
|
return buildResult("unknown", "无法判定体质类型,请重新测评。", scores, constitutionMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 修改 determineResult 方法,使用数据库规则动态判定
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param scoreById 体质得分映射(体质ID -> 总分)
|
|
|
|
|
* @param constitutionMap 模块信息
|
|
|
|
|
* @param surveyId 问卷id
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
private AnswerResultVO determineResultByRules(Map<Long, Integer> scoreById,
|
|
|
|
|
Map<Long, QuestionConstitutionType> constitutionMap,
|
|
|
|
|
Long surveyId) {
|
|
|
|
|
List<QuestionRule> rules = ruleService.getEnabledRulesBySurveyId(surveyId);
|
|
|
|
|
if (rules.isEmpty()) {
|
|
|
|
|
return buildFallbackResult(scoreById, constitutionMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (Long aLong : scoreById.keySet()) {
|
|
|
|
|
|
|
|
|
|
Integer score = scoreById.get(aLong);
|
|
|
|
|
if (score == null) continue;
|
|
|
|
|
|
|
|
|
|
if (score >=8) {
|
|
|
|
|
//
|
|
|
|
|
scoreById.values().stream().filter(v -> v < 6).count();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
// 预计算一些统计值,供规则使用
|
|
|
|
|
Map<Long, Integer> biasedScores = new HashMap<>(scoreById);
|
|
|
|
|
// Long pingheId = constitutionMap.values().stream()
|
|
|
|
|
// .filter(ct -> ct.getIsBalanced() == 1)
|
|
|
|
|
// .findFirst().map(QuestionConstitutionType::getId).orElse(null);
|
|
|
|
|
// if (pingheId != null) {
|
|
|
|
|
// biasedScores.remove(pingheId);
|
|
|
|
|
// }
|
|
|
|
|
int maxBiasedScore = biasedScores.values().stream().mapToInt(v -> v).max().orElse(0);
|
|
|
|
|
long countBiasedGe7 = biasedScores.values().stream().filter(v -> v >= 7).count();
|
|
|
|
|
|
|
|
|
|
// 按优先级匹配规则
|
|
|
|
|
// for (QuestionRule rule : rules) {
|
|
|
|
|
// boolean matched = false;
|
|
|
|
|
// if ("constitution".equals(rule.getTargetType())) {
|
|
|
|
|
// Long targetId = rule.getTargetConstitutionId();
|
|
|
|
|
// if (targetId == null) continue;
|
|
|
|
|
// Integer targetScore = scoreById.getOrDefault(targetId, 0);
|
|
|
|
|
// // 检查目标体质得分区间
|
|
|
|
|
// boolean scoreInRange = true;
|
|
|
|
|
// if (rule.getMinScore() != null && targetScore < rule.getMinScore()) scoreInRange = false;
|
|
|
|
|
// if (rule.getMaxScore() != null && targetScore > rule.getMaxScore()) scoreInRange = false;
|
|
|
|
|
// if (!scoreInRange) continue;
|
|
|
|
|
//
|
|
|
|
|
// // 检查“其他体质最高分上限”条件
|
|
|
|
|
// boolean otherMaxOk = true;
|
|
|
|
|
// if (rule.getRequireOtherMaxScore() != null) {
|
|
|
|
|
// // 若目标体质是平和体质,则其他体质包括所有偏颇;否则其他体质指除目标外的所有体质(含平和?按业务调整)
|
|
|
|
|
//// if (pingheId != null && targetId.equals(pingheId)) {
|
|
|
|
|
//// int maxOther = biasedScores.values().stream().mapToInt(v -> v).max().orElse(0);
|
|
|
|
|
//// if (maxOther >= rule.getRequireOtherMaxScore()) otherMaxOk = false;
|
|
|
|
|
//// } else {
|
|
|
|
|
// // 非平和体质:其他体质指除自己外所有(含平和)
|
|
|
|
|
// Map<Long, Integer> otherScores = new HashMap<>(scoreById);
|
|
|
|
|
// otherScores.remove(targetId);
|
|
|
|
|
// int maxOther = otherScores.values().stream().mapToInt(v -> v).max().orElse(0);
|
|
|
|
|
// if (maxOther >= rule.getRequireOtherMaxScore()) otherMaxOk = false;
|
|
|
|
|
//// }
|
|
|
|
|
// }
|
|
|
|
|
// matched = scoreInRange && otherMaxOk;
|
|
|
|
|
// }
|
|
|
|
|
// else if ("compound".equals(rule.getTargetType())) {
|
|
|
|
|
// // 复合型规则:例如 countBiasedGe7 >= 2,可存储在 condition_json 中
|
|
|
|
|
// if (rule.getConditionJson() != null && !rule.getConditionJson().isEmpty()) {
|
|
|
|
|
// // 解析 JSON 条件,例如 {"countBiasedGe7": 2}
|
|
|
|
|
// // 简化:只支持 countBiasedGe7 比较
|
|
|
|
|
// // 实际可用 Jackson 解析
|
|
|
|
|
// matched = countBiasedGe7 >= 2; // 示例,可改为读取 JSON
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// if (matched) {
|
|
|
|
|
// // 渲染结果文案:替换 ${targetName} 等变量
|
|
|
|
|
// String targetName = "";
|
|
|
|
|
// if (rule.getTargetConstitutionId() != null) {
|
|
|
|
|
// QuestionConstitutionType ct = constitutionMap.get(rule.getTargetConstitutionId());
|
|
|
|
|
// targetName = ct != null ? ct.getName() : "";
|
|
|
|
|
// }
|
|
|
|
|
// String resultText = rule.getResultText().replace("${targetName}", targetName);
|
|
|
|
|
// // 确定结果类型:若目标体质存在则用其别名,否则用 rule 的 targetType
|
|
|
|
|
// String resultType = "";
|
|
|
|
|
// if (rule.getTargetConstitutionId() != null) {
|
|
|
|
|
// QuestionConstitutionType ct = constitutionMap.get(rule.getTargetConstitutionId());
|
|
|
|
|
// resultType = ct != null ? ct.getAlias() : rule.getTargetType();
|
|
|
|
|
// } else {
|
|
|
|
|
// resultType = rule.getTargetType(); // "compound"
|
|
|
|
|
// }
|
|
|
|
|
// // 构建得分明细列表
|
|
|
|
|
// List<ConstitutionScoreDTO> dtoList = scoreById.entrySet().stream()
|
|
|
|
|
// .map(e -> {
|
|
|
|
|
// QuestionConstitutionType ct = constitutionMap.get(e.getKey());
|
|
|
|
|
// ConstitutionScoreDTO dto = new ConstitutionScoreDTO();
|
|
|
|
|
// dto.setConstitutionAlias(ct.getAlias());
|
|
|
|
|
// dto.setConstitutionName(ct.getName());
|
|
|
|
|
// dto.setTotalScore(e.getValue());
|
|
|
|
|
// return dto;
|
|
|
|
|
// }).collect(Collectors.toList());
|
|
|
|
|
// AnswerResultVO vo = new AnswerResultVO();
|
|
|
|
|
// vo.setResultType(resultType);
|
|
|
|
|
// vo.setResultText(resultText);
|
|
|
|
|
// vo.setConstitutionScores(dtoList);
|
|
|
|
|
// return vo;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
return buildFallbackResult(scoreById, constitutionMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AnswerResultVO buildFallbackResult(Map<Long, Integer> scoreById,
|
|
|
|
|
Map<Long, QuestionConstitutionType> constitutionMap) {
|
|
|
|
|
// 兜底结果
|
|
|
|
|
List<ConstitutionScoreDTO> dtoList = scoreById.entrySet().stream()
|
|
|
|
|
.map(e -> {
|
|
|
|
|
QuestionConstitutionType ct = constitutionMap.get(e.getKey());
|
|
|
|
|
ConstitutionScoreDTO dto = new ConstitutionScoreDTO();
|
|
|
|
|
dto.setConstitutionAlias(ct.getAlias());
|
|
|
|
|
dto.setConstitutionName(ct.getName());
|
|
|
|
|
dto.setTotalScore(e.getValue());
|
|
|
|
|
return dto;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
AnswerResultVO vo = new AnswerResultVO();
|
|
|
|
|
vo.setResultType("unknown");
|
|
|
|
|
vo.setResultText("无法判定体质类型,请重新测评。");
|
|
|
|
|
vo.setConstitutionScores(dtoList);
|
|
|
|
|
return vo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 简单表达式求值(示例,实际可用 Aviator/SpEL)
|
|
|
|
|
private boolean evaluateCondition(String expression, int pingheScore, int maxBiasedScore, String alias, String name) {
|
|
|
|
|
// 替换变量后使用 JavaScript 引擎或 SpEL
|
|
|
|
|
// 例如:expression = "pingheScore >= 8 && maxBiasedScore < 6"
|
|
|
|
|
// 实际实现请引入 AviatorEvaluator
|
|
|
|
|
return false; // 伪代码
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 模板渲染(支持 ${name} 等变量)
|
|
|
|
|
private String renderTemplate(String template, String name, int pingheScore) {
|
|
|
|
|
return template.replace("${name}", name).replace("${pingheScore}", String.valueOf(pingheScore));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AnswerResultVO buildResult(String resultType, String resultText,
|
|
|
|
|
List<QuestionConstitutionScore> scores,
|
|
|
|
|
Map<Long, QuestionConstitutionType> constitutionMap) {
|
|
|
|
|
AnswerResultVO vo = new AnswerResultVO();
|
|
|
|
|
vo.setResultType(resultType);
|
|
|
|
|
vo.setResultText(resultText);
|
|
|
|
|
|
|
|
|
|
List<ConstitutionScoreDTO> dtoList = scores.stream().map(score -> {
|
|
|
|
|
QuestionConstitutionType ct = constitutionMap.get(score.getConstitutionId());
|
|
|
|
|
ConstitutionScoreDTO dto = new ConstitutionScoreDTO();
|
|
|
|
|
dto.setConstitutionAlias(ct.getAlias());
|
|
|
|
|
dto.setConstitutionName(ct.getName());
|
|
|
|
|
dto.setTotalScore(score.getTotalScore());
|
|
|
|
|
return dto;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
vo.setConstitutionScores(dtoList);
|
|
|
|
|
return vo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构建体质VO列表(包含题目和选项)
|
|
|
|
|
* @param constitutions 体质类型列表
|
|
|
|
|
* @param allQuestions 所有题目列表(可能为空)
|
|
|
|
|
* @param allOptions 所有选项列表(可能为空)
|
|
|
|
|
* @return 体质VO列表
|
|
|
|
|
*/
|
|
|
|
|
private List<QuestionnaireVO.ConstitutionVO> buildConstitutionVOs(
|
|
|
|
|
List<QuestionConstitutionType> constitutions,
|
|
|
|
|
List<QuestionQuestion> allQuestions,
|
|
|
|
|
List<QuestionOption> allOptions) {
|
|
|
|
|
|
|
|
|
|
if (CollUtil.isEmpty(constitutions)) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按体质ID分组题目
|
|
|
|
|
Map<Long, List<QuestionQuestion>> questionsByConstitutionId = allQuestions.stream()
|
|
|
|
|
.collect(Collectors.groupingBy(QuestionQuestion::getConstitutionId));
|
|
|
|
|
|
|
|
|
|
// 按题目ID分组选项
|
|
|
|
|
Map<Long, List<QuestionOption>> optionsByQuestionId = allOptions.stream()
|
|
|
|
|
.collect(Collectors.groupingBy(QuestionOption::getQuestionId));
|
|
|
|
|
|
|
|
|
|
// 构建结果
|
|
|
|
|
return constitutions.stream().map(ct -> {
|
|
|
|
|
QuestionnaireVO.ConstitutionVO ctVO = new QuestionnaireVO.ConstitutionVO();
|
|
|
|
|
ctVO.setConstitutionId(ct.getId());
|
|
|
|
|
ctVO.setName(ct.getName());
|
|
|
|
|
ctVO.setAlias(ct.getAlias());
|
|
|
|
|
ctVO.setDescription(ct.getDescription());
|
|
|
|
|
ctVO.setSortOrder(ct.getSortOrder());
|
|
|
|
|
|
|
|
|
|
// 获取该体质下的题目
|
|
|
|
|
List<QuestionQuestion> questions = questionsByConstitutionId.getOrDefault(ct.getId(), Collections.emptyList());
|
|
|
|
|
List<QuestionnaireVO.QuestionVO> questionVOs = questions.stream().map(q -> {
|
|
|
|
|
QuestionnaireVO.QuestionVO qVO = new QuestionnaireVO.QuestionVO();
|
|
|
|
|
qVO.setQuestionId(q.getId());
|
|
|
|
|
qVO.setSerialNumber(q.getSerialNumber());
|
|
|
|
|
qVO.setContent(q.getContent());
|
|
|
|
|
qVO.setFullScore(q.getFullScore());
|
|
|
|
|
|
|
|
|
|
// 获取该题目下的选项
|
|
|
|
|
List<QuestionOption> options = optionsByQuestionId.getOrDefault(q.getId(), Collections.emptyList());
|
|
|
|
|
List<QuestionnaireVO.OptionVO> optionVOs = options.stream().map(opt -> {
|
|
|
|
|
QuestionnaireVO.OptionVO optVO = new QuestionnaireVO.OptionVO();
|
|
|
|
|
optVO.setOptionId(opt.getId());
|
|
|
|
|
optVO.setOptionText(opt.getOptionText());
|
|
|
|
|
optVO.setScore(opt.getScore());
|
|
|
|
|
optVO.setSortOrder(opt.getSortOrder());
|
|
|
|
|
return optVO;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
qVO.setOptions(optionVOs);
|
|
|
|
|
return qVO;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
|
ctVO.setQuestions(questionVOs);
|
|
|
|
|
return ctVO;
|
|
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
}
|
|
|
|
|
}
|