diff --git a/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/controller/admin/ecganalysisparas/EcganalysisparasController.java b/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/controller/admin/ecganalysisparas/EcganalysisparasController.java index 96e500990..3878ce150 100644 --- a/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/controller/admin/ecganalysisparas/EcganalysisparasController.java +++ b/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/controller/admin/ecganalysisparas/EcganalysisparasController.java @@ -54,6 +54,7 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.net.URLEncoder; +import org.springframework.web.multipart.MultipartFile; @Tag(name = "管理后台 - 心电分析数据") @RestController @@ -117,7 +118,7 @@ public class EcganalysisparasController { ecgidCardDataVO.setHR(ecganalysisparasDO.getHr()); ecgidCardDataVO.setDiagTime(ecganalysisparasDO.getDoctorDiagTime()); String diagResult = ecganalysisparasDO.getDoctorDiagResult(); - // 判断是否包含“心率不齐” + // 判断是否包含"心率不齐" int heartRateUneven = diagResult.contains("心率不齐") ? 1 : 0; ecgidCardDataVO.setHeartRateUneven(heartRateUneven); list1.add(ecgidCardDataVO); @@ -334,4 +335,33 @@ public class EcganalysisparasController { // 导出Excel ExcelUtils.write(response, "医生工作量统计.xls", "数据", EcgWorkloadVO.class, workloadList); } + + @PostMapping("/parsePhotoCreateData") + @Operation(summary = "从JSON文件解析心电数据") + public CommonResult parsePhotoCreateData(@RequestParam("file") MultipartFile file) { + try { + // 验证文件不为空 + if (file == null || file.isEmpty()) { + return error(400, "请选择要上传的JSON文件"); + } + + // 调用service进行文件解析 + List dataList = ecganalysisparasService.parseEcgDataFromJsonFile(file); + + // 将数据转换为DO对象 + // List resultList = ecganalysisparasService.parsePhotoCreateData(dataList, EcganalysisparasDO.class); + ecganalysisparasService.createEcgFromPhotoJson(dataList); + + return success(true); + } catch (IllegalArgumentException e) { + // 参数错误异常 + return error(400, e.getMessage()); + } catch (IOException e) { + // 文件读取错误 + return error(500, "文件读取失败: " + e.getMessage()); + } catch (Exception e) { + // 其他未知错误 + return error(500, "处理失败: " + e.getMessage()); + } + } } \ No newline at end of file diff --git a/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/service/ecganalysisparas/EcganalysisparasService.java b/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/service/ecganalysisparas/EcganalysisparasService.java index 64d7c8673..47339569f 100644 --- a/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/service/ecganalysisparas/EcganalysisparasService.java +++ b/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/service/ecganalysisparas/EcganalysisparasService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.tblist.service.ecganalysisparas; import java.util.*; +import java.io.IOException; import cn.iocoder.yudao.module.tblist.controller.admin.ecganalysisparas.vo.*; import cn.iocoder.yudao.module.tblist.dal.dataobject.ecganalysisparas.EcganalysisparasDO; @@ -9,6 +10,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.module.tblist.dal.dataobject.patientexamlist.PatientexamlistDO; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; @@ -93,4 +95,30 @@ public interface EcganalysisparasService extends IService { * @return Map类型的结果集 */ Map saveECGPdf_img(JSONObject params); + + /** + * 解析照片创建数据 + * + * @param list 心电分析数据列表 + * @param clazz 目标类的Class对象 + * @param 目标类型的泛型参数 + * @return 转换后的目标类型列表 + */ + List parsePhotoCreateData(List list, Class clazz); + + /** + * 从JSON文件解析数据 + * + * @param file 上传的JSON文件 + * @return 解析后的心电分析数据列表 + * @throws IOException 如果文件读取失败 + */ + List parseEcgDataFromJsonFile(MultipartFile file) throws IOException; + + /** + * 将解析后的数据插入数据库 + * @param list + */ + void createEcgFromPhotoJson(List list); + } \ No newline at end of file diff --git a/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/service/ecganalysisparas/EcganalysisparasServiceImpl.java b/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/service/ecganalysisparas/EcganalysisparasServiceImpl.java index 54066272a..1a21997a1 100644 --- a/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/service/ecganalysisparas/EcganalysisparasServiceImpl.java +++ b/yudao-module-tblist/yudao-module-tblist-biz/src/main/java/cn/iocoder/yudao/module/tblist/service/ecganalysisparas/EcganalysisparasServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.tblist.service.ecganalysisparas; +import cn.hutool.core.lang.UUID; import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.module.infra.dal.dataobject.config.ConfigDO; import cn.iocoder.yudao.module.infra.service.config.ConfigService; @@ -12,6 +13,7 @@ import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.tblist.dal.dataobject.patientexamlist.PatientexamlistDO; import cn.iocoder.yudao.module.tblist.dal.mysql.patientexamlist.PatientexamlistMapper; import cn.iocoder.yudao.module.tblist.service.positivestatistics.PositivestatisticsService; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; @@ -21,12 +23,17 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; import cn.iocoder.yudao.module.tblist.controller.admin.ecganalysisparas.vo.*; import cn.iocoder.yudao.module.tblist.dal.dataobject.ecganalysisparas.EcganalysisparasDO; @@ -240,4 +247,322 @@ public class EcganalysisparasServiceImpl extends ServiceImpl List parsePhotoCreateData(List list, Class clazz) { + + return BeanUtils.toBean(list, clazz); + } + @Override + public List parseEcgDataFromJsonFile(MultipartFile file) throws IOException { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("文件不能为空"); + } + + // 校验文件类型 + String contentType = file.getContentType(); + String originalFilename = file.getOriginalFilename(); + if (contentType == null || !contentType.contains("json") && + (originalFilename == null || !originalFilename.toLowerCase().endsWith(".json"))) { + throw new IllegalArgumentException("只支持JSON文件格式"); + } + + // 读取文件内容 + String content = new String(file.getBytes(), StandardCharsets.UTF_8); + + try { + // 解析为List + List jsonList = JSON.parseArray(content, JSONObject.class); + List resultList = new ArrayList<>(); + + for (JSONObject json : jsonList) { + // 获取extracted_data字段 + JSONObject extractedData = json.getJSONObject("extracted_data"); + if (extractedData != null) { + EcganalysisparasSaveReqVO vo = new EcganalysisparasSaveReqVO(); + + // 处理简单字段映射 + processSimpleFields(extractedData, vo); + + // 处理复合字段 + processCompositeFields(extractedData, vo); + + // 添加当前时间为处理时间 + vo.setCreateDate(LocalDateTime.now()); + resultList.add(vo); + } + } + + return resultList; + } catch (Exception e) { + throw new IllegalArgumentException("JSON文件格式不正确或不匹配要求的数据结构: " + e.getMessage()); + } + } + + /** + * 处理简单字段 + */ + private void processSimpleFields(JSONObject extractedData, EcganalysisparasSaveReqVO vo) { + // 定义字段映射关系 - + Map fieldMappings = new HashMap<>(); + + // 添加简单字段的映射 + fieldMappings.put("examId", (value) -> vo.setExamId(cleanValue(value, false))); + fieldMappings.put("name", (value) -> vo.setName(cleanValue(value, false))); + fieldMappings.put("gender", (value) -> vo.setGender(cleanValue(value, false))); + fieldMappings.put("hr", (value) -> vo.setHr(cleanValue(value, true))); + fieldMappings.put("pr", (value) -> vo.setPr(cleanValue(value, true))); + fieldMappings.put("department", (value) -> vo.setDepartName(cleanValue(value, false))); + fieldMappings.put("rv5Sv1", (value) -> vo.setRv5Sv1(cleanValue(value, true))); + fieldMappings.put("qrsTimeLimit", (value) -> vo.setQrsTimeLimit(cleanValue(value, true))); + + // 处理所有简单字段 + for (Map.Entry entry : fieldMappings.entrySet()) { + String fieldName = entry.getKey(); + if (extractedData.containsKey(fieldName)) { + String value = extractedData.getString(fieldName); + if (value != null) { + entry.getValue().process(value); + } + } + } + } + + /** + * 处理复合字段(含有"/"的字段) + */ + private void processCompositeFields(JSONObject extractedData, EcganalysisparasSaveReqVO vo) { + // 处理P/QRS/T或类似格式字段 + processAxisField(extractedData, vo); + + // 处理QT/QTC字段 + processQTField(extractedData, vo); + + // 处理RV5/SV1字段 + processRV5SV1Field(extractedData, vo); + } + + /** + * 处理轴相关的复合字段 + */ + private void processAxisField(JSONObject extractedData, EcganalysisparasSaveReqVO vo) { + for (String key : extractedData.keySet()) { + if (key.contains("/") && + containsIgnoreCase(key, "p") && + containsIgnoreCase(key, "qrs") && + containsIgnoreCase(key, "t")) { + + String value = extractedData.getString(key); + if (value != null) { + String cleaned = cleanCompositeValue(value); + String[] parts = cleaned.split("/"); + if (parts.length >= 3) { + vo.setPAxle(parts[0]); + vo.setQrsAxle(parts[1]); + vo.setTAxle(parts[2]); + } + } + break; + } + } + } + + /** + * 处理QT/QTC复合字段 + */ + private void processQTField(JSONObject extractedData, EcganalysisparasSaveReqVO vo) { + for (String key : extractedData.keySet()) { + if (key.contains("/") && + containsIgnoreCase(key, "qt") && + (containsIgnoreCase(key, "qtc") || containsIgnoreCase(key, "qtc"))) { + + String value = extractedData.getString(key); + if (value != null) { + String cleaned = cleanCompositeValue(value); + String[] parts = cleaned.split("/"); + if (parts.length >= 2) { + vo.setQt(parts[0]); + vo.setQtc(parts[1]); + } + } + break; + } + } + } + + /** + * 处理RV5/SV1复合字段 + */ + private void processRV5SV1Field(JSONObject extractedData, EcganalysisparasSaveReqVO vo) { + for (String key : extractedData.keySet()) { + if (key.contains("/") && + containsIgnoreCase(key, "rv5") && + containsIgnoreCase(key, "sv1")) { + + String value = extractedData.getString(key); + if (value != null) { + String cleaned = cleanCompositeValue(value); + String[] parts = cleaned.split("/"); + if (parts.length >= 2) { + vo.setRv5(parts[0]); + vo.setSv1(parts[1]); + } + } + break; + } + } + } + + /** + * 清理单值字段数据 + * @param value 原始值 + * @param numbersOnly 是否只保留数字 + * @return 清理后的值 + */ + private String cleanValue(String value, boolean numbersOnly) { + if (value == null) return null; + + // 移除所有空白字符 + String result = value.replaceAll("\\s+", ""); + + // 如果只需要数字,则过滤掉非数字字符(保留小数点和负号) + if (numbersOnly) { + result = result.replaceAll("[^0-9.-]", ""); + } + + return result; + } + + /** + * 清理复合字段值,保留数字、小数点、斜杠和负号 + */ + private String cleanCompositeValue(String value) { + if (value == null) return ""; + return value.replaceAll("[^0-9./-]", "").replaceAll("\\s+", ""); + } + + /** + * 检查字符串是否包含另一个字符串(忽略大小写) + */ + private boolean containsIgnoreCase(String source, String part) { + return source.toLowerCase().contains(part.toLowerCase()); + } + + /** + * 字段处理器接口 + */ + @FunctionalInterface + private interface FieldProcessor { + void process(String value); + } + @Override + @Transactional + public void createEcgFromPhotoJson(List list) { + // 存储格式不正确的记录 + List invalidRecords = new ArrayList<>(); + + // 遍历列表检查数字字段的有效性 + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + EcganalysisparasSaveReqVO item = iterator.next(); + if (!isValidNumberFormat(item) && (item.getExamId() == null || item.getExamId().equals(""))) { + invalidRecords.add(item); + iterator.remove(); // 从原始列表中移除无效记录 + } + + } + + // 如果所有记录都无效,则不进行后续处理 + if (list.isEmpty()) { + // 可以记录日志或抛出异常 + return; + } + list.forEach(e -> { + e.setRegId(UUID.randomUUID().toString()); + e.setCreateDate(LocalDateTime.now()); + }); + // 处理有效记录 + List ecgDO = parsePhotoCreateData(list, EcganalysisparasDO.class); + + // List patientDO = parsePhotoCreateData(list, PatientexamlistDO.class); + List patientDOList = list.stream().map(ecg -> { + PatientexamlistDO patient = new PatientexamlistDO(); + + // 设置基本属性 + patient.setRegId(ecg.getRegId()); + patient.setExamId(ecg.getExamId()); + patient.setPName(ecg.getName()); + patient.setGender(ecg.getGender()); + + + // 设置额外属性 + patient.setCreateDate(ecg.getCreateDate()); + patient.setDeviceType("ECG"); // 设置检查类型 + + return patient; + }) + .collect(Collectors.toList()); + + // return; + ecganalysisparasMapper.insertBatch(ecgDO); + patientexamlistMapper.insertBatch(patientDOList); + + // 这里可以对invalidRecords进行日志记录或其他处理 + } + + /** + * 检查EcganalysisparasSaveReqVO对象中的数字字段是否有效 + * + * @param item 要检查的对象 + * @return 如果所有数字字段格式都有效,则返回true;否则返回false + */ + private boolean isValidNumberFormat(EcganalysisparasSaveReqVO item) { + // 检查数字格式的字段列表 + if (!isValidNumber(item.getHr()) || + !isValidNumber(item.getPr()) || + !isValidNumber(item.getQrsTimeLimit()) || + !isValidNumber(item.getQt()) || + !isValidNumber(item.getQtc()) || + !isValidNumber(item.getPAxle()) || + !isValidNumber(item.getQrsAxle()) || + !isValidNumber(item.getTAxle()) || + !isValidNumber(item.getRv5()) || + !isValidNumber(item.getSv1()) || + !isValidNumber(item.getRv5Sv1())) { + return false; + } + return true; + } + + /** + * 判断字符串是否是有效的数字格式 + * + * @param str 要检查的字符串 + * @return 如果是有效的数字格式,则返回true;否则返回false + */ + private boolean isValidNumber(String str) { + // 空字符串视为有效(允许未设置的字段) + if (str == null || str.isEmpty()) { + return true; + } + + // 检查是否是有效的数字格式 + // 1. 不允许多个小数点 + if (str.chars().filter(ch -> ch == '.').count() > 1) { + return false; + } + + // 2. 不允许以"0"开头后面跟着其他数字(除非是小数) + if (str.length() > 1 && str.charAt(0) == '0' && str.charAt(1) != '.') { + return false; + } + + // 3. 不允许以小数点结尾 + if (str.endsWith(".")) { + return false; + } + + // 4. 检查是否只包含数字、小数点和负号 + String regex = "^-?\\d+(\\.\\d+)?$"; + return str.matches(regex); + } } \ No newline at end of file