省口腔同步需求

master
linjj 3 months ago
commit 7401fe1bee

33
.gitignore vendored

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.medical</groupId>
<artifactId>record-system</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.5.3.2</mybatis-plus.version>
<httpclient.version>4.5.14</httpclient.version>
</properties>
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.7.0</version>
</dependency>
<!-- SQL Server驱动JDK 1.8版本) -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>9.4.1.jre8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version> <!-- Spring Boot 2.7.x 默认版本 -->
</dependency>
<!-- MyBatis-Plus兼容JDK 1.8 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Apache HttpClient替代Java 11的HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.14</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Jackson XML -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,13 @@
package com.medical.record;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.medical.record.mapper") // 扫描Mapper接口
public class RecordSystemApplication {
public static void main(String[] args) {
SpringApplication.run(RecordSystemApplication.class, args);
}
}

@ -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-dataSID
*/
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);
}
}
}

@ -0,0 +1,19 @@
package com.medical.record.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("同步接口")
.version("1.0")
.description("同步接口系统接口文档"));
}
}

@ -0,0 +1,125 @@
package com.medical.record.controller;
import com.medical.record.client.MedicalRecordHttpClient;
import com.medical.record.dto.MedicalAttachmentFile;
import com.medical.record.dto.MedicalRecordData;
import com.medical.record.dto.MedicalRecordException;
import com.medical.record.dto.MedicalRecordXml;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/api/record")
@Tag(name = "同步接口接口")
public class MedicalRecordController {
// 创建SLF4J Logger实例
private static final Logger log = LoggerFactory.getLogger(MedicalRecordController.class);
/**
*
*
*/
@PostMapping("/sync")
@Operation(summary = "获取影像记录", description = "获取影像记录")
public String syncMedicalRecord(@RequestParam String accessKey) {
Path zipFile = null;
try {
// 1. 初始化HTTP客户端
MedicalRecordHttpClient httpClient = new MedicalRecordHttpClient("http://172.16.1.133/emras");
// 2. 获取SID
String sid = httpClient.getSid(accessKey);
log.info("sid:::::::::::::::::"+sid);
// 3. 准备图像文件(实际业务中动态获取)
List<Path> imagePaths = new ArrayList<>();
imagePaths.add(Paths.get("D:/test/image1.jpg"));
imagePaths.add(Paths.get("D:/test/image2.jpg"));
// 4. 打包成ZIP
zipFile = Paths.get("D:/temp/upload_" + System.currentTimeMillis() + ".zip");
MedicalRecordHttpClient.packageImagesToZip(imagePaths, zipFile);
// 5. 构建附件信息
List<MedicalAttachmentFile> fileList = new ArrayList<>();
for (Path img : imagePaths) {
MedicalAttachmentFile file = new MedicalAttachmentFile();
file.setFileName(img.getFileName().toString());
file.setFileSize(Files.size(img));
file.setFileSuffix(getExtension(img));
file.setMd5(MedicalRecordHttpClient.calculateMD5(img));
file.setCatalogue("病历首页");
file.setCatalogueId("C6A8308D842B450FB9FA0D21762237AB");
file.setCode("CODE_" + System.currentTimeMillis());
file.setTimeStapPrice(String.valueOf(System.currentTimeMillis()));
fileList.add(file);
}
// 6. 构建病人信息
MedicalRecordData data = new MedicalRecordData();
data.setNumber("BA" + System.currentTimeMillis());
data.setSickName("测试患者");
data.setSerialNumber("SN" + System.currentTimeMillis());
data.setInHospitalNumber(1);
data.setInSection("影像科");
data.setInSectionId(" ");
data.setOutSection("影像科");
data.setOutSectionId(" ");
data.setAttendingPhysician(" ");
data.setAttendingPhysicianId(" ");
data.setIdentityCard(" ");
data.setInHospitalDate("2024-01-20 08:00:00");
data.setOutHospitalDate("2024-01-20 10:30:00");
// 7. 构建XML对象
MedicalRecordXml xml =
new MedicalRecordXml(fileList, data);
// 8. 上传第三方系统
String recordId = httpClient.uploadMedicalRecord(sid, xml, zipFile);
// 9 清理临时文件
Files.deleteIfExists(zipFile);
return "同步成功病案ID: " + recordId;
} catch (MedicalRecordException e) {
log.error("业务异常: {}", e.getMessage(), e);
return "同步失败(业务异常): " + e.getMessage();
} catch (IOException e) {
log.error("IO异常: {}", e.getMessage(), e);
return "同步失败IO异常: " + e.getMessage();
} catch (Exception e) {
log.error("未知异常: {}", e.getMessage(), e);
return "同步失败(未知异常): " + e.getMessage();
}
finally {
if (zipFile != null) {
try {
Files.deleteIfExists(zipFile);
log.debug("临时文件在finally中已清理");
} catch (IOException e) {
log.warn("清理临时文件失败: {}", e.getMessage());
}
}
}
}
private String getExtension(Path path) {
String name = path.getFileName().toString();
int i = name.lastIndexOf('.');
return i > 0 ? name.substring(i + 1) : "";
}
}

