心电图接收图片解析数据并插入数据库

This commit is contained in:
yy2205 2025-05-08 16:33:33 +08:00
parent 8586d9fea8
commit 6e2a5fe694
3 changed files with 384 additions and 1 deletions

View File

@ -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<Boolean> parsePhotoCreateData(@RequestParam("file") MultipartFile file) {
try {
// 验证文件不为空
if (file == null || file.isEmpty()) {
return error(400, "请选择要上传的JSON文件");
}
// 调用service进行文件解析
List<EcganalysisparasSaveReqVO> dataList = ecganalysisparasService.parseEcgDataFromJsonFile(file);
// 将数据转换为DO对象
// List<EcganalysisparasDO> 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());
}
}
}

View File

@ -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<EcganalysisparasDO> {
* @return Map<String, Object>类型的结果集
*/
Map<String, Object> saveECGPdf_img(JSONObject params);
/**
* 解析照片创建数据
*
* @param list 心电分析数据列表
* @param clazz 目标类的Class对象
* @param <T> 目标类型的泛型参数
* @return 转换后的目标类型列表
*/
<T> List<T> parsePhotoCreateData(List<EcganalysisparasSaveReqVO> list, Class<T> clazz);
/**
* 从JSON文件解析数据
*
* @param file 上传的JSON文件
* @return 解析后的心电分析数据列表
* @throws IOException 如果文件读取失败
*/
List<EcganalysisparasSaveReqVO> parseEcgDataFromJsonFile(MultipartFile file) throws IOException;
/**
* 将解析后的数据插入数据库
* @param list
*/
void createEcgFromPhotoJson(List<EcganalysisparasSaveReqVO> list);
}

View File

@ -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<EcganalysisparasMap
}
return mapResult;
}
@Override
public <T> List<T> parsePhotoCreateData(List<EcganalysisparasSaveReqVO> list, Class<T> clazz) {
return BeanUtils.toBean(list, clazz);
}
@Override
public List<EcganalysisparasSaveReqVO> 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<JSONObject>
List<JSONObject> jsonList = JSON.parseArray(content, JSONObject.class);
List<EcganalysisparasSaveReqVO> 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) {
// 定义字段映射关系 - <JSON字段名, 处理方式>
Map<String, FieldProcessor> 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<String, FieldProcessor> 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<EcganalysisparasSaveReqVO> list) {
// 存储格式不正确的记录
List<EcganalysisparasSaveReqVO> invalidRecords = new ArrayList<>();
// 遍历列表检查数字字段的有效性
Iterator<EcganalysisparasSaveReqVO> 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<EcganalysisparasDO> ecgDO = parsePhotoCreateData(list, EcganalysisparasDO.class);
// List<PatientexamlistDO> patientDO = parsePhotoCreateData(list, PatientexamlistDO.class);
List<PatientexamlistDO> 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);
}
}