forked from liweijie/education
feat: 提交课表冲突检查相关功能模块
This commit is contained in:
+144
@@ -0,0 +1,144 @@
|
||||
package com.roomroot.jwgl.service;
|
||||
|
||||
import com.roomroot.jwgl.entity.TimetableConflictResult;
|
||||
import com.roomroot.jwgl.unit.PageResult;
|
||||
import com.roomroot.jwgl.unit.conflict.TimetableConflictDimension;
|
||||
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictCardVO;
|
||||
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO;
|
||||
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictSummaryVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 课表冲突检查服务接口。
|
||||
* <p>
|
||||
* 现在支持两套"存储/读取"机制,调用方可按需选择:
|
||||
* <ul>
|
||||
* <li><b>内存模式(默认,兼容旧逻辑)</b>:检查结果只存在 ConcurrentHashMap,JVM 重启丢失。
|
||||
* 方法签名不带 jcpch/useDb。</li>
|
||||
* <li><b>落库模式(方案 A)</b>:检查结果同时写"课表_冲突检查结果"表,
|
||||
* 分页可按 useDb=true / jcpch 指定某一批次从 DB 读。</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @author management
|
||||
*/
|
||||
public interface TimetableConflictService {
|
||||
|
||||
/**
|
||||
* 加载课表冲突检查页面的初始化汇总(卡片列表)。
|
||||
* <p>
|
||||
* 用于 checkAll / reset 内部生成卡片结构;同时也可以返回"已落库历史检查结果回填"的状态,
|
||||
* 若该年度该维度在 DB 有最新批次也会自动填充 checked=true。
|
||||
* </p>
|
||||
*/
|
||||
TimetableConflictSummaryVO loadSummary(Integer nd);
|
||||
|
||||
// =========================================================
|
||||
// 检查相关(兼容旧签名 + 新签名)
|
||||
// =========================================================
|
||||
|
||||
/**
|
||||
* 【旧签名,内存模式】单维度检查。
|
||||
* 等价于 {@code checkDimension(nd, dimension, null, false)}。
|
||||
*/
|
||||
default TimetableConflictCardVO checkDimension(Integer nd, TimetableConflictDimension dimension) {
|
||||
return checkDimension(nd, dimension, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【完整签名】单维度检查。
|
||||
*
|
||||
* @param nd 年度
|
||||
* @param dimension 维度枚举
|
||||
* @param jcpch 检查批次号(=null 则内部自动生成;checkAll 调用时所有维度传同一个批次号)
|
||||
* @param writeToDb true=同时写入落库表"课表_冲突检查结果",false=只写内存缓存
|
||||
* @return 卡片结果 VO(内部会保证 conflictCount / checked 的正确性)
|
||||
*/
|
||||
TimetableConflictCardVO checkDimension(Integer nd, TimetableConflictDimension dimension,
|
||||
String jcpch, boolean writeToDb);
|
||||
|
||||
/**
|
||||
* 【旧签名,内存模式】全部维度检查。等价 checkAll(nd, false)。
|
||||
*/
|
||||
default TimetableConflictSummaryVO checkAll(Integer nd) {
|
||||
return checkAll(nd, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【完整签名】全部维度检查。
|
||||
*
|
||||
* @param nd 年度
|
||||
* @param writeToDb true=写落库表(自动生成统一批次号,所有维度共用;返回 summary 会带上最后生成的批次号),false=只写内存
|
||||
* @return 汇总 VO
|
||||
*/
|
||||
TimetableConflictSummaryVO checkAll(Integer nd, boolean writeToDb);
|
||||
|
||||
/**
|
||||
* 返回 checkAll(writeToDb=true) 生成的批次号(如果 writeToDb=false,返回 null)。
|
||||
* 提供给 Controller 层把"本次检查批次号"放到 Result.message / data 扩展字段给前端用。
|
||||
*/
|
||||
String getLastBatchNo();
|
||||
|
||||
// =========================================================
|
||||
// 重置
|
||||
// =========================================================
|
||||
|
||||
/**
|
||||
* 【旧签名】重置:清内存 + 不清 DB。
|
||||
*/
|
||||
default TimetableConflictSummaryVO reset(Integer nd) {
|
||||
return reset(nd, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置检查结果。
|
||||
*
|
||||
* @param nd 年度
|
||||
* @param clearDb true=同时 DELETE 落库表里该年度的所有批次;false=只清内存缓存(保持历史)
|
||||
*/
|
||||
TimetableConflictSummaryVO reset(Integer nd, boolean clearDb);
|
||||
|
||||
// =========================================================
|
||||
// 分页查询明细
|
||||
// =========================================================
|
||||
|
||||
/**
|
||||
* 【旧签名,内存模式】分页查询明细。
|
||||
* 等价于 {@code getDetailsPage(nd, dimensionCode, pageNum, pageSize, false, null)}。
|
||||
*/
|
||||
default PageResult<TimetableConflictDetailVO> getDetailsPage(Integer nd, String dimensionCode,
|
||||
Integer pageNum, Integer pageSize) {
|
||||
return getDetailsPage(nd, dimensionCode, pageNum, pageSize, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【完整签名】分页查询明细。
|
||||
*
|
||||
* @param nd 年度
|
||||
* @param dimensionCode 维度编码,null / "ALL"=合并全部维度
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页大小
|
||||
* @param useDb true=从"课表_冲突检查结果"表查;false=从内存缓存查(旧行为)
|
||||
* @param jcpch useDb=true 时可选:具体查哪一个批次号;null 自动取该年度 DB 里"最新批次号"(MAX 创建时间对应批次号)
|
||||
*/
|
||||
PageResult<TimetableConflictDetailVO> getDetailsPage(Integer nd, String dimensionCode,
|
||||
Integer pageNum, Integer pageSize,
|
||||
boolean useDb, String jcpch);
|
||||
|
||||
// =========================================================
|
||||
// 便捷方法:批次号列表查询、实体→VO 转换等
|
||||
// =========================================================
|
||||
|
||||
/**
|
||||
* 查询指定年度的所有检查批次号(倒序,最新批次排第一个)。
|
||||
* 给前端下拉框"选择某次历史检查查看详情"。
|
||||
*/
|
||||
List<String> listBatchNos(Integer nd);
|
||||
|
||||
/**
|
||||
* 把一条"课表_冲突检查结果"实体转成前端明细表格需要的 TimetableConflictDetailVO。
|
||||
*/
|
||||
TimetableConflictDetailVO toDetailVO(TimetableConflictResult entity);
|
||||
}
|
||||
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
package com.roomroot.jwgl.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.roomroot.jwgl.entity.TimetableConflictResult;
|
||||
import com.roomroot.jwgl.mapper.SSKCBFZJYMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCBJSMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCBXYDMapper;
|
||||
import com.roomroot.jwgl.mapper.TimetableConflictResultMapper;
|
||||
import com.roomroot.jwgl.service.TimetableConflictService;
|
||||
import com.roomroot.jwgl.unit.PageResult;
|
||||
import com.roomroot.jwgl.unit.conflict.TimetableConflictDimension;
|
||||
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictCardVO;
|
||||
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictDetailVO;
|
||||
import com.roomroot.jwgl.vo.timetableconflict.TimetableConflictSummaryVO;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 课表冲突检查服务实现类。
|
||||
* <p>
|
||||
* 现在支持:
|
||||
* <ul>
|
||||
* <li><b>内存模式(兼容旧前端)</b>:ConcurrentHashMap 缓存(默认行为,不改代码也能跑);</li>
|
||||
* <li><b>落库模式(方案 A)</b>:检查结果写入【课表_冲突检查结果】表,分页可用 useDb=true 查询。</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @author management
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TimetableConflictServiceImpl implements TimetableConflictService {
|
||||
|
||||
// =====================================================================
|
||||
// 依赖注入
|
||||
// =====================================================================
|
||||
|
||||
@Resource
|
||||
private SSKCBXYDMapper sskcbxydMapper;
|
||||
|
||||
@Resource
|
||||
private SSKCBFZJYMapper sskcbfzjyMapper;
|
||||
|
||||
@Resource
|
||||
private SSKCBJSMapper sskcbjsMapper;
|
||||
|
||||
/**
|
||||
* 方案 A 落库表 Mapper(继承 MP BaseMapper,开箱自带 insert / selectList / delete)。
|
||||
*/
|
||||
@Resource
|
||||
private TimetableConflictResultMapper conflictResultMapper;
|
||||
|
||||
// =====================================================================
|
||||
// 内存缓存(不需要持久化到数据库,临时保存每次检查结果)
|
||||
// Key 格式:"<年度>_<维度编码>" 例:"2026_TEACHER_CONFLICT"
|
||||
// =====================================================================
|
||||
private final ConcurrentHashMap<String, List<TimetableConflictDetailVO>> detailCache
|
||||
= new ConcurrentHashMap<>();
|
||||
|
||||
private String cacheKey(Integer nd, String dimensionCode) {
|
||||
return nd + "_" + dimensionCode;
|
||||
}
|
||||
|
||||
/** 最近一次 checkAll(writeToDb=true) 生成的批次号 */
|
||||
private volatile String lastBatchNo;
|
||||
|
||||
private static final DateTimeFormatter BATCH_FMT
|
||||
= DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss-SSS");
|
||||
|
||||
// =====================================================================
|
||||
// 工具:生成批次号 / VO ↔ Entity 转换
|
||||
// =====================================================================
|
||||
|
||||
private String generateBatchNo(Integer nd) {
|
||||
// 格式:{年度}_{yyyyMMdd-HHmmss-SSS},例:2026_20260821-153000-123
|
||||
String ts = LocalDateTime.now().format(BATCH_FMT);
|
||||
return nd + "_" + ts;
|
||||
}
|
||||
|
||||
private TimetableConflictResult toResultEntity(Integer nd, String jcpch,
|
||||
TimetableConflictDetailVO vo) {
|
||||
TimetableConflictResult e = new TimetableConflictResult();
|
||||
e.setNd(nd);
|
||||
e.setJcpch(jcpch);
|
||||
e.setWdbm(vo.getDimensionCode());
|
||||
e.setWdbt(vo.getDimensionTitle());
|
||||
e.setZybh(vo.getResourceId());
|
||||
e.setZymc(vo.getResourceName());
|
||||
e.setRq(vo.getRq());
|
||||
e.setJc(vo.getJc());
|
||||
e.setZykcsl(vo.getOccupiedLessonCount());
|
||||
e.setKcmclb(vo.getCourseNames());
|
||||
e.setCtkcbhlb(vo.getConflictSskcbBhs());
|
||||
e.setCtyy(vo.getReason());
|
||||
e.setCth(vo.getConflictNo());
|
||||
e.setBcmclb(vo.getXydNames());
|
||||
e.setJyxmlb(vo.getTeacherNames());
|
||||
e.setCdmclb(vo.getClassroomNames());
|
||||
e.setZrdwlb(vo.getResponsibleDept());
|
||||
return e;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimetableConflictDetailVO toDetailVO(TimetableConflictResult e) {
|
||||
if (e == null) return null;
|
||||
return TimetableConflictDetailVO.builder()
|
||||
.dimensionCode(e.getWdbm())
|
||||
.dimensionTitle(e.getWdbt())
|
||||
.resourceId(e.getZybh())
|
||||
.resourceName(e.getZymc())
|
||||
.rq(e.getRq())
|
||||
.jc(e.getJc())
|
||||
.occupiedLessonCount(e.getZykcsl())
|
||||
.courseNames(e.getKcmclb())
|
||||
.conflictSskcbBhs(e.getCtkcbhlb())
|
||||
.reason(e.getCtyy())
|
||||
.conflictNo(e.getCth())
|
||||
.xydNames(e.getBcmclb())
|
||||
.teacherNames(e.getJyxmlb())
|
||||
.classroomNames(e.getCdmclb())
|
||||
.responsibleDept(e.getZrdwlb())
|
||||
.build();
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 接口实现:初始化汇总
|
||||
// =====================================================================
|
||||
|
||||
@Override
|
||||
public TimetableConflictSummaryVO loadSummary(Integer nd) {
|
||||
TimetableConflictSummaryVO summary = new TimetableConflictSummaryVO();
|
||||
summary.setNd(nd);
|
||||
|
||||
for (TimetableConflictDimension dim : TimetableConflictDimension.values()) {
|
||||
TimetableConflictCardVO card = TimetableConflictCardVO.builder()
|
||||
.dimensionCode(dim.getCode())
|
||||
.title(dim.getTitle())
|
||||
.description(dim.getDescription())
|
||||
.checked(false)
|
||||
.conflictCount(0)
|
||||
.build();
|
||||
|
||||
// 1) 优先:内存缓存有就回填(最高优先级:刚点过检查的结果)
|
||||
List<TimetableConflictDetailVO> cached = detailCache.get(cacheKey(nd, dim.getCode()));
|
||||
if (cached != null) {
|
||||
card.setChecked(true);
|
||||
card.setConflictCount(cached.size());
|
||||
} else {
|
||||
// 2) 兜底:DB 里该年度最新批次有数据就回填(避免"重启完 JVM 卡片全变未检查")
|
||||
Long dbCnt = countLatestInDb(nd, dim.getCode());
|
||||
if (dbCnt != null && dbCnt > 0) {
|
||||
card.setChecked(true);
|
||||
card.setConflictCount(dbCnt.intValue());
|
||||
}
|
||||
}
|
||||
summary.getCards().add(card);
|
||||
}
|
||||
|
||||
summary.recalculateTotal();
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* DB 辅助:返回某年度某维度在"最新批次"中的冲突条数。
|
||||
*/
|
||||
private Long countLatestInDb(Integer nd, String wdbm) {
|
||||
// 1. 查该年度在 DB 中"最新批次号"
|
||||
List<String> batchNos = listBatchNos(nd);
|
||||
if (batchNos.isEmpty()) return null;
|
||||
String latest = batchNos.get(0);
|
||||
|
||||
// 2. 按 nd+jcpch+wdbm count
|
||||
LambdaQueryWrapper<TimetableConflictResult> qw = new LambdaQueryWrapper<>();
|
||||
qw.eq(TimetableConflictResult::getNd, nd)
|
||||
.eq(TimetableConflictResult::getJcpch, latest)
|
||||
.eq(TimetableConflictResult::getWdbm, wdbm);
|
||||
try {
|
||||
return conflictResultMapper.selectCount(qw);
|
||||
} catch (Exception ex) {
|
||||
log.warn("[课表冲突检查] 回填DB历史冲突数失败(nd={},wdbm={}): {}", nd, wdbm, ex.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 接口实现:单维度检查(完整签名 = 内存 + 可选落库)
|
||||
// =====================================================================
|
||||
|
||||
@Override
|
||||
public TimetableConflictCardVO checkDimension(Integer nd, TimetableConflictDimension dimension,
|
||||
String jcpch, boolean writeToDb) {
|
||||
if (dimension == null) {
|
||||
throw new IllegalArgumentException("维度不能为空");
|
||||
}
|
||||
|
||||
// 1. 跑聚合 SQL(和旧版完全一样,保证结果一致性)
|
||||
List<TimetableConflictDetailVO> details;
|
||||
long startMs = System.currentTimeMillis();
|
||||
try {
|
||||
details = switch (dimension) {
|
||||
case TEAM_CONFLICT -> sskcbxydMapper.selectTeamConflictDetails(nd);
|
||||
case ELECTIVE_REQUIRED_CONFLICT -> sskcbxydMapper.selectElectiveRequiredConflictDetails(nd);
|
||||
case TEACHER_CONFLICT -> sskcbfzjyMapper.selectTeacherConflictDetails(nd);
|
||||
case CLASSROOM_CONFLICT -> sskcbjsMapper.selectClassroomConflictDetails(nd);
|
||||
case GUARANTEE_CONFLICT, EVENT_CONFLICT -> Collections.emptyList();
|
||||
};
|
||||
} catch (Exception e) {
|
||||
log.error("[课表冲突检查][维度:{}][年度:{}] 执行SQL失败: {}",
|
||||
dimension.getCode(), nd, e.getMessage(), e);
|
||||
details = Collections.emptyList();
|
||||
}
|
||||
if (details == null) details = Collections.emptyList();
|
||||
long costMs = System.currentTimeMillis() - startMs;
|
||||
log.info("[课表冲突检查][维度:{}][年度:{}] SQL完成, {} 条冲突, 耗时 {} ms",
|
||||
dimension.getCode(), nd, details.size(), costMs);
|
||||
|
||||
// 2. 写内存缓存(保持原逻辑,旧前端不动也能看)
|
||||
detailCache.put(cacheKey(nd, dimension.getCode()), details);
|
||||
|
||||
// 3. 可选:写落库表(方案 A)
|
||||
if (writeToDb) {
|
||||
String batch = jcpch;
|
||||
if (batch == null || batch.isEmpty()) {
|
||||
batch = generateBatchNo(nd);
|
||||
}
|
||||
persistOneDimension(nd, batch, dimension.getCode(), details);
|
||||
}
|
||||
|
||||
// 4. 返回卡片
|
||||
return TimetableConflictCardVO.builder()
|
||||
.dimensionCode(dimension.getCode())
|
||||
.title(dimension.getTitle())
|
||||
.description(dimension.getDescription())
|
||||
.checked(true)
|
||||
.conflictCount(details.size())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 把一个维度的明细持久化到【课表_冲突检查结果】。
|
||||
* 幂等策略:先 DELETE (nd, jcpch, wdbm) 再 INSERT 明细,保证同一批次重跑不重复。
|
||||
*/
|
||||
private void persistOneDimension(Integer nd, String jcpch, String wdbm,
|
||||
List<TimetableConflictDetailVO> details) {
|
||||
long start = System.currentTimeMillis();
|
||||
// 1) 幂等清理
|
||||
LambdaQueryWrapper<TimetableConflictResult> qw = new LambdaQueryWrapper<>();
|
||||
qw.eq(TimetableConflictResult::getNd, nd)
|
||||
.eq(TimetableConflictResult::getJcpch, jcpch)
|
||||
.eq(TimetableConflictResult::getWdbm, wdbm);
|
||||
conflictResultMapper.delete(qw);
|
||||
|
||||
// 2) 逐条 INSERT(一次检查一个维度最多几千条,直接循环够用;要更高性能改成 SqlSession Batch)
|
||||
int ok = 0, fail = 0;
|
||||
for (TimetableConflictDetailVO vo : details) {
|
||||
try {
|
||||
conflictResultMapper.insert(toResultEntity(nd, jcpch, vo));
|
||||
ok++;
|
||||
} catch (Exception e) {
|
||||
fail++;
|
||||
if (fail <= 3) {
|
||||
log.warn("[课表冲突检查][落库] nd={},jcpch={},wdbm={},插入冲突号={}失败: {}",
|
||||
nd, jcpch, wdbm, vo.getConflictNo(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("[课表冲突检查][落库][维度:{}] nd={},jcpch={},成功{}条/失败{}条,耗时{}ms",
|
||||
wdbm, nd, jcpch, ok, fail, (System.currentTimeMillis() - start));
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 接口实现:全部维度检查(完整签名)
|
||||
// =====================================================================
|
||||
|
||||
@Override
|
||||
public TimetableConflictSummaryVO checkAll(Integer nd, boolean writeToDb) {
|
||||
TimetableConflictSummaryVO summary = loadSummary(nd);
|
||||
|
||||
// 1) writeToDb 模式:统一生成一个批次号,6 个维度共用
|
||||
String batchNo = null;
|
||||
if (writeToDb) {
|
||||
batchNo = generateBatchNo(nd);
|
||||
lastBatchNo = batchNo;
|
||||
log.info("[课表冲突检查][checkAll] nd={}, 生成批次号={}", nd, batchNo);
|
||||
} else {
|
||||
lastBatchNo = null;
|
||||
}
|
||||
|
||||
// 2) 按卡片顺序串行检查
|
||||
for (int i = 0; i < summary.getCards().size(); i++) {
|
||||
TimetableConflictCardVO old = summary.getCards().get(i);
|
||||
TimetableConflictDimension dim = TimetableConflictDimension.of(old.getDimensionCode());
|
||||
if (dim == null) continue;
|
||||
TimetableConflictCardVO newCard = checkDimension(nd, dim, batchNo, writeToDb);
|
||||
summary.getCards().set(i, newCard);
|
||||
}
|
||||
|
||||
summary.recalculateTotal();
|
||||
summary.setFullyChecked(true);
|
||||
return summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastBatchNo() {
|
||||
return lastBatchNo;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 接口实现:重置
|
||||
// =====================================================================
|
||||
|
||||
@Override
|
||||
public TimetableConflictSummaryVO reset(Integer nd, boolean clearDb) {
|
||||
// 1) 清内存缓存
|
||||
Iterator<Map.Entry<String, List<TimetableConflictDetailVO>>> it
|
||||
= detailCache.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, List<TimetableConflictDetailVO>> e = it.next();
|
||||
if (e.getKey().startsWith(nd + "_")) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
lastBatchNo = null;
|
||||
log.info("[课表冲突检查][年度:{}] 已重置内存缓存", nd);
|
||||
|
||||
// 2) 可选:清落库表(方案A 硬删除,不是逻辑删除——重置意为"不留任何检查痕迹")
|
||||
if (clearDb) {
|
||||
int del = conflictResultMapper.deleteByNd(nd);
|
||||
log.info("[课表冲突检查][年度:{}] 已清空落库表记录 {} 条", nd, del);
|
||||
}
|
||||
return loadSummary(nd);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 接口实现:明细分页(支持 useDb)
|
||||
// =====================================================================
|
||||
|
||||
@Override
|
||||
public PageResult<TimetableConflictDetailVO> getDetailsPage(Integer nd, String dimensionCode,
|
||||
Integer pageNum, Integer pageSize,
|
||||
boolean useDb, String jcpch) {
|
||||
// 兜底分页参数
|
||||
if (pageNum == null || pageNum < 1) pageNum = 1;
|
||||
if (pageSize == null || pageSize < 1) pageSize = 20;
|
||||
|
||||
List<TimetableConflictDetailVO> all = useDb
|
||||
? buildListFromDb(nd, dimensionCode, jcpch)
|
||||
: buildListFromCache(nd, dimensionCode);
|
||||
|
||||
// 排序(不管缓存/DB,统一按 日期 → 节次 → 维度 排)
|
||||
all.sort(Comparator
|
||||
.comparing((TimetableConflictDetailVO v) -> v.getRq() == null ? "" : v.getRq().toString())
|
||||
.thenComparing(v -> v.getJc() == null ? 0 : v.getJc())
|
||||
.thenComparing(v -> v.getDimensionCode() == null ? "" : v.getDimensionCode()));
|
||||
|
||||
// 手动分页
|
||||
long total = all.size();
|
||||
int fromIndex = Math.min((pageNum - 1) * pageSize, (int) total);
|
||||
int toIndex = Math.min(fromIndex + pageSize, (int) total);
|
||||
List<TimetableConflictDetailVO> pageData = total == 0
|
||||
? Collections.emptyList()
|
||||
: all.subList(fromIndex, toIndex);
|
||||
return new PageResult<>(pageData, total, pageNum, pageSize);
|
||||
}
|
||||
|
||||
private List<TimetableConflictDetailVO> buildListFromCache(Integer nd, String dimensionCode) {
|
||||
List<TimetableConflictDetailVO> all = new ArrayList<>();
|
||||
for (TimetableConflictDimension dim : TimetableConflictDimension.values()) {
|
||||
if (dimensionCode != null && !"ALL".equalsIgnoreCase(dimensionCode)
|
||||
&& !dim.getCode().equalsIgnoreCase(dimensionCode)) {
|
||||
continue;
|
||||
}
|
||||
List<TimetableConflictDetailVO> part = detailCache.get(cacheKey(nd, dim.getCode()));
|
||||
if (part != null && !part.isEmpty()) all.addAll(part);
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
private List<TimetableConflictDetailVO> buildListFromDb(Integer nd, String dimensionCode,
|
||||
String jcpch) {
|
||||
// 批次号没传 → 取该年度最新批次
|
||||
String batch = jcpch;
|
||||
if (batch == null || batch.isEmpty()) {
|
||||
List<String> batches = listBatchNos(nd);
|
||||
if (batches.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
batch = batches.get(0);
|
||||
}
|
||||
// 从 DB 拉该 nd+jcpch 的所有结果行
|
||||
List<TimetableConflictResult> rows = conflictResultMapper.listByNdAndJcpch(nd, batch);
|
||||
if (rows == null || rows.isEmpty()) return Collections.emptyList();
|
||||
|
||||
// 按 dimensionCode 过滤(null/ALL=保留全部),再实体→VO
|
||||
return rows.stream()
|
||||
.filter(r -> {
|
||||
if (dimensionCode == null || "ALL".equalsIgnoreCase(dimensionCode)) return true;
|
||||
return dimensionCode.equalsIgnoreCase(r.getWdbm());
|
||||
})
|
||||
.map(this::toDetailVO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// 便捷方法:批次号列表(倒序,最新排第一)
|
||||
// =====================================================================
|
||||
|
||||
@Override
|
||||
public List<String> listBatchNos(Integer nd) {
|
||||
// 用 MP QueryWrapper:SELECT DISTINCT "检查批次号" FROM 表 WHERE "年度" = ? ORDER BY MAX("创建时间") DESC
|
||||
QueryWrapper<TimetableConflictResult> qw = new QueryWrapper<>();
|
||||
qw.select("检查批次号 AS jcpch")
|
||||
.eq("年度", nd)
|
||||
.groupBy("检查批次号")
|
||||
.orderByDesc("MAX(创建时间)");
|
||||
try {
|
||||
List<Object> objs = conflictResultMapper.selectObjs(qw);
|
||||
List<String> res = new ArrayList<>(objs.size());
|
||||
for (Object o : objs) {
|
||||
if (o != null) res.add(String.valueOf(o));
|
||||
}
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
// 表若不存在 / 列名报错 → 不影响主流程,返回空
|
||||
log.warn("[课表冲突检查] 查询批次号列表异常(nd={}): {}", nd, e.getMessage());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user