@ -0,0 +1,43 @@
package com.medical.record.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class AuthResponse {
private String msg;
private String result;
private String data;
private Integer code;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}

@ -0,0 +1,91 @@
package com.medical.record.dto;
public class MedicalAttachmentFile {
private String fileName;
private Long fileSize;
private String fileSuffix;
private String md5;
private String catalogue;
private String catalogueId;
private String code;
private String timeStapPrice;
public MedicalAttachmentFile() {}
public MedicalAttachmentFile(String fileName, Long fileSize, String fileSuffix, String md5,
String catalogue, String catalogueId) {
this.fileName = fileName;
this.fileSize = fileSize;
this.fileSuffix = fileSuffix;
this.md5 = md5;
this.catalogue = catalogue;
this.catalogueId = catalogueId;
}
// Getters and Setters
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Long getFileSize() {
return fileSize;
}
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
public String getFileSuffix() {
return fileSuffix;
}
public void setFileSuffix(String fileSuffix) {
this.fileSuffix = fileSuffix;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public String getCatalogue() {
return catalogue;
}
public void setCatalogue(String catalogue) {
this.catalogue = catalogue;
}
public String getCatalogueId() {
return catalogueId;
}
public void setCatalogueId(String catalogueId) {
this.catalogueId = catalogueId;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTimeStapPrice() {
return timeStapPrice;
}
public void setTimeStapPrice(String timeStapPrice) {
this.timeStapPrice = timeStapPrice;
}
}

@ -0,0 +1,154 @@
package com.medical.record.dto;
public class MedicalRecordData {
/** 病案号 */
private String number;
/** 患者姓名 */
private String sickName;
/** 流水号 */
private String serialNumber;
/** 住院次数 */
private Integer inHospitalNumber;
/** 入院科室 */
private String inSection;
/** 入院科室id */
private String inSectionId;
/** 出院科室 */
private String outSection;
/** 出院科室ID非必填 */
private String outSectionId;
/** 主治医生(非必填) */
private String attendingPhysician;
/** 主治医生ID非必填 */
private String attendingPhysicianId;
/** 身份证号(非必填) */
private String identityCard;
/** 入院时间格式yyyy-MM-dd HH:mm:ss */
private String inHospitalDate;
/** 出院时间格式yyyy-MM-dd HH:mm:ss */
private String outHospitalDate;
public String getAttendingPhysician() {
return attendingPhysician;
}
public void setAttendingPhysician(String attendingPhysician) {
this.attendingPhysician = attendingPhysician;
}
public String getAttendingPhysicianId() {
return attendingPhysicianId;
}
public void setAttendingPhysicianId(String attendingPhysicianId) {
this.attendingPhysicianId = attendingPhysicianId;
}
public String getIdentityCard() {
return identityCard;
}
public void setIdentityCard(String identityCard) {
this.identityCard = identityCard;
}
public String getOutSectionId() {
return outSectionId;
}
public String getInSectionId() {
return inSectionId;
}
public void setInSectionId(String inSectionId) {
this.inSectionId = inSectionId;
}
public void setOutSectionId(String outSectionId) {
this.outSectionId = outSectionId;
}
// Getters and Setters
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getSickName() {
return sickName;
}
public void setSickName(String sickName) {
this.sickName = sickName;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public Integer getInHospitalNumber() {
return inHospitalNumber;
}
public void setInHospitalNumber(Integer inHospitalNumber) {
this.inHospitalNumber = inHospitalNumber;
}
public String getInSection() {
return inSection;
}
public void setInSection(String inSection) {
this.inSection = inSection;
}
public String getOutSection() {
return outSection;
}
public void setOutSection(String outSection) {
this.outSection = outSection;
}
public String getInHospitalDate() {
return inHospitalDate;
}
public void setInHospitalDate(String inHospitalDate) {
this.inHospitalDate = inHospitalDate;
}
public String getOutHospitalDate() {
return outHospitalDate;
}
public void setOutHospitalDate(String outHospitalDate) {
this.outHospitalDate = outHospitalDate;
}
}

@ -0,0 +1,7 @@
package com.medical.record.dto;
public class MedicalRecordException extends Exception {
public MedicalRecordException(String message) {
super(message);
}
}

@ -0,0 +1,39 @@
package com.medical.record.dto;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import java.util.List;
@JacksonXmlRootElement(localName = "datas")
public class MedicalRecordXml {
@JacksonXmlProperty(localName = "file")
@JacksonXmlElementWrapper(useWrapping = false)
private List<MedicalAttachmentFile> files;
@JacksonXmlProperty(localName = "data")
private MedicalRecordData data;
public MedicalRecordXml() {}
public MedicalRecordXml(List<MedicalAttachmentFile> files, MedicalRecordData data) {
this.files = files;
this.data = data;
}
public List<MedicalAttachmentFile> getFiles() {
return files;
}
public void setFiles(List<MedicalAttachmentFile> files) {
this.files = files;
}
public MedicalRecordData getData() {
return data;
}
public void setData(MedicalRecordData data) {
this.data = data;
}
}

@ -0,0 +1,47 @@
package com.medical.record.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class UploadResponse {
private String msg;
private String result;
private RecordData data;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public RecordData getData() {
return data;
}
public void setData(RecordData data) {
this.data = data;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static class RecordData {
private String recordId;
public String getRecordId() {
return recordId;
}
public void setRecordId(String recordId) {
this.recordId = recordId;
}
}
}

@ -0,0 +1,71 @@
server:
port: 8891
spring:
datasource:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://localhost:1433;databaseName=medical_record_db;encrypt=true;trustServerCertificate=true;
username: SA
password: YourStrong@Passw0rd
hikari:
minimum-idle: 5
maximum-pool-size: 15
auto-commit: true
connection-timeout: 30000
idle-timeout: 300000
max-lifetime: 1200000
# MyBatis-Plus配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.medical.record.entity
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# ==========================================
# SLF4J + Logback 日志配置
# ==========================================
logging:
# 日志级别
level:
root: INFO
com.medical.record: DEBUG
com.medical.record.client: DEBUG # 第三方接口调用日志
com.medical.record.service: DEBUG
com.medical.record.mapper: DEBUG # SQL日志
org.springframework.web: INFO
org.apache.http: INFO
com.baomidou.mybatisplus: INFO
# 控制台输出格式
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n"
# 文件输出配置
file:
# 日志文件路径(生产环境必须配置)
path: D:/logs/medical-record
name: ${logging.file.path}/medical-record.log
# 日志文件大小限制
max-size: 100MB
# 保留天数
max-history: 30
# 总大小限制(达到后自动清理)
total-size-cap: 3GB
# 日志分组(便于管理)
group:
medical:
- "com.medical.record"
- "com.medical.record.controller"
- "com.medical.record.service"
external:
- "org.apache.http"
- "com.medical.record.client"

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 引入Spring Boot默认配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 定义变量 -->
<property name="LOG_PATH" value="${LOG_PATH:-D:/logs/medical-record}"/>
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH}/medical-record.log}"/>
<property name="MAX_FILE_SIZE" value="100MB"/>
<property name="MAX_HISTORY" value="30"/>
<property name="TOTAL_SIZE_CAP" value="3GB"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 文件输出(按天滚动) -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名称模板 -->
<fileNamePattern>${LOG_PATH}/medical-record.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 保留天数 -->
<maxHistory>${MAX_HISTORY}</maxHistory>
<!-- 总大小限制 -->
<totalSizeCap>${TOTAL_SIZE_CAP}</totalSizeCap>
<!-- 时间策略 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- 错误日志单独输出 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/medical-record-error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/medical-record-error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!-- 异步日志(提升性能) -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>true</includeCallerData>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<!-- 自定义Logger可选 -->
<logger name="com.medical.record.client" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
</logger>
<logger name="com.medical.record.mapper" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
</configuration>

@ -0,0 +1,6 @@
<html>
<body>
<h1>hello word!!!</h1>
<p>this is a html page</p>
</body>
</html>

@ -0,0 +1,13 @@
package com.medical.record;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RecordSystemApplicationTests {
@Test
void contextLoads() {
}
}
Loading…
Cancel
Save