package jnpf.tmsService.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jnpf.exception.DataException; import jnpf.permission.UserApi; import jnpf.permission.entity.UserEntity; import jnpf.tmsEntity.TmsQuestionBankEntity; import jnpf.tmsEntity.TmsQuestionEntity; import jnpf.tmsEntity.TmsQuestionOptionEntity; import jnpf.tmsEntity.question.TmsQuestionBankOption; import jnpf.tmsEntity.question.TmsQuestionForm; import jnpf.tmsEntity.question.TmsQuestionOptionForm; import jnpf.tmsEntity.question.TmsQuestionQuery; import jnpf.tmsMapper.TmsQuestionBankMapper; import jnpf.tmsMapper.TmsQuestionMapper; import jnpf.tmsMapper.TmsQuestionOptionMapper; import jnpf.tmsService.TmsQuestionService; import jnpf.util.DateUtil; import jnpf.util.RandomUtil; import jnpf.util.StringUtil; import jnpf.util.UserProvider; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class TmsQuestionServiceImpl implements TmsQuestionService { private final TmsQuestionMapper questionMapper; private final TmsQuestionOptionMapper optionMapper; private final TmsQuestionBankMapper bankMapper; private final UserApi userApi; @Override public List listBanks() { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.orderByDesc(TmsQuestionBankEntity::getCreatorTime); List list = bankMapper.selectList(qw); List result = new ArrayList<>(list.size()); for (TmsQuestionBankEntity bank : list) { TmsQuestionBankOption opt = new TmsQuestionBankOption(); opt.setId(bank.getId()); opt.setFullName(bank.getBankName()); result.add(opt); } return result; } @Override public List> listAdmins() { QueryWrapper qw = new QueryWrapper<>(); qw.select("DISTINCT admin_user_id"); qw.isNotNull("admin_user_id"); qw.ne("admin_user_id", ""); List rows = questionMapper.selectList(qw); List userIds = rows.stream() .map(TmsQuestionEntity::getAdminUserId) .filter(StringUtil::isNotEmpty) .distinct() .collect(Collectors.toList()); if (userIds.isEmpty()) { return Collections.emptyList(); } Map nameMap = loadUserNames(userIds); List> result = new ArrayList<>(); for (String userId : userIds) { Map item = new HashMap<>(2); item.put("id", userId); item.put("fullName", nameMap.getOrDefault(userId, userId)); result.add(item); } return result; } @Override public List getList(TmsQuestionQuery query) { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); if (StringUtil.isNotEmpty(query.getBankId())) { qw.eq(TmsQuestionEntity::getBankId, query.getBankId()); } if (StringUtil.isNotEmpty(query.getQuestionType())) { qw.eq(TmsQuestionEntity::getQuestionType, query.getQuestionType()); } if (StringUtil.isNotEmpty(query.getBizStatus())) { qw.eq(TmsQuestionEntity::getBizStatus, query.getBizStatus()); } if (StringUtil.isNotEmpty(query.getAdminUserId())) { qw.eq(TmsQuestionEntity::getAdminUserId, query.getAdminUserId()); } if (StringUtil.isNotEmpty(query.getKeyword()) && !"null".equalsIgnoreCase(query.getKeyword())) { String kw = query.getKeyword().trim(); qw.and(w -> w.like(TmsQuestionEntity::getStem, kw) .or() .like(TmsQuestionEntity::getQuestionNo, kw)); } qw.orderByDesc(TmsQuestionEntity::getCreatorTime); Page page = new Page<>(query.getCurrentPage(), query.getPageSize()); Page result = questionMapper.selectPage(page, qw); List records = result.getRecords(); Map bankNames = loadBankNames( records.stream().map(TmsQuestionEntity::getBankId).filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList())); Map userNames = loadUserNames( records.stream().map(TmsQuestionEntity::getAdminUserId).filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList())); List list = new ArrayList<>(records.size()); for (TmsQuestionEntity entity : records) { list.add(toForm(entity, bankNames, userNames, false)); } query.setData(list, result.getTotal()); return list; } @Override public TmsQuestionForm getInfo(String id) { TmsQuestionEntity entity = questionMapper.selectById(id); if (entity == null) { throw new DataException("试题不存在"); } Map bankNames = loadBankNames(Collections.singletonList(entity.getBankId())); Map userNames = loadUserNames( StringUtil.isEmpty(entity.getAdminUserId()) ? Collections.emptyList() : Collections.singletonList(entity.getAdminUserId())); return toForm(entity, bankNames, userNames, true); } @Override @Transactional(rollbackFor = Exception.class) public String create(TmsQuestionForm form) { validateForm(form); String id = RandomUtil.uuId(); Date now = new Date(); String userId = UserProvider.getLoginUserId(); TmsQuestionEntity entity = new TmsQuestionEntity(); entity.setId(id); entity.setQuestionNo(nextQuestionNo()); entity.setBankId(form.getBankId()); entity.setQuestionType(form.getQuestionType()); entity.setDifficulty(form.getDifficulty()); entity.setSourceType(form.getSourceType()); entity.setStem(form.getStem()); entity.setAnalysis(form.getAnalysis()); entity.setAdminUserId(StringUtil.isNotEmpty(form.getAdminUserId()) ? form.getAdminUserId() : userId); entity.setBizStatus(StringUtil.isNotEmpty(form.getBizStatus()) ? form.getBizStatus() : "closed"); entity.setCreatorUserId(userId); entity.setCreatorTime(now); questionMapper.insert(entity); saveOptions(id, form.getOptions()); refreshBankCount(form.getBankId()); return id; } @Override @Transactional(rollbackFor = Exception.class) public void update(String id, TmsQuestionForm form) { TmsQuestionEntity entity = questionMapper.selectById(id); if (entity == null) { throw new DataException("试题不存在"); } validateForm(form); String oldBankId = entity.getBankId(); entity.setBankId(form.getBankId()); entity.setQuestionType(form.getQuestionType()); entity.setDifficulty(form.getDifficulty()); entity.setSourceType(form.getSourceType()); entity.setStem(form.getStem()); entity.setAnalysis(form.getAnalysis()); if (StringUtil.isNotEmpty(form.getAdminUserId())) { entity.setAdminUserId(form.getAdminUserId()); } if (StringUtil.isNotEmpty(form.getBizStatus())) { entity.setBizStatus(form.getBizStatus()); } entity.setLastModifyUserId(UserProvider.getLoginUserId()); entity.setLastModifyTime(new Date()); questionMapper.updateById(entity); // 选项全量替换 LambdaQueryWrapper delQw = new LambdaQueryWrapper<>(); delQw.eq(TmsQuestionOptionEntity::getForeignId, id); optionMapper.delete(delQw); saveOptions(id, form.getOptions()); refreshBankCount(oldBankId); if (!Objects.equals(oldBankId, form.getBankId())) { refreshBankCount(form.getBankId()); } } @Override @Transactional(rollbackFor = Exception.class) public void invalidate(String id) { TmsQuestionEntity entity = questionMapper.selectById(id); if (entity == null) { return; } String bankId = entity.getBankId(); // TableLogic:写 f_delete_mark,列表/详情自动过滤 questionMapper.deleteById(id); LambdaQueryWrapper delQw = new LambdaQueryWrapper<>(); delQw.eq(TmsQuestionOptionEntity::getForeignId, id); optionMapper.delete(delQw); refreshBankCount(bankId); } private void validateForm(TmsQuestionForm form) { if (form == null) { throw new DataException("参数不能为空"); } if (StringUtil.isEmpty(form.getBankId())) { throw new DataException("请选择所属题库"); } if (StringUtil.isEmpty(form.getQuestionType())) { throw new DataException("请选择题型"); } if (StringUtil.isEmpty(form.getStem())) { throw new DataException("请填写题干"); } TmsQuestionBankEntity bank = bankMapper.selectById(form.getBankId()); if (bank == null) { throw new DataException("题库不存在或已删除"); } } private void saveOptions(String questionId, List options) { if (options == null || options.isEmpty()) { return; } Date now = new Date(); String userId = UserProvider.getLoginUserId(); int i = 0; for (TmsQuestionOptionForm opt : options) { TmsQuestionOptionEntity row = new TmsQuestionOptionEntity(); row.setId(RandomUtil.uuId()); row.setForeignId(questionId); row.setSortNo(opt.getSortNo() != null ? opt.getSortNo() : ++i); row.setOptionLabel(opt.getOptionLabel()); row.setOptionContent(opt.getOptionContent()); row.setIsCorrect(StringUtil.isNotEmpty(opt.getIsCorrect()) ? opt.getIsCorrect() : "0"); row.setCreatorUserId(userId); row.setCreatorTime(now); optionMapper.insert(row); } } /** 按题库下未删除试题实计回写 question_count(创建 +1 / 删除 -1 / 换库两边重算) */ private void refreshBankCount(String bankId) { if (StringUtil.isEmpty(bankId)) { return; } TmsQuestionBankEntity bank = bankMapper.selectById(bankId); if (bank == null) { return; } LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(TmsQuestionEntity::getBankId, bankId); Long count = questionMapper.selectCount(qw); bank.setQuestionCount(count == null ? 0 : count.intValue()); bank.setLastModifyUserId(UserProvider.getLoginUserId()); bank.setLastModifyTime(new Date()); bankMapper.updateById(bank); } /** * 试题编号:年份后两位 + 4 位流水,如 260001。 * 按当年最大号 +1,含已删记录,避免流水回退。 */ private synchronized String nextQuestionNo() { String prefix = String.format("%02d", LocalDate.now().getYear() % 100); String maxNo = questionMapper.selectMaxQuestionNo(prefix); int next = 1; if (StringUtil.isNotEmpty(maxNo) && maxNo.length() >= 6 && maxNo.startsWith(prefix)) { try { next = Integer.parseInt(maxNo.substring(prefix.length())) + 1; } catch (NumberFormatException ignored) { next = 1; } } if (next > 9999) { throw new DataException("当年试题编号已用尽(" + prefix + "9999)"); } return prefix + String.format("%04d", next); } private TmsQuestionForm toForm( TmsQuestionEntity entity, Map bankNames, Map userNames, boolean withOptions) { TmsQuestionForm form = new TmsQuestionForm(); form.setId(entity.getId()); form.setQuestionNo(entity.getQuestionNo()); form.setBankId(entity.getBankId()); form.setBankName(bankNames.get(entity.getBankId())); form.setQuestionType(entity.getQuestionType()); form.setDifficulty(entity.getDifficulty()); form.setSourceType(entity.getSourceType()); form.setStem(entity.getStem()); form.setAnalysis(entity.getAnalysis()); form.setAdminUserId(entity.getAdminUserId()); form.setAdminUserName(userNames.get(entity.getAdminUserId())); form.setBizStatus(entity.getBizStatus()); form.setCreatorTime(entity.getCreatorTime() == null ? null : DateUtil.dateToString(entity.getCreatorTime(), "yyyy-MM-dd HH:mm:ss")); if (withOptions) { form.setOptions(loadOptions(entity.getId())); } return form; } private List loadOptions(String questionId) { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(TmsQuestionOptionEntity::getForeignId, questionId); qw.orderByAsc(TmsQuestionOptionEntity::getSortNo); List rows = optionMapper.selectList(qw); List list = new ArrayList<>(rows.size()); for (TmsQuestionOptionEntity row : rows) { TmsQuestionOptionForm opt = new TmsQuestionOptionForm(); opt.setId(row.getId()); opt.setSortNo(row.getSortNo()); opt.setOptionLabel(row.getOptionLabel()); opt.setOptionContent(row.getOptionContent()); opt.setIsCorrect(row.getIsCorrect()); list.add(opt); } return list; } private Map loadBankNames(List bankIds) { if (bankIds == null || bankIds.isEmpty()) { return Collections.emptyMap(); } List banks = bankMapper.selectBatchIds(bankIds); Map map = new HashMap<>(); for (TmsQuestionBankEntity bank : banks) { map.put(bank.getId(), bank.getBankName()); } return map; } private Map loadUserNames(List userIds) { if (userIds == null || userIds.isEmpty()) { return Collections.emptyMap(); } try { List users = userApi.getUserName(userIds); if (users == null || users.isEmpty()) { return Collections.emptyMap(); } return users.stream() .filter(Objects::nonNull) .collect(Collectors.toMap(UserEntity::getId, UserEntity::getRealName, (a, b) -> a)); } catch (Exception ex) { return Collections.emptyMap(); } } }