心电图接收图片解析数据并插入数据库
This commit is contained in:
parent
8586d9fea8
commit
6e2a5fe694
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user