|
|
|
|
@ -0,0 +1,231 @@
|
|
|
|
|
package com.medical.record.client;
|
|
|
|
|
|
|
|
|
|
import com.medical.record.controller.MedicalRecordController;
|
|
|
|
|
import com.medical.record.dto.*;
|
|
|
|
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
|
|
|
|
import com.fasterxml.jackson.annotation.Nulls;
|
|
|
|
|
import com.fasterxml.jackson.annotation.JsonSetter;
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
|
|
|
|
import org.apache.http.Consts;
|
|
|
|
|
import org.apache.http.HttpEntity;
|
|
|
|
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
|
|
import org.apache.http.client.methods.HttpPost;
|
|
|
|
|
import org.apache.http.entity.BufferedHttpEntity;
|
|
|
|
|
import org.apache.http.entity.ContentType;
|
|
|
|
|
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
|
|
|
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
|
|
import org.apache.http.impl.client.HttpClients;
|
|
|
|
|
import org.apache.http.util.EntityUtils;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
|
|
import java.io.*;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.nio.file.Files;
|
|
|
|
|
import java.nio.file.Path;
|
|
|
|
|
import java.nio.file.Paths;
|
|
|
|
|
import java.nio.file.StandardOpenOption;
|
|
|
|
|
import java.security.MessageDigest;
|
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.zip.ZipEntry;
|
|
|
|
|
import java.util.zip.ZipOutputStream;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 历史病案数据对接客户端(JDK 1.8 + Apache HttpClient)
|
|
|
|
|
* 请求方式:POST multipart/form-data
|
|
|
|
|
*/
|
|
|
|
|
public class MedicalRecordHttpClient {
|
|
|
|
|
|
|
|
|
|
private final ObjectMapper jsonMapper;
|
|
|
|
|
private final XmlMapper xmlMapper;
|
|
|
|
|
private final String baseUrl;
|
|
|
|
|
private final CloseableHttpClient httpClient;
|
|
|
|
|
|
|
|
|
|
// 创建SLF4J Logger实例
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(MedicalRecordHttpClient.class);
|
|
|
|
|
|
|
|
|
|
public MedicalRecordHttpClient(String baseUrl) {
|
|
|
|
|
this.baseUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
|
|
|
|
|
this.jsonMapper = new ObjectMapper();
|
|
|
|
|
|
|
|
|
|
/* ===== 空值也输出 XML 标签 ===== */
|
|
|
|
|
this.xmlMapper = XmlMapper.builder()
|
|
|
|
|
.defaultUseWrapper(false) // 保持原设置
|
|
|
|
|
.serializationInclusion(JsonInclude.Include.ALWAYS) // 1. 输出 null
|
|
|
|
|
.build();
|
|
|
|
|
// 2. 把 null 映射成空串,避免 xsi:nil
|
|
|
|
|
this.xmlMapper.configOverride(String.class)
|
|
|
|
|
.setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
|
|
|
|
|
/* ===== 空值也输出 XML 标签 ===== */
|
|
|
|
|
|
|
|
|
|
this.httpClient = HttpClients.createDefault();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 【POST multipart/form-data】获取认证SID
|
|
|
|
|
*/
|
|
|
|
|
public String getSid(String accessKey) throws IOException, MedicalRecordException {
|
|
|
|
|
String url = baseUrl + "apps/com.itmppaas.user.apps.dzblgd/medicalRecord/getSidByAccessKey";
|
|
|
|
|
log.info("url::::::::"+url);
|
|
|
|
|
HttpPost httpPost = new HttpPost(url);
|
|
|
|
|
|
|
|
|
|
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
|
|
|
|
builder.addTextBody("accessKey", accessKey, ContentType.TEXT_PLAIN);
|
|
|
|
|
|
|
|
|
|
httpPost.setEntity(builder.build());
|
|
|
|
|
|
|
|
|
|
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
|
|
|
|
String result = EntityUtils.toString(response.getEntity());
|
|
|
|
|
int statusCode = response.getStatusLine().getStatusCode();
|
|
|
|
|
|
|
|
|
|
if (statusCode != 200) {
|
|
|
|
|
throw new MedicalRecordException("认证请求失败, HTTP状态码: " + statusCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AuthResponse authResponse = jsonMapper.readValue(result, AuthResponse.class);
|
|
|
|
|
|
|
|
|
|
if (!"ok".equals(authResponse.getResult())) {
|
|
|
|
|
throw new MedicalRecordException("认证失败: " + authResponse.getMsg());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return authResponse.getData();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 【POST multipart/form-data】上传病案数据
|
|
|
|
|
*/
|
|
|
|
|
public String uploadMedicalRecord(String sid, MedicalRecordXml medicalRecordXml, Path zipFile)
|
|
|
|
|
throws IOException, MedicalRecordException {
|
|
|
|
|
|
|
|
|
|
if (!Files.exists(zipFile)) {
|
|
|
|
|
throw new IllegalArgumentException("ZIP文件不存在: " + zipFile);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String url = baseUrl + "apps/com.itmppaas.user.apps.dzblgd/medicalRecord/saveMedicalRecordHistoryData";
|
|
|
|
|
HttpPost httpPost = new HttpPost(url);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 把 XML 对象序列化成字符串 */
|
|
|
|
|
String xmlContent = xmlMapper.writeValueAsString(medicalRecordXml);
|
|
|
|
|
|
|
|
|
|
/* 保存到本地(可选,调试时打开) */
|
|
|
|
|
Path xmlPath = Paths.get("medicalRecord.xml"); // 可改成绝对路径
|
|
|
|
|
Files.write(xmlPath,
|
|
|
|
|
xmlContent.getBytes(StandardCharsets.UTF_8),
|
|
|
|
|
StandardOpenOption.CREATE,
|
|
|
|
|
StandardOpenOption.TRUNCATE_EXISTING);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//把 XML 对象序列化成字节数组,准备当“文件”传 */
|
|
|
|
|
byte[] xmlBytes = xmlMapper.writeValueAsBytes(medicalRecordXml);
|
|
|
|
|
|
|
|
|
|
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
|
|
|
|
|
builder.addTextBody("sid", sid, ContentType.TEXT_PLAIN);
|
|
|
|
|
builder.addBinaryBody("xml", xmlBytes,
|
|
|
|
|
ContentType.create("application/xml"),
|
|
|
|
|
"medicalRecord.xml");
|
|
|
|
|
builder.addBinaryBody("zip", zipFile.toFile(),
|
|
|
|
|
ContentType.create("application/zip"),
|
|
|
|
|
zipFile.getFileName().toString());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 先 build 实体
|
|
|
|
|
HttpEntity rawEntity = builder.build();
|
|
|
|
|
|
|
|
|
|
// 2. 包成可重复读实体,这样后面真正发请求时还能再用
|
|
|
|
|
BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(rawEntity);
|
|
|
|
|
|
|
|
|
|
// 3. 输出到内存缓冲区
|
|
|
|
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
|
|
|
|
bufferedEntity.writeTo(buf); // 不会把流弄空
|
|
|
|
|
String multipartTxt = buf.toString(String.valueOf(StandardCharsets.UTF_8));
|
|
|
|
|
|
|
|
|
|
// 4. 用 log 打印(slf4j 的 info 级别)
|
|
|
|
|
log.info("---------- multipart 原文本 ----------\n{}", multipartTxt);
|
|
|
|
|
|
|
|
|
|
// 5. 再放回请求,正常发
|
|
|
|
|
httpPost.setEntity(bufferedEntity);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// httpPost.setEntity(builder.build());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
|
|
|
|
String result = EntityUtils.toString(response.getEntity());
|
|
|
|
|
int statusCode = response.getStatusLine().getStatusCode();
|
|
|
|
|
|
|
|
|
|
if (statusCode != 200) {
|
|
|
|
|
throw new MedicalRecordException("上传请求失败, HTTP状态码: " + statusCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UploadResponse uploadResponse = jsonMapper.readValue(result, UploadResponse.class);
|
|
|
|
|
|
|
|
|
|
if (!"ok".equals(uploadResponse.getResult())) {
|
|
|
|
|
throw new MedicalRecordException("上传失败: " + uploadResponse.getMsg());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return uploadResponse.getData().getRecordId();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将多个图像打包成ZIP
|
|
|
|
|
*/
|
|
|
|
|
public static Path packageImagesToZip(List<Path> imagePaths, Path outputZipPath)
|
|
|
|
|
throws IOException, MedicalRecordException {
|
|
|
|
|
|
|
|
|
|
for (Path imagePath : imagePaths) {
|
|
|
|
|
if (!Files.exists(imagePath)) {
|
|
|
|
|
throw new MedicalRecordException("图像文件不存在: " + imagePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputZipPath.toFile()))) {
|
|
|
|
|
byte[] buffer = new byte[8192];
|
|
|
|
|
|
|
|
|
|
for (Path imagePath : imagePaths) {
|
|
|
|
|
ZipEntry zipEntry = new ZipEntry(imagePath.getFileName().toString());
|
|
|
|
|
zipEntry.setSize(Files.size(imagePath));
|
|
|
|
|
zos.putNextEntry(zipEntry);
|
|
|
|
|
|
|
|
|
|
try (FileInputStream fis = new FileInputStream(imagePath.toFile())) {
|
|
|
|
|
int len;
|
|
|
|
|
while ((len = fis.read(buffer)) > 0) {
|
|
|
|
|
zos.write(buffer, 0, len);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
zos.closeEntry();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return outputZipPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* MD5工具方法
|
|
|
|
|
*/
|
|
|
|
|
public static String calculateMD5(Path filePath) throws IOException {
|
|
|
|
|
try {
|
|
|
|
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
|
|
|
byte[] fileBytes = Files.readAllBytes(filePath);
|
|
|
|
|
byte[] digest = md.digest(fileBytes);
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (byte b : digest) {
|
|
|
|
|
sb.append(String.format("%02x", b));
|
|
|
|
|
}
|
|
|
|
|
return sb.toString();
|
|
|
|
|
} catch (NoSuchAlgorithmException e) {
|
|
|
|
|
throw new RuntimeException("MD5算法不可用", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|