package jnpf.tmsService.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import jnpf.exception.DataException; import jnpf.tmsEntity.TmsEvalModeEntity; import jnpf.tmsEntity.TmsExamEntity; import jnpf.tmsEntity.TmsMaterialEntity; import jnpf.tmsEntity.TmsPaperEntity; import jnpf.tmsEntity.TmsPersonTaskEntity; import jnpf.tmsEntity.TmsQuizScoreEntity; import jnpf.tmsEntity.TmsRecordEntity; import jnpf.tmsEntity.TmsRecordSignEntity; import jnpf.tmsEntity.TmsTaskEntity; import jnpf.tmsEntity.TmsTaskFileEntity; import jnpf.tmsEntity.TmsTaskQuizEntity; import jnpf.tmsEntity.TmsSignModeEntity; import jnpf.tmsEntity.TmsTrainModeEntity; import jnpf.tmsMapper.TmsEvalModeMapper; import jnpf.tmsMapper.TmsExamMapper; import jnpf.tmsMapper.TmsPaperMapper; import jnpf.tmsMapper.TmsQuizScoreMapper; import jnpf.tmsMapper.TmsRecordMapper; import jnpf.tmsMapper.TmsRecordSignMapper; import jnpf.tmsEntity.person.TmsPersonTaskFileForm; import jnpf.tmsEntity.person.TmsPersonTaskLearnResult; import jnpf.tmsEntity.person.TmsPersonTaskMineForm; import jnpf.tmsEntity.person.TmsPersonTaskQuery; import jnpf.tmsEntity.person.TmsPersonTaskSignResult; import jnpf.tmsEntity.person.TmsQuizAnswerForm; import jnpf.tmsEntity.person.TmsQuizQuestionVO; import jnpf.tmsEntity.person.TmsQuizSubmitResult; import jnpf.tmsEntity.record.TmsQuizAnswerPaperVO; import jnpf.tmsMapper.TmsMaterialMapper; import jnpf.tmsMapper.TmsPersonTaskMapper; import jnpf.tmsMapper.TmsSignModeMapper; import jnpf.tmsMapper.TmsTaskFileMapper; import jnpf.tmsMapper.TmsTaskMapper; import jnpf.tmsMapper.TmsTaskQuizMapper; import jnpf.tmsMapper.TmsTrainModeMapper; import jnpf.tmsService.TmsPersonTaskService; import jnpf.tmsService.TmsRecordService; import jnpf.tmsEntity.enums.TmsPassFlag; import jnpf.tmsEntity.enums.TmsPersonTaskStatus; import jnpf.tmsEntity.enums.TmsSignMode; import jnpf.tmsEntity.enums.TmsQrType; import jnpf.tmsEntity.enums.TmsExamStatus; import jnpf.tmsEntity.enums.TmsTaskPublishStatus; import jnpf.tmsEntity.enums.TmsArchiveStatus; import jnpf.tmsEntity.enums.TmsEvalMode; import jnpf.tmsEntity.enums.TmsSignRule; import jnpf.tmsEntity.enums.TmsTrainMode; import jnpf.tmsUtil.TmsQuizHelper; import jnpf.util.DateUtil; import jnpf.util.RandomUtil; import jnpf.util.StringUtil; import jnpf.util.UserProvider; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * 学员端个人培训任务:我的列表、学完、签到/扫码签到、考试结果回写。 * *

不负责写入个人档案培训目录;目录仅在培训记录归档时由 {@link TmsRecordServiceImpl} 写入。

*

