更新了导出毒品计算Excel会因为重名报错的Bug

This commit is contained in:
杨海航
2026-09-29 15:46:05 +08:00
parent d0bbd4b023
commit e0dc3b8842
3 changed files with 302 additions and 111 deletions
@@ -72,6 +72,11 @@ import java.util.stream.Collectors;
public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<EntrustMaterialCheckoutResultMapper, EntrustMaterialCheckoutResult> public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<EntrustMaterialCheckoutResultMapper, EntrustMaterialCheckoutResult>
implements EntrustMaterialCheckoutResultService { implements EntrustMaterialCheckoutResultService {
/**
* Excel 工作表名最大长度(OOXML 规范:1~31 字符)
*/
private static final int MAX_SHEET_NAME_LENGTH = 31;
@Resource @Resource
private EntrustmentService entrustmentService; private EntrustmentService entrustmentService;
@@ -688,6 +693,9 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
*/ */
private List<String> getOriginalIdentificationList(List<String> oldResults) { private List<String> getOriginalIdentificationList(List<String> oldResults) {
List<String> allResults = new ArrayList<>(); List<String> allResults = new ArrayList<>();
if (CollUtil.isEmpty(oldResults)) {
return allResults;
}
for (String oldResult : oldResults) { for (String oldResult : oldResults) {
if (StringUtils.isNotBlank(oldResult)) { if (StringUtils.isNotBlank(oldResult)) {
if ("委托".equals(oldResult)) { if ("委托".equals(oldResult)) {
@@ -751,39 +759,34 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
return Collections.emptyMap(); return Collections.emptyMap();
} }
// 4. 提取所有涉及的毒品名称(去重) // 4. 提取所有涉及的化合物名称,并按"归一化名称"归并分组
Set<String> drugNames = new HashSet<>(); // 字典里用 "氢可酮*" 标注管制药品,业务上与 "氢可酮" 是同一化合物,这里合并为一组
// key = 归一化名称(统计表展示、工作表名都用它),value = 库里的各种原始写法(查询要用原始写法)
Map<String, Set<String>> groupedDrugNames = new LinkedHashMap<>();
for (EntrustMaterialCheckoutResult result : newResultList) { for (EntrustMaterialCheckoutResult result : newResultList) {
List<DrugLite> drugLites = DrugLiteConvert.getDrugLites(result.getQualitativeResult()); collectDrugNames(groupedDrugNames, result.getQualitativeResult());
for (DrugLite drugLite : drugLites) { if (isMetabolite) {
drugNames.add(drugLite.getName()); collectDrugNames(groupedDrugNames, result.getMetaboliteResult());
}
if (isMetabolite && StringUtils.isNotBlank(result.getMetaboliteResult())) {
List<DrugLite> drugLites1 = DrugLiteConvert.getDrugLites(result.getMetaboliteResult());
for (DrugLite drugLite : drugLites1) {
if (!drugNames.contains(drugLite.getName())) {
drugNames.add(drugLite.getName());
}
}
} }
} }
// 5. 处理不同毒品名称的数据 // 5. 处理不同毒品名称的数据
Map<String, List<EntrustmentIdentificationMaterialVO>> resultMap = new HashMap<>(); Map<String, List<EntrustmentIdentificationMaterialVO>> resultMap = new HashMap<>();
for (String drugName : drugNames) { for (Map.Entry<String, Set<String>> drugGroup : groupedDrugNames.entrySet()) {
// 5.1 查询所有含有该毒品的检验结果 String drugName = drugGroup.getKey();
Set<String> rawDrugNames = drugGroup.getValue();
// 5.1 查询所有含有该毒品的检验结果(组内每种原始写法各查一次,再按检材 id 去重合并)
Map<String, EntrustMaterialCheckoutResult> checkoutResultMap = new LinkedHashMap<>();
for (String rawDrugName : rawDrugNames) {
List<EntrustMaterialCheckoutResult> resultList = this.list(Wrappers.<EntrustMaterialCheckoutResult>lambdaQuery() List<EntrustMaterialCheckoutResult> resultList = this.list(Wrappers.<EntrustMaterialCheckoutResult>lambdaQuery()
.and(queryWrapper -> queryWrapper .and(queryWrapper -> queryWrapper
.like(EntrustMaterialCheckoutResult::getMetaboliteResult, drugName) .like(EntrustMaterialCheckoutResult::getMetaboliteResult, rawDrugName)
.or() .or()
.like(EntrustMaterialCheckoutResult::getQualitativeResult, drugName)) .like(EntrustMaterialCheckoutResult::getQualitativeResult, rawDrugName))
.in(EntrustMaterialCheckoutResult::getEntrustId, entrustIdList)); .in(EntrustMaterialCheckoutResult::getEntrustId, entrustIdList));
if (resultList.isEmpty()) {
continue;
}
if (isMetabolite) { if (isMetabolite) {
resultList.forEach(item -> { resultList.forEach(item -> {
if (StringUtils.isNotBlank(item.getMetaboliteResult())) { if (StringUtils.isNotBlank(item.getMetaboliteResult())) {
@@ -795,9 +798,15 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
}); });
} }
// 转换为 Map,方便后续查找 // 同一检材可能同时命中组内的多个原始写法,按检材 id 去重
Map<String, EntrustMaterialCheckoutResult> checkoutResultMap = resultList.stream() for (EntrustMaterialCheckoutResult item : resultList) {
.collect(Collectors.toMap(EntrustMaterialCheckoutResult::getId, Function.identity())); checkoutResultMap.putIfAbsent(item.getId(), item);
}
}
if (checkoutResultMap.isEmpty()) {
continue;
}
// 5.2 获取相关的鉴定材料 // 5.2 获取相关的鉴定材料
Set<String> materialIdSet = checkoutResultMap.keySet(); Set<String> materialIdSet = checkoutResultMap.keySet();
@@ -862,6 +871,46 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
} }
} }
/**
* 收集化合物名称并按"归一化名称"归并。
*
* @param grouped 归并结果:key = 归一化名称,value = 库里的各种原始写法
* @param drugResultJson 检出结果 JSON(qualitative_result / metabolite_result)
*/
private void collectDrugNames(Map<String, Set<String>> grouped, String drugResultJson) {
if (StringUtils.isBlank(drugResultJson)) {
return;
}
for (DrugLite drugLite : DrugLiteConvert.getDrugLites(drugResultJson)) {
String rawName = drugLite.getName();
if (StringUtils.isBlank(rawName)) {
continue;
}
grouped.computeIfAbsent(normalizeDrugName(rawName), key -> new LinkedHashSet<>()).add(rawName);
}
}
/**
* 化合物名称归一化。
* <p>
* 化合物字典里用星号标注管制药品(如 "氢可酮*",共 52 条),业务上它与不带星号的
* "氢可酮" 是同一化合物,所以导出时按下述规则归并:去掉星号 + 去首尾空白。
* 归并后同一个化合物在统计表中只占一行、在工作簿中只占一张工作表。
* <p>
* 注意:只影响"化合物归并/工作表名",明细行"检出结果"列仍按原始名称输出。
*
* @param drugName 原始化合物名称
* @return 归一化后的名称
*/
private static String normalizeDrugName(String drugName) {
if (StringUtils.isBlank(drugName)) {
return drugName;
}
String normalized = drugName.replace("*", "").replace("*", "").trim();
// 名称本身全是星号时归一化会变空,退回原始名称,避免出现空的分组键
return normalized.isEmpty() ? drugName : normalized;
}
/** /**
* 导出Excel * 导出Excel
* *
@@ -897,9 +946,17 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
Workbook workbook = new XSSFWorkbook(); Workbook workbook = new XSSFWorkbook();
// 建表阶段(工作表名、单元格填充)全部包起来:万一某条数据仍触发异常,
// 也返回可读的失败提示,而不是 500 并且把已经拼好的表一起丢掉
try {
createDataSheet(workbook, entrustType, resultData); createDataSheet(workbook, entrustType, resultData);
createStatisticsSheet(workbook, resultDataMap, oldResults, entrustType, startTime, endTime); createStatisticsSheet(workbook, resultDataMap, oldResults, entrustType, startTime, endTime);
createDrugSheets(workbook, resultDataMap, entrustType); createDrugSheets(workbook, resultDataMap, entrustType);
} catch (Exception e) {
log.error("导出检出结果Excel失败: entrustType={}, startTime={}, endTime={}",
entrustType, startTime, endTime, e);
return R.failed("导出失败:" + e.getMessage());
}
workbook.write(response.getOutputStream()); workbook.write(response.getOutputStream());
} }
@@ -908,13 +965,13 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
private void createDataSheet(Workbook workbook, Integer entrustType, List<EntrustmentIdentificationMaterialVO> resultData) { private void createDataSheet(Workbook workbook, Integer entrustType, List<EntrustmentIdentificationMaterialVO> resultData) {
// 确定表名 // 确定表名
String sheetName = (entrustType == 0) ? "常规毒品" : "生物样本"; String sheetName = isBiologySample(entrustType) ? "生物样本" : "常规毒品";
Sheet sheet = workbook.createSheet(sheetName); Sheet sheet = workbook.createSheet(safeSheetName(workbook, sheetName));
// 定义表头 // 定义表头
String[] inVivoHeaders = {"检材类型", "检材编号", "检材名称", "检材性状", "检材质量", "年龄", "性别", "委托单位", "受理日期", "检出结果"}; String[] inVivoHeaders = {"检材类型", "检材编号", "检材名称", "检材性状", "检材质量", "年龄", "性别", "委托单位", "受理日期", "检出结果"};
String[] inVitroHeaders = {"检材类型", "检材编号", "检材名称", "检材性状", "检材质量", "委托单位", "受理日期", "检出结果"}; String[] inVitroHeaders = {"检材类型", "检材编号", "检材名称", "检材性状", "检材质量", "委托单位", "受理日期", "检出结果"};
String[] headers = (entrustType == 0) ? inVitroHeaders : inVivoHeaders; String[] headers = isBiologySample(entrustType) ? inVivoHeaders : inVitroHeaders;
// 创建表头行 // 创建表头行
Row headerRow = sheet.createRow(0); Row headerRow = sheet.createRow(0);
@@ -942,7 +999,7 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
} }
private void createStatisticsSheet(Workbook workbook, Map<String, List<EntrustmentIdentificationMaterialVO>> resultDataMap, List<String> oldResults, Integer entrustType, LocalDateTime startTime, LocalDateTime endTime) { private void createStatisticsSheet(Workbook workbook, Map<String, List<EntrustmentIdentificationMaterialVO>> resultDataMap, List<String> oldResults, Integer entrustType, LocalDateTime startTime, LocalDateTime endTime) {
Sheet sheet = workbook.createSheet("统计"); Sheet sheet = workbook.createSheet(safeSheetName(workbook, "统计"));
String[] headers = {"化合物种类", "检出数量", "总数量", "检出率"}; String[] headers = {"化合物种类", "检出数量", "总数量", "检出率"};
@@ -965,19 +1022,24 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
row.createCell(0).setCellValue(drugName); row.createCell(0).setCellValue(drugName);
row.createCell(1).setCellValue(materials.size()); row.createCell(1).setCellValue(materials.size());
row.createCell(2).setCellValue(size); row.createCell(2).setCellValue(size);
row.createCell(3).setCellValue(String.format("%.2f%%", materials.size() * 100.0 / size)); row.createCell(3).setCellValue((size == null || size == 0)
? "0.00%"
: String.format("%.2f%%", materials.size() * 100.0 / size));
} }
} }
private void createDrugSheets(Workbook workbook, Map<String, List<EntrustmentIdentificationMaterialVO>> resultDataMap, Integer entrustType) { private void createDrugSheets(Workbook workbook, Map<String, List<EntrustmentIdentificationMaterialVO>> resultDataMap, Integer entrustType) {
for (Map.Entry<String, List<EntrustmentIdentificationMaterialVO>> entry : resultDataMap.entrySet()) { for (Map.Entry<String, List<EntrustmentIdentificationMaterialVO>> entry : resultDataMap.entrySet()) {
// String drugName = entry.getKey().replaceAll("[^\u4e00-\u9fa5a-zA-Z0-9]", ""); // 工作表名必须净化:化合物字典里存在 "氢可酮*" 这类带 * 的名称(共 52 条)以及含 [ ] 的
String drugName = entry.getKey(); // IUPAC 名称,直接建表会抛 IllegalArgumentException 导致整份报表导出失败。
Sheet sheet = workbook.createSheet(drugName); // 注意只清洗"工作表名",单元格里的检出结果仍保留原始名称。
String sheetName = safeSheetName(workbook, entry.getKey());
Sheet sheet = workbook.createSheet(sheetName);
// 定义表头 // 定义表头
String[] headers = (entrustType == 0) ? new String[]{"检材类型", "检材编号", "检材名称", "检材性状", "检材质量", "委托单位", "受理日期", "检出结果"} String[] headers = isBiologySample(entrustType)
: new String[]{"检材类型", "检材编号", "检材名称", "检材性状", "检材质量", "年龄", "性别", "委托单位", "受理日期", "检出结果"}; ? new String[]{"检材类型", "检材编号", "检材名称", "检材性状", "检材质量", "年龄", "性别", "委托单位", "受理日期", "检出结果"}
: new String[]{"检材类型", "检材编号", "检材名称", "检材性状", "检材质量", "委托单位", "受理日期", "检出结果"};
// 创建表头行 // 创建表头行
Row headerRow = sheet.createRow(0); Row headerRow = sheet.createRow(0);
@@ -995,6 +1057,50 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
} }
} }
/**
* 委托类型是否为"生物样本"
*
* @param entrustType 0=常规毒品, 1=生物样本
* @return true=生物样本,false(含 entrustType 为空)=常规毒品
*/
private static boolean isBiologySample(Integer entrustType) {
return entrustType != null && entrustType == 1;
}
/**
* 生成合法且唯一的 Excel 工作表名。
* <p>
* Excel/OOXML 规范要求工作表名:不能包含 {@code : \ / ? * [ ]} 这 7 个字符、长度 1~31、不能为空、不能重名
* (POI 判重不区分大小写,校验不通过会抛 IllegalArgumentException)。
* 化合物字典里本身就有带 * 的名称(如 "氢可酮*",共 52 条)和含 [ ] 的 IUPAC 名称,
* 所以这里统一做"过滤非法字符 -&gt; 截断 31 字符 -&gt; 空名兜底 -&gt; 重名加后缀(2)(3)…"。
*
* @param workbook 目标工作簿
* @param rawName 原始名称(化合物名称)
* @return 可直接用于 createSheet 的合法且唯一的工作表名
*/
private String safeSheetName(Workbook workbook, String rawName) {
String base = StrUtil.isBlank(rawName)
? ""
: rawName.replaceAll("[\\[\\]:*?/\\\\\\x00-\\x1f\\x7f]", "").trim();
if (base.isEmpty()) {
base = "未命名";
}
if (base.length() > MAX_SHEET_NAME_LENGTH) {
base = base.substring(0, MAX_SHEET_NAME_LENGTH);
}
String name = base;
int index = 2;
while (workbook.getSheet(name) != null) {
String suffix = "(" + index++ + ")";
String cut = base.length() + suffix.length() > MAX_SHEET_NAME_LENGTH
? base.substring(0, MAX_SHEET_NAME_LENGTH - suffix.length())
: base;
name = cut + suffix;
}
return name;
}
private void fillRowData(Row row, EntrustmentIdentificationMaterialVO vo, Integer entrustType) { private void fillRowData(Row row, EntrustmentIdentificationMaterialVO vo, Integer entrustType) {
row.createCell(0).setCellValue(vo.getOldIdentificationResult()); row.createCell(0).setCellValue(vo.getOldIdentificationResult());
row.createCell(1).setCellValue(vo.getAcceptNo()); row.createCell(1).setCellValue(vo.getAcceptNo());
@@ -1002,8 +1108,9 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
row.createCell(3).setCellValue(vo.getFormName()); row.createCell(3).setCellValue(vo.getFormName());
row.createCell(4).setCellValue(vo.getQuantity() + vo.getUnit()); row.createCell(4).setCellValue(vo.getQuantity() + vo.getUnit());
if (entrustType == 1) { if (isBiologySample(entrustType)) {
row.createCell(5).setCellValue(vo.getMaterialAge() != null || vo.getMaterialAge() != 0 ? vo.getMaterialAge().toString() + "岁" : "未知"); Integer materialAge = vo.getMaterialAge();
row.createCell(5).setCellValue(materialAge != null && materialAge != 0 ? materialAge + "岁" : "未知");
row.createCell(6).setCellValue(StringUtils.isNotBlank(vo.getBiologyGender()) ? vo.getBiologyGender() : "未知"); row.createCell(6).setCellValue(StringUtils.isNotBlank(vo.getBiologyGender()) ? vo.getBiologyGender() : "未知");
row.createCell(7).setCellValue(vo.getOrgName()); row.createCell(7).setCellValue(vo.getOrgName());
row.createCell(8).setCellValue(LocalDateTimeUtil.format(vo.getAcceptTime(), "yyyy-MM-dd")); row.createCell(8).setCellValue(LocalDateTimeUtil.format(vo.getAcceptTime(), "yyyy-MM-dd"));
@@ -1052,23 +1159,24 @@ public class EntrustMaterialCheckoutResultServiceImpl extends ServiceImpl<Entrus
wrapper.ge(Entrustment::getAcceptTime, queryStartTime); wrapper.ge(Entrustment::getAcceptTime, queryStartTime);
} }
ArrayList<String> results = new ArrayList<>(); // 鉴定情况过滤条件("委托" 等价于首次/补充/重新鉴定)
for (String oldResult : oldResults) { List<String> results = getOriginalIdentificationList(oldResults);
if (oldResult.equals("委托")) {
results.add("首次鉴定");
results.add("补充鉴定");
results.add("重新鉴定");
} else results.add(oldResult);
}
// 查询符合条件的 Entrustment 记录 // 查询符合条件的 Entrustment 记录
List<Entrustment> entrusts = entrustmentService.list(wrapper LambdaQueryWrapper<Entrustment> entrustWrapper = wrapper
.eq(Entrustment::getEntrustmentType, entrustType) .eq(Entrustment::getEntrustmentType, entrustType)
.ge(Entrustment::getStatus, EntrustStatusConstants.ENTRUST_STATUS_ACCEPTED.getStatus()) .ge(Entrustment::getStatus, EntrustStatusConstants.ENTRUST_STATUS_ACCEPTED.getStatus());
.in(Entrustment::getOldIdentificationResult, results) // 注意:鉴定情况为空时不能拼 in 条件,否则 MyBatis-Plus 会生成 "IN ()" 造成 SQL 语法错误
); if (CollUtil.isNotEmpty(results)) {
entrustWrapper.in(Entrustment::getOldIdentificationResult, results);
}
List<Entrustment> entrusts = entrustmentService.list(entrustWrapper);
List<String> entrustIds = entrusts.stream().map(Entrustment::getId).collect(Collectors.toList()); List<String> entrustIds = entrusts.stream().map(Entrustment::getId).collect(Collectors.toList());
// 没有委托时直接返回 0,同样避免 "IN ()"
if (CollUtil.isEmpty(entrustIds)) {
return 0;
}
return entrustmentIdentificationMaterialService.lambdaQuery() return entrustmentIdentificationMaterialService.lambdaQuery()
.in(EntrustmentIdentificationMaterial::getEntrustmentId, entrustIds) .in(EntrustmentIdentificationMaterial::getEntrustmentId, entrustIds)
@@ -1661,15 +1661,43 @@ public class EntrustmentIdentificationMaterialServiceImpl extends ServiceImpl<En
@Override @Override
public List<EntrustmentIdentificationMaterialVO> sortVoByAcceptNo(List<EntrustmentIdentificationMaterialVO> materials) { public List<EntrustmentIdentificationMaterialVO> sortVoByAcceptNo(List<EntrustmentIdentificationMaterialVO> materials) {
materials.sort(Comparator.comparing((EntrustmentIdentificationMaterial m) -> { if (materials == null || materials.isEmpty()) {
String[] parts = m.getAcceptNo().split("-"); return materials;
return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2])}; }
}, Comparator.comparingInt((int[] arr) -> arr[0]) // 按年份排序 materials.sort(Comparator.comparing((EntrustmentIdentificationMaterial m) -> parseAcceptNoSortKey(m.getAcceptNo()),
Comparator.comparingInt((int[] arr) -> arr[0]) // 按年份排序
.thenComparingInt(arr -> arr[1]) // 按流水号排序 .thenComparingInt(arr -> arr[1]) // 按流水号排序
.thenComparingInt(arr -> arr[2]))); // 按子流水号排序 .thenComparingInt(arr -> arr[2]))); // 按子流水号排序
return materials; return materials;
} }
/**
* 把受理编号(形如 "2025-3-7")解析成排序键 {年, 流水号, 子流水号}。
* <p>
* 编号为空或格式不符合预期时返回最大值排到最后,避免排序过程中抛
* NullPointerException / NumberFormatException 导致接口报错。
*
* @param acceptNo 受理编号
* @return 排序键
*/
private static int[] parseAcceptNoSortKey(String acceptNo) {
if (StrUtil.isNotBlank(acceptNo)) {
String[] parts = acceptNo.trim().split("-");
if (parts.length == 3) {
try {
return new int[]{
Integer.parseInt(parts[0].trim()),
Integer.parseInt(parts[1].trim()),
Integer.parseInt(parts[2].trim())
};
} catch (NumberFormatException ignored) {
// 走下面的兜底分支
}
}
}
return new int[]{Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
}
@Override @Override
/** /**
* 根据嫌疑人信息生成委托检材信息 * 根据嫌疑人信息生成委托检材信息
@@ -362,26 +362,68 @@ public class ExcelOperationServiceImpl implements ExcelOperationService {
*/ */
private List<EntrustmentIdentificationMaterial> processHairExcelDataToMaterialEntity(List<Map<String, String>> data, Entrustment entrustment, Map<String, DrugLite> drugLiteMap, int orderNo, CaseEvent cj) { private List<EntrustmentIdentificationMaterial> processHairExcelDataToMaterialEntity(List<Map<String, String>> data, Entrustment entrustment, Map<String, DrugLite> drugLiteMap, int orderNo, CaseEvent cj) {
List<EntrustmentIdentificationMaterial> entrustmentIdentificationMaterialList = new ArrayList<>(); List<EntrustmentIdentificationMaterial> entrustmentIdentificationMaterialList = new ArrayList<>();
for (Map<String, String> datum : data) { List<String> errorMessages = new ArrayList<>();
// 构建检材对象 for (int i = 0; i < data.size(); i++) {
Map<String, String> datum = data.get(i);
int rowNum = i + 2; // Excel行号(数据从第2行开始)
String idNum = null;
String name = null;
try {
name = datum.get("姓名");
// ---------- 1. 姓名必填校验 ----------
if (StrUtil.isBlank(name)) {
throw new IllegalArgumentException("姓名不能为空");
}
// ---------- 2. 身份证号校验 ----------
idNum = datum.get("身份证号");
if (StrUtil.isBlank(idNum)) {
throw new IllegalArgumentException("身份证号不能为空");
}
if (idNum.length() != 18) {
throw new IllegalArgumentException(String.format(
"身份证号[%s]长度不正确(当前%d位,应为18位),请检查身份证号是否完整",
idNum, idNum.length()));
}
// 性别解析
String gender = this.getSexByIdNum(idNum);
if (StrUtil.isBlank(gender)) {
throw new IllegalArgumentException(String.format(
"身份证号[%s]性别解析失败,请检查第17位是否为数字0~9", idNum));
}
// 年龄解析
String ageStr = this.getAgeByIdNum(idNum);
if (StrUtil.isBlank(ageStr)) {
throw new IllegalArgumentException(String.format(
"身份证号[%s]出生日期解析失败,请检查第7~14位是否为合法日期(yyyyMMdd格式),如19900520",
idNum));
}
// ---------- 3. 检测结果必填校验 ----------
String testResult = datum.get("检测结果");
if (StrUtil.isBlank(testResult)) {
throw new IllegalArgumentException("检测结果不能为空");
}
// ---------- 4. 采样单位校验 ----------
String drawPlace = datum.get("采样单位");
if (StrUtil.isBlank(drawPlace)) {
throw new IllegalArgumentException("采样单位不能为空");
}
// ---------- 构建检材对象 ----------
EntrustmentIdentificationMaterial material = new EntrustmentIdentificationMaterial(); EntrustmentIdentificationMaterial material = new EntrustmentIdentificationMaterial();
material.setEntrustmentId(entrustment.getId()); material.setEntrustmentId(entrustment.getId());
material.setCaseId(entrustment.getCaseId()); material.setCaseId(entrustment.getCaseId());
material.setName(datum.get("姓名") + "毛发"); material.setName(name + "毛发");
material.setRemark(datum.get("身份证号")); material.setRemark(idNum);
material.setBiologyGender(gender);
// 解析身份证号 material.setMaterialAge(Integer.valueOf(ageStr));
String idNum = datum.get("身份证号"); material.setDrawPlace(drawPlace);
if (idNum != null && idNum.length() == 18) {
material.setBiologyGender(this.getSexByIdNum(idNum));
material.setMaterialAge(Integer.valueOf(this.getAgeByIdNum(idNum)));
}
material.setDrawPlace(datum.get("采样单位"));
material.setId(IdWorker.get32UUID().toUpperCase()); material.setId(IdWorker.get32UUID().toUpperCase());
// 检测结果解析 // 检测结果解析
String[] drugs = datum.get("检测结果").split("、"); String[] drugs = testResult.split("、");
buildMatchedDrugs(drugLiteMap, material, drugs); buildMatchedDrugs(drugLiteMap, material, drugs);
// 设置检材默认属性 // 设置检材默认属性
@@ -404,6 +446,17 @@ public class ExcelOperationServiceImpl implements ExcelOperationService {
material.setSample1No(sampleService.getNewSampleNo(material.getImNo(), 1)); material.setSample1No(sampleService.getNewSampleNo(material.getImNo(), 1));
entrustmentIdentificationMaterialList.add(material); entrustmentIdentificationMaterialList.add(material);
} catch (Exception e) {
String idInfo = StrUtil.isNotBlank(idNum) ? (" 证件号[" + idNum + "]") : "";
String nameInfo = StrUtil.isNotBlank(name) ? name : "未知";
errorMessages.add(String.format("第%d行 姓名[%s]%s: %s", rowNum, nameInfo, idInfo, e.getMessage()));
}
}
// 一次性汇总所有错误返回给用户
if (!errorMessages.isEmpty()) {
throw new CheckedException("Excel导入校验失败,共" + errorMessages.size() + "处错误:\n" + String.join("\n", errorMessages));
} }
return entrustmentIdentificationMaterialList; return entrustmentIdentificationMaterialList;
} }
@@ -708,7 +761,7 @@ public class ExcelOperationServiceImpl implements ExcelOperationService {
int genderCode = Integer.parseInt(idNumber.substring(16, 17)); int genderCode = Integer.parseInt(idNumber.substring(16, 17));
return genderCode % 2 == 0 ? "女" : "男"; return genderCode % 2 == 0 ? "女" : "男";
} catch (Exception e) { } catch (Exception e) {
log.warn("身份证号[{}] 性别解析失败: {}", idNumber, e.getMessage());
} }
} }
return ""; return "";
@@ -726,6 +779,8 @@ public class ExcelOperationServiceImpl implements ExcelOperationService {
return String.valueOf(age); return String.valueOf(age);
} catch (Exception e) { } catch (Exception e) {
log.warn("身份证号[{}] 年龄解析失败(出生日期[{}-{}]): {}", idNumber,
idNumber.substring(6, 10), idNumber.substring(10, 14), e.getMessage());
} }
} }
return ""; return "";