package jnpf.tmsService.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import jnpf.exception.DataException; import jnpf.tmsEntity.TmsMaterialEntity; import jnpf.tmsEntity.TmsPersonTaskEntity; import jnpf.tmsEntity.TmsTaskEntity; import jnpf.tmsEntity.TmsTaskFileEntity; import jnpf.tmsEntity.TmsTrainModeEntity; 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.tmsMapper.TmsMaterialMapper; import jnpf.tmsMapper.TmsPersonTaskMapper; import jnpf.tmsMapper.TmsTaskFileMapper; import jnpf.tmsMapper.TmsTaskMapper; import jnpf.tmsMapper.TmsTrainModeMapper; import jnpf.tmsService.TmsPersonTaskService; import jnpf.util.DateUtil; import jnpf.util.StringUtil; import jnpf.util.UserProvider; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; 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; @Service @RequiredArgsConstructor public class TmsPersonTaskServiceImpl implements TmsPersonTaskService { private static final String FMT = "yyyy-MM-dd HH:mm:ss"; private static final String STATUS_CANCELLED = "cancelled"; private static final String STATUS_DONE = "done"; private static final String STATUS_SIGNED = "signed"; private static final String STATUS_TODO = "todo"; private static final String SIGN_MODE_ONLINE = "online"; 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; @Override public List getMine(TmsPersonTaskQuery query) { String userId = UserProvider.getLoginUserId(); LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(TmsPersonTaskEntity::getUserId, userId); qw.ne(TmsPersonTaskEntity::getBizStatus, STATUS_CANCELLED); if (StringUtil.isNotEmpty(query.getBizStatus())) { qw.eq(TmsPersonTaskEntity::getBizStatus, query.getBizStatus().trim()); } qw.orderByDesc(TmsPersonTaskEntity::getCreatorTime); List persons = personTaskMapper.selectList(qw); Map taskMap = loadTasks(persons); String keyword = StringUtil.isNotEmpty(query.getKeyword()) ? query.getKeyword().trim() : null; List all = new ArrayList<>(); for (TmsPersonTaskEntity person : persons) { TmsTaskEntity task = taskMap.get(person.getTaskId()); if (task == null || !"published".equals(task.getPublishStatus())) { continue; } if (keyword != null && !matchKeyword(person, task, keyword)) { continue; } all.add(toForm(person, task, false)); } 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("培训任务不存在"); } 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 = "exam".equals(task.getEvalMode()) && StringUtil.isNotEmpty(task.getPaperId()); if (justFull) { if (!needExam && !STATUS_DONE.equals(person.getBizStatus()) && !STATUS_CANCELLED.equals(person.getBizStatus())) { person.setBizStatus(STATUS_DONE); person.setLastModifyUserId(UserProvider.getLoginUserId()); person.setLastModifyTime(new Date()); personTaskMapper.updateById(person); result.setAutoCompleted(true); result.setBizStatus(STATUS_DONE); result.setMessage("已学满规定课时,系统已自动确认完成"); } else if (needExam) { if (person.getSignTime() == null) { result.setMessage("已学满规定课时,请先签到后再进入考试"); } else { result.setMessage("已学满规定课时,可以进入考试"); } } } TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm(); fillExamFlag(tmp, task, person); result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam())); result.setExamTip(tmp.getExamTip()); return result; } @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 (!"published".equals(task.getPublishStatus())) { throw new DataException("任务未发布,不可签到"); } if (STATUS_CANCELLED.equals(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(SIGN_MODE_ONLINE); if (STATUS_TODO.equals(person.getBizStatus()) || StringUtil.isEmpty(person.getBizStatus())) { person.setBizStatus(STATUS_SIGNED); } person.setLastModifyUserId(UserProvider.getLoginUserId()); person.setLastModifyTime(now); personTaskMapper.updateById(person); 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); result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam())); result.setExamTip(tmp.getExamTip()); return result; } 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); 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 (!"exam".equals(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; } if ("online".equals(task.getTrainMode())) { int required = toRequiredSeconds(person.getHours()); int learned = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds(); if (required <= 0) { form.setCanExam(false); form.setExamTip("未配置课时,暂不能考试"); return; } if (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 (STATUS_CANCELLED.equals(person.getBizStatus())) { return "任务已取消,不可签到"; } if (task == null || !"published".equals(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) { if (StringUtil.isEmpty(trainMode)) { return false; } // 配置表优先:sign_rule=both 才允许在线;无配置时按字典约定 online 允许 TmsTrainModeEntity mode = trainModeMapper.selectOne( new LambdaQueryWrapper() .eq(TmsTrainModeEntity::getModeCode, trainMode) .last("LIMIT 1")); if (mode != null && StringUtil.isNotEmpty(mode.getSignRule())) { return "both".equalsIgnoreCase(mode.getSignRule()); } return "online".equalsIgnoreCase(trainMode); } 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) { String url = extractUrl(file.getFileJson()); String ext = extractExt(file.getFileJson(), file.getFileName()); if ((StringUtil.isEmpty(url) || StringUtil.isEmpty(ext)) && StringUtil.isNotEmpty(file.getMaterialId())) { TmsMaterialEntity material = materialMap.get(file.getMaterialId()); if (material != null) { if (StringUtil.isEmpty(url)) { url = extractUrl(material.getFileJson()); } if (StringUtil.isEmpty(ext)) { ext = extractExt(material.getFileJson(), material.getMaterialName()); } } } TmsPersonTaskFileForm item = new TmsPersonTaskFileForm(); item.setId(file.getId()); item.setName(file.getFileName()); item.setUrl(url); item.setFileExt(ext); item.setPreviewType(resolvePreviewType(ext)); list.add(item); } return list; } private String extractExt(String fileJson, String fileName) { try { if (StringUtil.isNotEmpty(fileJson)) { String text = fileJson.trim(); if (text.startsWith("[")) { List> arr = OBJECT_MAPPER.readValue(text, new TypeReference>>() { }); if (arr != null && !arr.isEmpty() && arr.get(0).get("fileExtension") != null) { return String.valueOf(arr.get(0).get("fileExtension")).toLowerCase(); } if (arr != null && !arr.isEmpty() && arr.get(0).get("name") != null) { fileName = String.valueOf(arr.get(0).get("name")); } } else { Map map = OBJECT_MAPPER.readValue(text, new TypeReference>() { }); if (map.get("fileExtension") != null) { return String.valueOf(map.get("fileExtension")).toLowerCase(); } if (map.get("name") != null) { fileName = String.valueOf(map.get("name")); } } } } catch (Exception ignored) { // ignore } if (StringUtil.isEmpty(fileName) || !fileName.contains(".")) { return ""; } return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(); } 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", "xls", "xlsx", "ppt", "pptx").contains(e)) { return "office"; } return "other"; } private String extractUrl(String fileJson) { if (StringUtil.isEmpty(fileJson)) { return null; } try { String text = fileJson.trim(); if (text.startsWith("[")) { List> arr = OBJECT_MAPPER.readValue(text, new TypeReference>>() { }); if (arr == null || arr.isEmpty() || arr.get(0).get("url") == null) { return null; } return String.valueOf(arr.get(0).get("url")); } Map map = OBJECT_MAPPER.readValue(text, new TypeReference>() { }); return map.get("url") == null ? null : String.valueOf(map.get("url")); } catch (Exception ex) { return null; } } 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 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); } }