考试交卷后通过 {@link #writeBackExamResult} 更新本表成绩与合格标记。

*/ @Service @RequiredArgsConstructor public class TmsPersonTaskServiceImpl implements TmsPersonTaskService { private static final String FMT = "yyyy-MM-dd HH:mm:ss"; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final TmsPersonTaskMapper personTaskMapper; private final TmsTaskMapper taskMapper; private final TmsTaskFileMapper taskFileMapper; private final TmsMaterialMapper materialMapper; private final TmsTrainModeMapper trainModeMapper; private final TmsSignModeMapper signModeMapper; private final TmsRecordMapper recordMapper; private final TmsRecordSignMapper recordSignMapper; private final TmsExamMapper examMapper; private final TmsPaperMapper paperMapper; private final TmsTaskQuizMapper taskQuizMapper; private final TmsQuizScoreMapper quizScoreMapper; private final TmsEvalModeMapper evalModeMapper; @Lazy @Autowired private TmsRecordService recordService; /** * 查询我的培训任务列表 */ @Override public List getMine(TmsPersonTaskQuery query) { String userId = UserProvider.getLoginUserId(); LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(TmsPersonTaskEntity::getUserId, userId); if (StringUtil.isNotEmpty(query.getBizStatus())) { qw.eq(TmsPersonTaskEntity::getBizStatus, query.getBizStatus().trim()); } else { qw.ne(TmsPersonTaskEntity::getBizStatus, TmsPersonTaskStatus.CANCELLED.getCode()); } if (StringUtil.isNotEmpty(query.getTaskNo())) { qw.like(TmsPersonTaskEntity::getTaskNo, query.getTaskNo().trim()); } if ("1".equals(query.getSigned())) { qw.isNotNull(TmsPersonTaskEntity::getSignTime); } else if ("0".equals(query.getSigned())) { qw.isNull(TmsPersonTaskEntity::getSignTime); } qw.orderByDesc(TmsPersonTaskEntity::getCreatorTime); List persons = personTaskMapper.selectList(qw); Map taskMap = loadTasks(persons); String keyword = StringUtil.isNotEmpty(query.getKeyword()) ? query.getKeyword().trim() : null; String trainMode = StringUtil.isNotEmpty(query.getTrainMode()) ? query.getTrainMode().trim() : null; String evalMode = StringUtil.isNotEmpty(query.getEvalMode()) ? query.getEvalMode().trim() : null; String category = StringUtil.isNotEmpty(query.getCategory()) ? query.getCategory().trim() : null; Date startFrom = parseDayStart(query.getStartTimeFrom()); Date startTo = parseDayEnd(query.getStartTimeTo()); List all = new ArrayList<>(); for (TmsPersonTaskEntity person : persons) { TmsTaskEntity task = taskMap.get(person.getTaskId()); if (task == null || !TmsTaskPublishStatus.PUBLISHED.equalsCode(task.getPublishStatus())) { continue; } if (keyword != null && !matchKeyword(person, task, keyword)) { continue; } if (trainMode != null && !trainMode.equals(task.getTrainMode())) { continue; } if (evalMode != null && !evalMode.equals(task.getEvalMode())) { continue; } if (category != null && !category.equals(task.getCategory())) { continue; } if (startFrom != null && (task.getStartTime() == null || task.getStartTime().before(startFrom))) { continue; } if (startTo != null && (task.getStartTime() == null || task.getStartTime().after(startTo))) { continue; } if (!matchPassFlag(query.getPassFlag(), person.getPassFlag())) { continue; } TmsPersonTaskMineForm form = toForm(person, task, false); if (!matchYesNo(query.getCanExam(), Boolean.TRUE.equals(form.getCanExam()))) { continue; } all.add(form); } all.sort(Comparator.comparing(TmsPersonTaskMineForm::getStartTime, Comparator.nullsLast(Comparator.reverseOrder()))); long current = query.getCurrentPage() > 0 ? query.getCurrentPage() : 1; long size = query.getPageSize() > 0 ? query.getPageSize() : 20; int from = (int) Math.min((current - 1) * size, all.size()); int to = (int) Math.min(from + size, all.size()); List page = all.subList(from, to); query.setData(page, all.size()); return page; } /** * 个人培训任务:按主键查询详情 */ @Override public TmsPersonTaskMineForm getInfo(String id) { TmsPersonTaskEntity person = requireMine(id); TmsTaskEntity task = taskMapper.selectById(person.getTaskId()); if (task == null) { throw new DataException("培训任务不存在"); } TmsPersonTaskMineForm form = toForm(person, task, true); form.setFiles(loadFiles(task.getId())); return form; } @Override @Transactional(rollbackFor = Exception.class) /** * 标记学习进度/完成 */ public TmsPersonTaskLearnResult markLearned(String id, Integer seconds) { TmsPersonTaskEntity person = requireMine(id); TmsTaskEntity task = taskMapper.selectById(person.getTaskId()); if (task == null) { throw new DataException("培训任务不存在"); } if (person.getSignTime() == null) { throw new DataException("请先签到后再学习"); } int before = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds(); int required = toRequiredSeconds(person.getHours()); boolean alreadyFull = required > 0 && before >= required; int add = seconds == null ? 0 : Math.max(0, seconds); if (add > 0) { person.setLearnSeconds(before + add); person.setLastModifyUserId(UserProvider.getLoginUserId()); person.setLastModifyTime(new Date()); personTaskMapper.updateById(person); } int learned = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds(); boolean justFull = required > 0 && !alreadyFull && learned >= required; TmsPersonTaskLearnResult result = new TmsPersonTaskLearnResult(); result.setLearnedSeconds(learned); result.setRequiredSeconds(required); result.setLearnStatus(resolveLearnStatus(learned, required)); result.setBizStatus(person.getBizStatus()); result.setAutoCompleted(false); result.setMessage(""); boolean needExam = TmsEvalMode.EXAM.equalsCode(task.getEvalMode()) && StringUtil.isNotEmpty(task.getPaperId()); if (justFull) { if (!needExam && !TmsPersonTaskStatus.DONE.equalsCode(person.getBizStatus()) && !TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) { person.setBizStatus(TmsPersonTaskStatus.DONE.getCode()); person.setLastModifyUserId(UserProvider.getLoginUserId()); person.setLastModifyTime(new Date()); personTaskMapper.updateById(person); result.setAutoCompleted(true); result.setBizStatus(TmsPersonTaskStatus.DONE.getCode()); result.setMessage("已学满规定课时,系统已自动确认完成"); } else if (needExam) { ensurePendingExam(person, task); result.setMessage("已学满规定课时,待考试卷已生成,可以进入考试"); } } TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm(); fillExamFlag(tmp, task, person); if (Boolean.TRUE.equals(tmp.getCanExam()) && needExam) { ensurePendingExam(person, task); } result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam())); result.setExamTip(tmp.getExamTip()); return result; } /** * 确保存在待考答卷壳 */ private void ensurePendingExam(TmsPersonTaskEntity person, TmsTaskEntity task) { if (person == null || task == null || StringUtil.isEmpty(task.getPaperId())) { return; } if (TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) { return; } Long exists = examMapper.selectCount(new LambdaQueryWrapper() .eq(TmsExamEntity::getPersonTaskId, person.getId()) .eq(TmsExamEntity::getPaperId, task.getPaperId()) .and(w -> w.isNull(TmsExamEntity::getExamStatus) .or().eq(TmsExamEntity::getExamStatus, "") .or().eq(TmsExamEntity::getExamStatus, TmsExamStatus.NOT_STARTED.getCode()) .or().eq(TmsExamEntity::getExamStatus, TmsExamStatus.DOING.getCode()) .or().eq(TmsExamEntity::getExamStatus, TmsExamStatus.SUBMITTED.getCode()))); if (exists != null && exists > 0) { // 已有答卷:把仍为空的状态补成 notStarted,便于考试记录显示「待考」 examMapper.update(null, new LambdaUpdateWrapper() .eq(TmsExamEntity::getPersonTaskId, person.getId()) .eq(TmsExamEntity::getPaperId, task.getPaperId()) .and(w -> w.isNull(TmsExamEntity::getExamStatus) .or().eq(TmsExamEntity::getExamStatus, "")) .set(TmsExamEntity::getExamStatus, TmsExamStatus.NOT_STARTED.getCode())); return; } String recordId = null; TmsRecordEntity record = recordMapper.selectOne(new LambdaQueryWrapper() .eq(TmsRecordEntity::getTaskId, task.getId()) .last("limit 1")); if (record != null) { recordId = record.getId(); } TmsPaperEntity paper = paperMapper.selectById(task.getPaperId()); BigDecimal passScore = task.getPassScore(); if (passScore == null && paper != null) { passScore = paper.getPassScore(); } Date now = new Date(); TmsExamEntity exam = new TmsExamEntity(); exam.setId(RandomUtil.uuId()); exam.setTaskId(task.getId()); exam.setPersonTaskId(person.getId()); exam.setRecordId(recordId); exam.setPaperId(task.getPaperId()); exam.setPaperName(StringUtil.isNotEmpty(task.getPaperName()) ? task.getPaperName() : (paper == null ? null : paper.getPaperName())); exam.setUserId(person.getUserId()); exam.setAttemptNo(1); exam.setExamStatus(TmsExamStatus.NOT_STARTED.getCode()); exam.setPassScore(passScore); exam.setDurationMin(paper == null ? null : paper.getDurationMin()); exam.setCreatorUserId(UserProvider.getLoginUserId()); exam.setCreatorTime(now); examMapper.insert(exam); } /** * 学员签到 */ @Override @Transactional(rollbackFor = Exception.class) public TmsPersonTaskSignResult sign(String id) { TmsPersonTaskEntity person = requireMine(id); TmsTaskEntity task = taskMapper.selectById(person.getTaskId()); if (task == null) { throw new DataException("培训任务不存在"); } if (!TmsTaskPublishStatus.PUBLISHED.equalsCode(task.getPublishStatus())) { throw new DataException("任务未发布,不可签到"); } if (TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) { throw new DataException("任务已取消,不可签到"); } if (person.getSignTime() != null) { throw new DataException("您已签到,无需重复签到"); } String deny = resolveSignDenyReason(task, person); if (StringUtil.isNotEmpty(deny)) { throw new DataException(deny); } Date now = new Date(); person.setSignTime(now); person.setSignMode(TmsSignMode.ONLINE.getCode()); if (TmsPersonTaskStatus.TODO.equalsCode(person.getBizStatus()) || StringUtil.isEmpty(person.getBizStatus())) { person.setBizStatus(TmsPersonTaskStatus.SIGNED.getCode()); } person.setLastModifyUserId(UserProvider.getLoginUserId()); person.setLastModifyTime(now); personTaskMapper.updateById(person); upsertRecordSign(task.getId(), person, TmsSignMode.ONLINE.getCode(), now); refreshRecordRates(task.getId()); TmsPersonTaskSignResult result = new TmsPersonTaskSignResult(); result.setSignTime(formatDate(now)); result.setBizStatus(person.getBizStatus()); result.setSigned(true); result.setCanSign(false); result.setSignTip("已签到"); result.setMessage("签到成功"); TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm(); fillExamFlag(tmp, task, person); if (Boolean.TRUE.equals(tmp.getCanExam()) && TmsEvalMode.EXAM.equalsCode(task.getEvalMode()) && StringUtil.isNotEmpty(task.getPaperId())) { ensurePendingExam(person, task); } result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam())); result.setExamTip(tmp.getExamTip()); result.setPersonTaskId(person.getId()); fillQuizOnSignResult(result, task, person); return result; } /** * 扫码签到 */ @Override @Transactional(rollbackFor = Exception.class) public TmsPersonTaskSignResult scanSign(String qrContent, String signMode) { Map payload = parseSignQr(qrContent); String type = stringVal(payload.get("t")); if (!TmsQrType.SIGN.equalsCode(type)) { throw new DataException("无效的签到二维码"); } long expMs = toLong(payload.get("exp")); if (expMs <= 0 || System.currentTimeMillis() > expMs) { throw new DataException("签到二维码已过期,请刷新后重扫"); } String recordId = stringVal(payload.get("recordId")); String taskId = stringVal(payload.get("taskId")); if (StringUtil.isEmpty(recordId)) { throw new DataException("签到二维码缺少培训记录"); } TmsRecordEntity record = recordMapper.selectById(recordId); if (record == null) { throw new DataException("培训记录不存在"); } if (TmsArchiveStatus.ARCHIVED.equalsCode(record.getArchiveStatus())) { throw new DataException("已存档记录不可签到"); } if (StringUtil.isEmpty(taskId)) { taskId = record.getTaskId(); } if (StringUtil.isEmpty(taskId) || !taskId.equals(record.getTaskId())) { throw new DataException("签到二维码与培训记录不匹配"); } TmsTaskEntity task = taskMapper.selectById(taskId); if (task == null) { throw new DataException("培训任务不存在"); } if (!TmsTaskPublishStatus.PUBLISHED.equalsCode(task.getPublishStatus())) { throw new DataException("任务未发布,不可签到"); } String signRule = resolveSignRule(task.getTrainMode()); if (!TmsSignRule.SCAN.equalsCodeIgnoreCase(signRule) && !TmsSignRule.BOTH.equalsCodeIgnoreCase(signRule)) { throw new DataException("该培训方式不支持扫码签到"); } String mode = normalizeScanSignMode(signMode); TmsSignModeEntity signModeCfg = requireEnabledSignMode(mode); String userId = UserProvider.getLoginUserId(); if (StringUtil.isEmpty(userId)) { throw new DataException("请先登录后再扫码签到"); } TmsPersonTaskEntity person = personTaskMapper.selectOne( new LambdaQueryWrapper() .eq(TmsPersonTaskEntity::getTaskId, taskId) .eq(TmsPersonTaskEntity::getUserId, userId) .last("LIMIT 1")); boolean guest = false; if (person == null) { if (!allowGuestScan(task.getTrainMode(), signModeCfg)) { throw new DataException("您不在本场培训名单中,无法签到"); } person = createGuestPersonTask(task, userId); guest = true; } if (TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) { throw new DataException("任务已取消,不可签到"); } if (person.getSignTime() != null) { TmsPersonTaskSignResult dup = new TmsPersonTaskSignResult(); dup.setSignTime(formatDate(person.getSignTime())); dup.setBizStatus(person.getBizStatus()); dup.setSigned(true); dup.setCanSign(false); dup.setSignTip("已签到"); dup.setMessage("您已签到,无需重复签到"); TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm(); fillExamFlag(tmp, task, person); dup.setCanExam(Boolean.TRUE.equals(tmp.getCanExam())); dup.setExamTip(tmp.getExamTip()); dup.setPersonTaskId(person.getId()); fillQuizOnSignResult(dup, task, person); return dup; } String timeDeny = resolveScanTimeDenyReason(task); if (StringUtil.isNotEmpty(timeDeny)) { throw new DataException(timeDeny); } Date now = new Date(); person.setSignTime(now); person.setSignMode(mode); if (guest) { person.setGuestFlag("1"); } if (TmsPersonTaskStatus.TODO.equalsCode(person.getBizStatus()) || StringUtil.isEmpty(person.getBizStatus())) { person.setBizStatus(TmsPersonTaskStatus.SIGNED.getCode()); } person.setLastModifyUserId(userId); person.setLastModifyTime(now); personTaskMapper.updateById(person); upsertRecordSign(recordId, person, mode, now); if (!guest) { refreshRecordRates(taskId); } TmsPersonTaskSignResult result = new TmsPersonTaskSignResult(); result.setSignTime(formatDate(now)); result.setBizStatus(person.getBizStatus()); result.setSigned(true); result.setCanSign(false); result.setSignTip("已签到"); result.setMessage(guest ? "扫码签到成功(名单外)" : "扫码签到成功"); TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm(); fillExamFlag(tmp, task, person); result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam())); result.setExamTip(tmp.getExamTip()); result.setPersonTaskId(person.getId()); if (!guest) { fillQuizOnSignResult(result, task, person); } else { result.setNeedQuiz(false); } return result; } @Override public List listQuizQuestions(String personTaskId) { TmsPersonTaskEntity person = requireMine(personTaskId); TmsTaskEntity task = taskMapper.selectById(person.getTaskId()); if (task == null || !needsQuiz(task)) { return Collections.emptyList(); } if (hasQuizScore(task.getId(), person.getUserId())) { return Collections.emptyList(); } return buildQuizQuestions(task.getId()); } @Override @Transactional(rollbackFor = Exception.class) public TmsQuizSubmitResult submitQuiz(String personTaskId, TmsQuizAnswerForm form) { TmsPersonTaskEntity person = requireMine(personTaskId); TmsTaskEntity task = taskMapper.selectById(person.getTaskId()); if (task == null) { throw new DataException("培训任务不存在"); } if (!needsQuiz(task)) { throw new DataException("当前任务无需提问"); } if (person.getSignTime() == null) { throw new DataException("请先完成签到"); } if (hasQuizScore(task.getId(), person.getUserId())) { throw new DataException("您已提交过提问答案"); } List presets = taskQuizMapper.selectList(new LambdaQueryWrapper() .eq(TmsTaskQuizEntity::getForeignId, task.getId()) .orderByAsc(TmsTaskQuizEntity::getSortNo) .orderByAsc(TmsTaskQuizEntity::getCreatorTime)); if (presets == null || presets.isEmpty()) { throw new DataException("任务未配置提问预设"); } Map answerMap = new HashMap<>(); if (form != null && form.getAnswers() != null) { for (TmsQuizAnswerForm.Item item : form.getAnswers()) { if (item != null && StringUtil.isNotEmpty(item.getQuizId())) { answerMap.put(item.getQuizId(), item.getAnswer() == null ? "" : item.getAnswer().trim()); } } } for (TmsTaskQuizEntity q : presets) { if (StringUtil.isEmpty(answerMap.get(q.getId()))) { throw new DataException("请完成全部提问后再提交"); } } TmsRecordEntity record = recordMapper.selectOne(new LambdaQueryWrapper() .eq(TmsRecordEntity::getTaskId, task.getId()) .last("LIMIT 1")); String recordId = record == null ? null : record.getId(); Date now = new Date(); int correct = 0; BigDecimal unit = BigDecimal.valueOf(100) .divide(BigDecimal.valueOf(presets.size()), 2, RoundingMode.HALF_UP); for (TmsTaskQuizEntity q : presets) { String userAns = answerMap.getOrDefault(q.getId(), ""); boolean ok = TmsQuizHelper.answersMatch(q.getAnswer(), userAns); if (ok) { correct++; } TmsQuizScoreEntity scoreRow = new TmsQuizScoreEntity(); scoreRow.setId(RandomUtil.uuId()); scoreRow.setTaskId(task.getId()); scoreRow.setRecordId(recordId); scoreRow.setUserId(person.getUserId()); scoreRow.setQuestion(q.getQuestion()); scoreRow.setAnswer(userAns); scoreRow.setScore(ok ? unit : BigDecimal.ZERO); scoreRow.setQuizTime(now); scoreRow.setCreatorUserId(person.getUserId()); scoreRow.setCreatorTime(now); quizScoreMapper.insert(scoreRow); } BigDecimal totalScore = BigDecimal.valueOf(correct) .multiply(BigDecimal.valueOf(100)) .divide(BigDecimal.valueOf(presets.size()), 1, RoundingMode.HALF_UP); BigDecimal passScore = task.getPassScore() == null ? BigDecimal.valueOf(60) : task.getPassScore(); String passFlag = totalScore.compareTo(passScore) >= 0 ? TmsPassFlag.PASS.getCode() : TmsPassFlag.FAIL.getCode(); person.setPassFlag(passFlag); if (TmsPassFlag.isPass(passFlag)) { person.setBizStatus(TmsPersonTaskStatus.DONE.getCode()); } person.setLastModifyUserId(person.getUserId()); person.setLastModifyTime(now); personTaskMapper.updateById(person); if (StringUtil.isNotEmpty(recordId)) { TmsRecordSignEntity sign = recordSignMapper.selectOne(new LambdaQueryWrapper() .eq(TmsRecordSignEntity::getForeignId, recordId) .eq(TmsRecordSignEntity::getUserId, person.getUserId()) .last("LIMIT 1")); if (sign != null) { sign.setQuizScore(totalScore); sign.setResultFlag(passFlag); sign.setLastModifyUserId(person.getUserId()); sign.setLastModifyTime(now); recordSignMapper.updateById(sign); } } refreshRecordRates(task.getId()); TmsQuizSubmitResult result = new TmsQuizSubmitResult(); result.setScore(totalScore); result.setPassFlag(passFlag); result.setMessage(TmsPassFlag.isPass(passFlag) ? "提问已提交,判定合格" : "提问已提交,未达合格分"); return result; } @Override public TmsQuizAnswerPaperVO getMyQuizAnswers(String personTaskId) { TmsPersonTaskEntity person = requireMine(personTaskId); TmsTaskEntity task = taskMapper.selectById(person.getTaskId()); if (task == null) { throw new DataException("培训任务不存在"); } if (!needsQuiz(task)) { throw new DataException("当前任务无需提问"); } List scores = quizScoreMapper.selectList(new LambdaQueryWrapper() .eq(TmsQuizScoreEntity::getTaskId, task.getId()) .eq(TmsQuizScoreEntity::getUserId, person.getUserId()) .orderByAsc(TmsQuizScoreEntity::getCreatorTime) .orderByAsc(TmsQuizScoreEntity::getId)); if (scores == null || scores.isEmpty()) { throw new DataException("尚未提交提问答卷"); } List presets = taskQuizMapper.selectList(new LambdaQueryWrapper() .eq(TmsTaskQuizEntity::getForeignId, task.getId()) .orderByAsc(TmsTaskQuizEntity::getSortNo) .orderByAsc(TmsTaskQuizEntity::getCreatorTime)); TmsRecordEntity record = recordMapper.selectOne(new LambdaQueryWrapper() .eq(TmsRecordEntity::getTaskId, task.getId()) .last("LIMIT 1")); String recordId = record == null ? null : record.getId(); BigDecimal quizScore = null; if (StringUtil.isNotEmpty(recordId)) { TmsRecordSignEntity sign = recordSignMapper.selectOne(new LambdaQueryWrapper() .eq(TmsRecordSignEntity::getForeignId, recordId) .eq(TmsRecordSignEntity::getUserId, person.getUserId()) .last("LIMIT 1")); if (sign != null) { quizScore = sign.getQuizScore(); } } String quizTime = formatDate(scores.get(0).getQuizTime() != null ? scores.get(0).getQuizTime() : scores.get(0).getCreatorTime()); TmsQuizAnswerPaperVO paper = TmsQuizHelper.buildAnswerPaper( recordId, task.getId(), person.getUserId(), null, person.getPassFlag(), quizTime, scores, presets); if (quizScore != null) { paper.setTotalScore(quizScore); } return paper; } @Override @Transactional(rollbackFor = Exception.class) /** * 在线考试交卷后回写:成绩、合格标记、个人任务业务状态。 * 不写 tms_person_train(档案目录仍等记录归档)。 */ public void writeBackExamResult(String personTaskId, BigDecimal examScore, String passFlag) { if (StringUtil.isEmpty(personTaskId)) { return; } TmsPersonTaskEntity person = personTaskMapper.selectById(personTaskId); if (person == null || TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) { return; } Date now = new Date(); person.setExamScore(examScore); person.setPassFlag(TmsPassFlag.normalize(passFlag)); person.setBizStatus(TmsPersonTaskStatus.DONE.getCode()); person.setLastModifyTime(now); String loginId = UserProvider.getLoginUserId(); if (StringUtil.isNotEmpty(loginId)) { person.setLastModifyUserId(loginId); } personTaskMapper.updateById(person); syncRecordSignExamResult(person, examScore, person.getPassFlag(), now); refreshRecordRates(person.getTaskId()); } private void refreshRecordRates(String taskId) { try { if (recordService != null) { recordService.refreshRatesByTaskId(taskId); } } catch (Exception ignored) { // 进度回写失败不影响主流程 } } private void fillQuizOnSignResult(TmsPersonTaskSignResult result, TmsTaskEntity task, TmsPersonTaskEntity person) { if (result == null || task == null || person == null) { return; } if (!needsQuiz(task) || hasQuizScore(task.getId(), person.getUserId())) { result.setNeedQuiz(false); result.setQuizQuestions(Collections.emptyList()); return; } List questions = buildQuizQuestions(task.getId()); result.setNeedQuiz(!questions.isEmpty()); result.setQuizQuestions(questions); } private boolean needsQuiz(TmsTaskEntity task) { if (task == null) { return false; } if (TmsEvalMode.QUIZ.equalsCode(task.getEvalMode())) { return true; } if (StringUtil.isEmpty(task.getEvalMode())) { return false; } TmsEvalModeEntity mode = evalModeMapper.selectOne(new LambdaQueryWrapper() .eq(TmsEvalModeEntity::getModeCode, task.getEvalMode()) .last("LIMIT 1")); return mode != null && "1".equals(mode.getNeedQuiz()); } private boolean hasQuizScore(String taskId, String userId) { if (StringUtil.isEmpty(taskId) || StringUtil.isEmpty(userId)) { return false; } Long cnt = quizScoreMapper.selectCount(new LambdaQueryWrapper() .eq(TmsQuizScoreEntity::getTaskId, taskId) .eq(TmsQuizScoreEntity::getUserId, userId)); return cnt != null && cnt > 0; } private List buildQuizQuestions(String taskId) { List rows = taskQuizMapper.selectList(new LambdaQueryWrapper() .eq(TmsTaskQuizEntity::getForeignId, taskId) .orderByAsc(TmsTaskQuizEntity::getSortNo) .orderByAsc(TmsTaskQuizEntity::getCreatorTime)); List list = new ArrayList<>(); if (rows == null) { return list; } for (TmsTaskQuizEntity row : rows) { if (row == null || StringUtil.isEmpty(row.getQuestion())) { continue; } TmsQuizQuestionVO vo = new TmsQuizQuestionVO(); vo.setId(row.getId()); vo.setSortNo(row.getSortNo()); vo.setQuestion(row.getQuestion()); list.add(vo); } return list; } /** * 同步签到记录中的考试结果 */ private void syncRecordSignExamResult(TmsPersonTaskEntity person, BigDecimal examScore, String passFlag, Date now) { if (person == null || StringUtil.isEmpty(person.getUserId()) || StringUtil.isEmpty(person.getTaskId())) { return; } TmsRecordEntity record = recordMapper.selectOne( new LambdaQueryWrapper() .eq(TmsRecordEntity::getTaskId, person.getTaskId()) .last("LIMIT 1")); if (record == null) { return; } TmsRecordSignEntity sign = recordSignMapper.selectOne( new LambdaQueryWrapper() .eq(TmsRecordSignEntity::getForeignId, record.getId()) .eq(TmsRecordSignEntity::getUserId, person.getUserId()) .last("LIMIT 1")); if (sign == null) { return; } sign.setExamScore(examScore); sign.setResultFlag(TmsPassFlag.normalize(passFlag)); sign.setLastModifyUserId(UserProvider.getLoginUserId()); sign.setLastModifyTime(now); recordSignMapper.updateById(sign); } /** 写入/更新培训记录签到明细 */ private void upsertRecordSign(String recordIdOrTaskId, TmsPersonTaskEntity person, String signMode, Date now) { if (person == null || StringUtil.isEmpty(person.getUserId())) { return; } String recordId = recordIdOrTaskId; TmsRecordEntity byId = StringUtil.isNotEmpty(recordId) ? recordMapper.selectById(recordId) : null; if (byId == null && StringUtil.isNotEmpty(person.getTaskId())) { byId = recordMapper.selectOne( new LambdaQueryWrapper() .eq(TmsRecordEntity::getTaskId, person.getTaskId()) .last("LIMIT 1")); } if (byId == null) { return; } recordId = byId.getId(); TmsRecordSignEntity exists = recordSignMapper.selectOne( new LambdaQueryWrapper() .eq(TmsRecordSignEntity::getForeignId, recordId) .eq(TmsRecordSignEntity::getUserId, person.getUserId()) .last("LIMIT 1")); if (exists != null) { exists.setSignTime(now); exists.setSignMode(signMode); exists.setGuestFlag(StringUtil.isNotEmpty(person.getGuestFlag()) ? person.getGuestFlag() : "0"); exists.setDeptId(person.getDeptId()); exists.setLastModifyUserId(UserProvider.getLoginUserId()); exists.setLastModifyTime(now); recordSignMapper.updateById(exists); return; } TmsRecordSignEntity row = new TmsRecordSignEntity(); row.setId(RandomUtil.uuId()); row.setForeignId(recordId); row.setUserId(person.getUserId()); row.setDeptId(person.getDeptId()); row.setSignTime(now); row.setSignMode(signMode); row.setGuestFlag(StringUtil.isNotEmpty(person.getGuestFlag()) ? person.getGuestFlag() : "0"); row.setCreatorUserId(UserProvider.getLoginUserId()); row.setCreatorTime(now); recordSignMapper.insert(row); } /** * 创建访客个人任务 */ private TmsPersonTaskEntity createGuestPersonTask(TmsTaskEntity task, String userId) { Date now = new Date(); TmsPersonTaskEntity pt = new TmsPersonTaskEntity(); pt.setId(RandomUtil.uuId()); pt.setTaskId(task.getId()); pt.setTaskNo(task.getTaskNo()); pt.setUserId(userId); pt.setHours(null); pt.setBizStatus(TmsPersonTaskStatus.TODO.getCode()); pt.setGuestFlag("1"); pt.setCreatorUserId(userId); pt.setCreatorTime(now); personTaskMapper.insert(pt); return pt; } /** * 是否允许访客扫码 */ private boolean allowGuestScan(String trainModeCode, TmsSignModeEntity signMode) { if (signMode != null && "1".equals(signMode.getMatchRoster())) { return false; } TmsTrainModeEntity tm = loadTrainMode(trainModeCode); return tm != null && "1".equals(tm.getAllowGuestSign()); } /** * 加载培训方式配置 */ private TmsTrainModeEntity loadTrainMode(String trainModeCode) { if (StringUtil.isEmpty(trainModeCode)) { return null; } return trainModeMapper.selectOne( new LambdaQueryWrapper() .eq(TmsTrainModeEntity::getModeCode, trainModeCode) .last("LIMIT 1")); } /** * 解析签到二维码内容 */ private Map parseSignQr(String qrContent) { if (StringUtil.isEmpty(qrContent)) { throw new DataException("签到二维码内容为空"); } try { Map map = OBJECT_MAPPER.readValue(qrContent.trim(), new TypeReference>() { }); if (map == null || map.isEmpty()) { throw new DataException("无效的签到二维码"); } return map; } catch (DataException ex) { throw ex; } catch (Exception ex) { throw new DataException("无效的签到二维码"); } } /** * 规范化扫码签到方式 */ private String normalizeScanSignMode(String signMode) { if (TmsSignMode.WX_SCAN.getCode().equalsIgnoreCase(signMode)) { return TmsSignMode.WX_SCAN.getCode(); } return TmsSignMode.APP_SCAN.getCode(); } /** * 确保扫码签到方式已启用 */ private void ensureScanModeEnabled(String modeCode) { requireEnabledSignMode(modeCode); } /** * 校验签到方式已启用 */ private TmsSignModeEntity requireEnabledSignMode(String modeCode) { TmsSignModeEntity sm = signModeMapper.selectOne( new LambdaQueryWrapper() .eq(TmsSignModeEntity::getModeCode, modeCode) .eq(TmsSignModeEntity::getEnabled, "1") .last("LIMIT 1")); if (sm == null) { throw new DataException("签到方式未启用:" + modeCode); } return sm; } /** 扫码时间窗(不含在线签到规则校验) */ private String resolveScanTimeDenyReason(TmsTaskEntity task) { Date now = new Date(); if (task.getStartTime() != null && now.before(task.getStartTime())) { return "未到培训开始时间,暂不可签到"; } if (task.getCloseTime() != null && now.after(task.getCloseTime())) { return "培训已关闭,不可签到"; } if (task.getEndTime() != null && now.after(task.getEndTime()) && task.getCloseTime() == null) { return "已过培训结束时间,不可签到"; } return ""; } /** * 安全转字符串 */ private static String stringVal(Object raw) { return raw == null ? "" : String.valueOf(raw).trim(); } /** * 转 Long */ private static long toLong(Object raw) { if (raw == null) { return 0L; } if (raw instanceof Number) { return ((Number) raw).longValue(); } try { return Long.parseLong(String.valueOf(raw).trim()); } catch (Exception ex) { return 0L; } } /** * 实体转为表单/VO */ private TmsPersonTaskMineForm toForm(TmsPersonTaskEntity person, TmsTaskEntity task, boolean detail) { TmsPersonTaskMineForm form = new TmsPersonTaskMineForm(); form.setId(person.getId()); form.setTaskId(task.getId()); form.setTaskNo(StringUtil.isNotEmpty(person.getTaskNo()) ? person.getTaskNo() : task.getTaskNo()); form.setSubject(task.getSubject()); form.setCategory(task.getCategory()); form.setTrainMode(task.getTrainMode()); form.setEvalMode(task.getEvalMode()); form.setStartTime(formatDate(task.getStartTime())); form.setEndTime(formatDate(task.getEndTime())); form.setCloseTime(formatDate(task.getCloseTime())); form.setPlaceName(task.getPlaceName()); form.setBizStatus(person.getBizStatus()); form.setSignTime(formatDate(person.getSignTime())); form.setSigned(person.getSignTime() != null); form.setSignRule(resolveSignRule(task.getTrainMode())); form.setTipSeconds(resolveOnlineTipSeconds()); fillSignFlag(form, task, person); form.setLearnSeconds(person.getLearnSeconds()); int learned = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds(); form.setLearnedSeconds(learned); BigDecimal hours = person.getHours(); form.setRequiredHours(hours); int requiredSeconds = toRequiredSeconds(hours); form.setRequiredSeconds(requiredSeconds); form.setLearnStatus(resolveLearnStatus(learned, requiredSeconds)); form.setExamScore(person.getExamScore()); form.setPassFlag(person.getPassFlag()); form.setPaperId(task.getPaperId()); form.setPaperName(task.getPaperName()); fillExamFlag(form, task, person); if (detail) { form.setKeyPoints(task.getKeyPoints()); } return form; } /** * 回填考试标识 */ private void fillExamFlag(TmsPersonTaskMineForm form, TmsTaskEntity task, TmsPersonTaskEntity person) { if (!TmsEvalMode.EXAM.equalsCode(task.getEvalMode())) { form.setCanExam(false); form.setExamTip("本次培训无需在线考试"); return; } if (StringUtil.isEmpty(task.getPaperId())) { form.setCanExam(false); form.setExamTip("未配置试卷"); return; } if (person.getSignTime() == null) { form.setCanExam(false); form.setExamTip("请先签到后再考试"); return; } // 配置了课时则须学满后再考(在线/现场一致),便于「学完生成待考试卷」 int required = toRequiredSeconds(person.getHours()); int learned = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds(); if (required > 0 && learned < required) { form.setCanExam(false); form.setExamTip("请先学满规定课时后再考试(已学 " + formatDuration(learned) + " / 需学 " + formatDuration(required) + ")"); return; } Date now = new Date(); if (task.getExamStart() != null && now.before(task.getExamStart())) { form.setCanExam(false); form.setExamTip("未到考试开始时间"); return; } if (task.getExamEnd() != null && now.after(task.getExamEnd())) { form.setCanExam(false); form.setExamTip("已过考试结束时间"); return; } form.setCanExam(true); form.setExamTip(""); } /** * 回填签到标识 */ private void fillSignFlag(TmsPersonTaskMineForm form, TmsTaskEntity task, TmsPersonTaskEntity person) { if (person.getSignTime() != null) { form.setCanSign(false); form.setSignTip("已签到"); return; } String deny = resolveSignDenyReason(task, person); if (StringUtil.isNotEmpty(deny)) { form.setCanSign(false); form.setSignTip(deny); return; } form.setCanSign(true); form.setSignTip(""); } /** 返回不可签到原因;空字符串表示可签到 */ private String resolveSignDenyReason(TmsTaskEntity task, TmsPersonTaskEntity person) { if (TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) { return "任务已取消,不可签到"; } if (task == null || !TmsTaskPublishStatus.PUBLISHED.equalsCode(task.getPublishStatus())) { return "任务未发布,不可签到"; } if (!allowOnlineSign(task.getTrainMode())) { return "该培训方式不支持在线签到,请到培训现场扫码签到"; } Date now = new Date(); if (task.getStartTime() != null && now.before(task.getStartTime())) { return "未到培训开始时间,暂不可签到"; } if (task.getCloseTime() != null && now.after(task.getCloseTime())) { return "培训已关闭,不可签到"; } if (task.getEndTime() != null && now.after(task.getEndTime()) && task.getCloseTime() == null) { return "已过培训结束时间,不可签到"; } return ""; } /** * 是否允许在线签到 */ private boolean allowOnlineSign(String trainMode) { return TmsSignRule.BOTH.equalsCodeIgnoreCase(resolveSignRule(trainMode)); } /** scan=仅扫码 / both=扫码+在线;无配置时 online→both,其它→scan */ private String resolveSignRule(String trainMode) { if (StringUtil.isEmpty(trainMode)) { return ""; } TmsTrainModeEntity mode = trainModeMapper.selectOne( new LambdaQueryWrapper() .eq(TmsTrainModeEntity::getModeCode, trainMode) .last("LIMIT 1")); if (mode != null && StringUtil.isNotEmpty(mode.getSignRule())) { return mode.getSignRule().trim(); } return TmsTrainMode.ONLINE.equalsCodeIgnoreCase(trainMode) ? TmsSignRule.BOTH.getCode() : TmsSignRule.SCAN.getCode(); } /** 启用的在线签到方式要点秒数;未配置或未启用则 0 */ private int resolveOnlineTipSeconds() { TmsSignModeEntity sm = signModeMapper.selectOne( new LambdaQueryWrapper() .eq(TmsSignModeEntity::getModeCode, TmsSignMode.ONLINE.getCode()) .eq(TmsSignModeEntity::getEnabled, "1") .last("LIMIT 1")); if (sm == null || sm.getTipSeconds() == null || sm.getTipSeconds() < 0) { return 0; } return sm.getTipSeconds(); } /** * 换算要求学习秒数 */ private int toRequiredSeconds(BigDecimal hours) { if (hours == null || hours.compareTo(BigDecimal.ZERO) <= 0) { return 0; } return hours.multiply(BigDecimal.valueOf(3600)).intValue(); } /** * 解析学习状态 */ private String resolveLearnStatus(int learned, int required) { if (required > 0 && learned >= required) { return "已学满"; } if (learned > 0) { return "学习中"; } return "未学习"; } /** * 格式化时长 */ private String formatDuration(int seconds) { int h = seconds / 3600; int m = (seconds % 3600) / 60; int s = seconds % 60; if (h > 0) { return h + "小时" + m + "分"; } if (m > 0) { return m + "分" + s + "秒"; } return s + "秒"; } /** * 加载学习课件/附件 */ private List loadFiles(String taskId) { List files = taskFileMapper.selectList( new LambdaQueryWrapper() .eq(TmsTaskFileEntity::getForeignId, taskId) .orderByAsc(TmsTaskFileEntity::getCreatorTime)); if (files.isEmpty()) { return Collections.emptyList(); } List materialIds = files.stream() .map(TmsTaskFileEntity::getMaterialId) .filter(StringUtil::isNotEmpty) .distinct() .collect(Collectors.toList()); Map materialMap = new HashMap<>(); if (!materialIds.isEmpty()) { for (TmsMaterialEntity material : materialMapper.selectBatchIds(materialIds)) { materialMap.put(material.getId(), material); } } List list = new ArrayList<>(); for (TmsTaskFileEntity file : files) { Map meta = extractFileMeta(file.getFileJson(), file.getFileName()); if ((StringUtil.isEmpty(meta.get("url")) || StringUtil.isEmpty(meta.get("ext"))) && StringUtil.isNotEmpty(file.getMaterialId())) { TmsMaterialEntity material = materialMap.get(file.getMaterialId()); if (material != null) { Map matMeta = extractFileMeta(material.getFileJson(), material.getMaterialName()); if (StringUtil.isEmpty(meta.get("url"))) { meta.put("url", matMeta.get("url")); } if (StringUtil.isEmpty(meta.get("ext"))) { meta.put("ext", matMeta.get("ext")); } if (StringUtil.isEmpty(meta.get("fileId"))) { meta.put("fileId", matMeta.get("fileId")); } if (StringUtil.isEmpty(meta.get("fileVersionId"))) { meta.put("fileVersionId", matMeta.get("fileVersionId")); } if (StringUtil.isEmpty(meta.get("name")) && StringUtil.isNotEmpty(matMeta.get("name"))) { meta.put("name", matMeta.get("name")); } } } TmsPersonTaskFileForm item = new TmsPersonTaskFileForm(); item.setId(file.getId()); item.setName(StringUtil.isNotEmpty(file.getFileName()) ? file.getFileName() : meta.get("name")); item.setUrl(meta.get("url")); item.setFileId(meta.get("fileId")); item.setFileVersionId(meta.get("fileVersionId")); item.setFileExt(meta.get("ext")); item.setPreviewType(resolvePreviewType(meta.get("ext"))); list.add(item); } return list; } /** 从 JNPF 附件 JSON 解析 url / fileId / 扩展名等。 */ private Map extractFileMeta(String fileJson, String fileName) { Map meta = new HashMap<>(); meta.put("url", null); meta.put("fileId", null); meta.put("fileVersionId", null); meta.put("ext", null); meta.put("name", fileName); try { if (StringUtil.isNotEmpty(fileJson)) { String text = fileJson.trim(); Map map = null; if (text.startsWith("[")) { List> arr = OBJECT_MAPPER.readValue(text, new TypeReference>>() { }); if (arr != null && !arr.isEmpty()) { map = arr.get(0); } } else { map = OBJECT_MAPPER.readValue(text, new TypeReference>() { }); } if (map != null) { if (map.get("url") != null) { meta.put("url", String.valueOf(map.get("url"))); } Object fid = map.get("fileId"); if (fid == null) { fid = map.get("file_id"); } if (fid != null) { meta.put("fileId", String.valueOf(fid)); } Object fvid = map.get("fileVersionId"); if (fvid == null) { fvid = map.get("fileVersion"); } if (fvid != null) { meta.put("fileVersionId", String.valueOf(fvid)); } if (map.get("fileExtension") != null) { meta.put("ext", String.valueOf(map.get("fileExtension")).toLowerCase()); } if (map.get("name") != null) { meta.put("name", String.valueOf(map.get("name"))); if (StringUtil.isEmpty(meta.get("ext"))) { fileName = String.valueOf(map.get("name")); } } } } } catch (Exception ignored) { // ignore } if (StringUtil.isEmpty(meta.get("ext")) && StringUtil.isNotEmpty(fileName) && fileName.contains(".")) { meta.put("ext", fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase()); } return meta; } /** * 解析附件预览类型 */ private String resolvePreviewType(String ext) { if (StringUtil.isEmpty(ext)) { return "other"; } String e = ext.toLowerCase(); if (Set.of("jpg", "jpeg", "png", "gif", "webp", "bmp", "svg").contains(e)) { return "image"; } if (Set.of("mp4", "webm", "ogg", "mov", "m4v").contains(e)) { return "video"; } if (Set.of("mp3", "wav", "aac", "m4a", "flac").contains(e)) { return "audio"; } if ("pdf".equals(e)) { return "pdf"; } if (Set.of("doc", "docx", "docm", "dot", "dotx", "xls", "xlsx", "xlsm", "xlt", "xltx", "ppt", "pptx", "pptm", "pps", "ppsx", "pot", "potx", "csv", "rtf", "odt", "ods", "odp", "ott", "ots", "otp").contains(e)) { return "office"; } return "other"; } /** * 批量加载培训任务 */ private Map loadTasks(List persons) { List ids = persons.stream() .map(TmsPersonTaskEntity::getTaskId) .filter(StringUtil::isNotEmpty) .distinct() .collect(Collectors.toList()); if (ids.isEmpty()) { return Collections.emptyMap(); } Map map = new HashMap<>(); for (TmsTaskEntity task : taskMapper.selectBatchIds(ids)) { map.put(task.getId(), task); } return map; } /** * 关键字匹配 */ private boolean matchKeyword(TmsPersonTaskEntity person, TmsTaskEntity task, String keyword) { String no = person.getTaskNo() == null ? "" : person.getTaskNo(); String taskNo = task.getTaskNo() == null ? "" : task.getTaskNo(); String subject = task.getSubject() == null ? "" : task.getSubject(); return no.contains(keyword) || taskNo.contains(keyword) || subject.contains(keyword); } /** * 筛选合格标识 */ private boolean matchPassFlag(String queryFlag, String passFlag) { if (StringUtil.isEmpty(queryFlag)) { return true; } String q = queryFlag.trim(); if ("none".equalsIgnoreCase(q)) { return StringUtil.isEmpty(passFlag); } String normalizedQuery = TmsPassFlag.normalize(q); String normalizedPass = TmsPassFlag.normalize(passFlag); if (normalizedQuery == null) { return false; } return normalizedQuery.equals(normalizedPass); } /** * 是否类条件匹配 */ private boolean matchYesNo(String query, boolean actual) { if (StringUtil.isEmpty(query)) { return true; } if ("1".equals(query) || "true".equalsIgnoreCase(query) || "yes".equalsIgnoreCase(query)) { return actual; } if ("0".equals(query) || "false".equalsIgnoreCase(query) || "no".equalsIgnoreCase(query)) { return !actual; } return true; } /** * 解析日开始时间 */ private static Date parseDayStart(String day) { if (StringUtil.isEmpty(day) || "null".equalsIgnoreCase(day)) { return null; } String raw = day.trim(); if (raw.matches("^\\d{13}$")) { return new Date(Long.parseLong(raw)); } if (raw.matches("^\\d{10}$")) { return new Date(Long.parseLong(raw) * 1000L); } if (raw.length() == 10) { raw = raw + " 00:00:00"; } return DateUtil.stringToDate(raw); } /** * 解析日结束时间 */ private static Date parseDayEnd(String day) { if (StringUtil.isEmpty(day) || "null".equalsIgnoreCase(day)) { return null; } String raw = day.trim(); if (raw.matches("^\\d{13}$") || raw.matches("^\\d{10}$")) { long ms = raw.length() == 13 ? Long.parseLong(raw) : Long.parseLong(raw) * 1000L; java.util.Calendar cal = java.util.Calendar.getInstance(); cal.setTimeInMillis(ms); // DateRange 常传当天 00:00,按日末过滤 if (cal.get(java.util.Calendar.HOUR_OF_DAY) == 0 && cal.get(java.util.Calendar.MINUTE) == 0 && cal.get(java.util.Calendar.SECOND) == 0) { cal.set(java.util.Calendar.HOUR_OF_DAY, 23); cal.set(java.util.Calendar.MINUTE, 59); cal.set(java.util.Calendar.SECOND, 59); cal.set(java.util.Calendar.MILLISECOND, 999); } return cal.getTime(); } if (raw.length() == 10) { raw = raw + " 23:59:59"; } return DateUtil.stringToDate(raw); } /** * 校验并获取当前用户个人任务 */ private TmsPersonTaskEntity requireMine(String id) { if (StringUtil.isEmpty(id)) { throw new DataException("个人任务ID不能为空"); } TmsPersonTaskEntity person = personTaskMapper.selectById(id); if (person == null) { throw new DataException("个人培训任务不存在"); } if (!UserProvider.getLoginUserId().equals(person.getUserId())) { throw new DataException("只能查看自己的培训任务"); } return person; } /** * 格式化日期时间 */ private String formatDate(Date date) { return date == null ? null : DateUtil.dateToString(date, FMT); } }