新增检验报告打印接口

This commit is contained in:
余文财 2025-06-25 10:01:50 +08:00
parent 25597dcfc9
commit 97aa3efef6
7 changed files with 302 additions and 18 deletions

23
pom.xml
View File

@ -17,7 +17,7 @@
</parent>
<properties>
<java.version>1.8</java.version>
<java.version>11</java.version>
<hutool.version>5.8.36</hutool.version>
<jna.version>5.17.0</jna.version>
<pdfbox.version>3.0.2</pdfbox.version>
@ -159,6 +159,27 @@
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<!-- 报告打印转PDF -->
<!-- iText 核心 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<!-- XML Worker (HTML 转 PDF) -->
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.13.3</version>
</dependency>
<!-- 中文字体支持 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies>

View File

@ -4,10 +4,13 @@ import com.dpkj.common.dto.LexMarkResultDTO;
import com.dpkj.common.vo.Result;
import com.dpkj.modules.print.request.MS439Request;
import com.dpkj.modules.print.service.MS439PrintService;
import com.dpkj.modules.print.vo.PrinterStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* MS439控制层利盟打印机
@ -24,12 +27,28 @@ public class MS439Controller {
private MS439PrintService ms439PrintService;
@PostMapping("/print")
public Result<LexMarkResultDTO<?>> print(@Validated @RequestBody MS439Request request){
public Result<LexMarkResultDTO<?>> print(@Validated @RequestBody MS439Request request) {
return Result.ok(ms439PrintService.printImage(request));
}
/**
* 检验报告打印
*
* @return
*/
@PostMapping("/printJY")
public Result<LexMarkResultDTO<?>> printJY() {
MS439Request ms439Request = new MS439Request();
// 获取检验报告PDF
String path = ms439PrintService.getJYPDFPath();
ms439Request.setPagesource("A5");
ms439Request.setFileDir(path);
LexMarkResultDTO<?> result = ms439PrintService.printImage(ms439Request);
return Result.ok(result);
}
@PostMapping("/getStatus")
public Result<String> print(@RequestParam(defaultValue = "A4") String papersource){
public Result<String> print(@RequestParam(defaultValue = "A4") String papersource) {
this.ms439PrintService.getStatus(papersource);
return Result.ok();
}

View File

@ -3,11 +3,12 @@ package com.dpkj.modules.print.service;
import com.dpkj.common.dto.LexMarkResultDTO;
import com.dpkj.modules.print.request.MS439Request;
import com.dpkj.modules.print.vo.PrinterStatus;
import javassist.compiler.Lex;
public interface MS439PrintService {
LexMarkResultDTO<?> printImage(MS439Request request);
LexMarkResultDTO<PrinterStatus> getStatus(String papersource);
String getJYPDFPath();
}

View File

@ -6,17 +6,48 @@ import com.dpkj.common.dto.LexMarkDTO;
import com.dpkj.common.dto.LexMarkResultDTO;
import com.dpkj.common.exception.RRException;
import com.dpkj.common.utils.ThirdService;
import com.dpkj.modules.print.enums.*;
import com.dpkj.modules.print.enums.MS439DeviceStatusEnum;
import com.dpkj.modules.print.enums.MS439InkStatusEnum;
import com.dpkj.modules.print.enums.MS439MediaStatusEnum;
import com.dpkj.modules.print.enums.MS439PaperStatusEnum;
import com.dpkj.modules.print.enums.MS439StampStatusEnum;
import com.dpkj.modules.print.enums.MS439TonerStatusEnum;
import com.dpkj.modules.print.request.MS439Request;
import com.dpkj.modules.print.service.MS439PrintService;
import com.dpkj.modules.print.utils.FolderUtils;
import com.dpkj.modules.print.utils.FontLoader;
import com.dpkj.modules.print.utils.PDFUtils;
import com.dpkj.modules.print.vo.PrinterStatus;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.templatemode.TemplateMode;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 利盟MS439打印机服务
@ -49,7 +80,7 @@ public class MS439PrintServiceImpl implements MS439PrintService {
PrinterStatus ms439 = status.getData();
// 如果是要盖章校验盖章机器是否正常
if ( request.getStamp() == 1) {
if (request.getStamp() == 1) {
String deviceExStatus = ms439.getStDeviceExStatus();
if (deviceExStatus == null || deviceExStatus.equals("")) {
throw new RRException("获取打印机设备状态出问题");
@ -60,7 +91,7 @@ public class MS439PrintServiceImpl implements MS439PrintService {
}
String device = devices[1];
// 校验盖章机是否正常,除了HEALTHY都抛异常
if (!device.equals(MS439StampStatusEnum.HEALTHY.getPrintCode()) ) {
if (!device.equals(MS439StampStatusEnum.HEALTHY.getPrintCode())) {
throw new RRException(MS439StampStatusEnum.getPCode(device),
MS439StampStatusEnum.getMessage(device));
}
@ -72,7 +103,7 @@ public class MS439PrintServiceImpl implements MS439PrintService {
lexMarkDTO.setDevName("HtmPrinter");
lexMarkDTO.setPluginMethod("exec");
JSONObject param = new JSONObject();
JSONObject param = new JSONObject();
// printType 选择箱子
// param.put("prtData", String.format("PrintType=%d;pagesource=%s;copies=%d;file[0]=%s;stamp=%d;duplex=%d;color=%d;direction=%d",
@ -104,7 +135,7 @@ public class MS439PrintServiceImpl implements MS439PrintService {
lexMarkDTO.setParam(param.toString());
LexMarkResultDTO<LexMarkResultDTO.Param> paramLexMarkResultDTO = thirdService.callDevice(lexMarkDTO, LexMarkResultDTO.Param.class);
if ( paramLexMarkResultDTO.getData().getResult() != 0){
if (paramLexMarkResultDTO.getData().getResult() != 0) {
throw new RRException(500, paramLexMarkResultDTO.getData().getResult() + "");
}
@ -132,7 +163,7 @@ public class MS439PrintServiceImpl implements MS439PrintService {
lexMarkDTO.setPluginMethod("exec");
LexMarkResultDTO<PrinterStatus> status = thirdService.callDevice(lexMarkDTO, PrinterStatus.class);
if ( status.getResult() != 0){
if (status.getResult() != 0) {
thirdService.open("HtmPrinter", 0);
status = thirdService.callDevice(lexMarkDTO, PrinterStatus.class);
}
@ -142,7 +173,7 @@ public class MS439PrintServiceImpl implements MS439PrintService {
throw new RRException("获取打印机纸张状态出问题");
}
String[] papers = stPaperEx.split("\\|");
if ( papers.length < 2) {
if (papers.length < 2) {
throw new RRException("打印机纸盒数量不对");
}
String paperStatus = "";
@ -154,13 +185,13 @@ public class MS439PrintServiceImpl implements MS439PrintService {
if (papersource.equals("A5")) {
paperStatus = printerConfig.getLevelOne().equals("A5") ? papers[0] : papers[1];
}
if ( !(paperStatus.equals(MS439PaperStatusEnum.PAPERFULL.getPrintCode()) || paperStatus.equals(MS439PaperStatusEnum.PAPERLOW.getPrintCode()))){
if (!(paperStatus.equals(MS439PaperStatusEnum.PAPERFULL.getPrintCode()) || paperStatus.equals(MS439PaperStatusEnum.PAPERLOW.getPrintCode()))) {
throw new RRException(500, paperStatus);
}
PrinterStatus ms439 = status.getData();
// 校验打印机是否正常,除了HEALTHY都抛异常
if (!ms439.getStDeviceStatus().equals(MS439DeviceStatusEnum.HEALTHY.getPrintCode()) ) {
if (!ms439.getStDeviceStatus().equals(MS439DeviceStatusEnum.HEALTHY.getPrintCode())) {
throw new RRException(500, ms439.getStDeviceStatus());
}
@ -170,19 +201,131 @@ public class MS439PrintServiceImpl implements MS439PrintService {
}
// 校验磁带 满或者少才放行
if ( !(ms439.getStToner().equals(MS439TonerStatusEnum.TONERFULL.getPrintCode()) || ms439.getStToner().equals(MS439TonerStatusEnum.TONERLOW.getPrintCode()))){
if (!(ms439.getStToner().equals(MS439TonerStatusEnum.TONERFULL.getPrintCode()) || ms439.getStToner().equals(MS439TonerStatusEnum.TONERLOW.getPrintCode()))) {
throw new RRException(500, ms439.getStToner());
}
// 校验墨盒 满或者少才放行当前获取为未知
if ( false && !(ms439.getStInk().equals(MS439InkStatusEnum.INKFULL.getPrintCode()) || ms439.getStInk().equals(MS439InkStatusEnum.INKLOW.getPrintCode())
|| ms439.getStInk().equals(MS439InkStatusEnum.INKUNKNOWN.getPrintCode()))){
if (false && !(ms439.getStInk().equals(MS439InkStatusEnum.INKFULL.getPrintCode()) || ms439.getStInk().equals(MS439InkStatusEnum.INKLOW.getPrintCode())
|| ms439.getStInk().equals(MS439InkStatusEnum.INKUNKNOWN.getPrintCode()))) {
throw new RRException(500, ms439.getStInk());
}
return status;
}
@Override
public String getJYPDFPath() {
// 1. 准备输出路径
String dirPath = "D:/TempJYPDF/";
File dir = new File(dirPath);// 确保目录存在
if (!dir.exists()) {
dir.mkdirs();
}
String fileName = "report_jy_" + System.currentTimeMillis() + ".pdf";
String pdfPath = dirPath + fileName;
try {
// 2. 准备模板数据
Map<String, Object> data = prepareReportData();
// 3. 渲染Thymeleaf模板
String htmlContent = renderThymeleafTemplate(data);
// 4. 生成PDF文件
createPdfFromHtml(htmlContent, pdfPath);
return pdfPath;
} catch (Exception e) {
e.printStackTrace(); // 打印完整异常堆栈
throw new RuntimeException("PDF生成失败: " + e.getMessage());
}
}
private void createPdfFromHtml(String html, String outputPath)
throws IOException, DocumentException {
// 大小为A5纸
Document document = new Document(PageSize.A5);
try (OutputStream os = Files.newOutputStream(Paths.get(outputPath))) {
PdfWriter writer = PdfWriter.getInstance(document, os);
document.open();
// 加载自定义字体
BaseFont simfangFont = FontLoader.loadFont("/font/simfang.ttf");
// 创建自定义字体提供器
XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider() {
@Override
public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color) {
return new Font(simfangFont, size, style, color);
}
};
// 解析 HTML 并生成 PDF
XMLWorkerHelper.getInstance().parseXHtml(
writer, document,
new ByteArrayInputStream(html.getBytes(StandardCharsets.UTF_8)),
null, StandardCharsets.UTF_8,
fontProvider // 注入字体提供器
);
document.close(); // 这里关闭确保 OutputStream 还没被关闭
}
}
// 模板渲染方法保持不变
private String renderThymeleafTemplate(Map<String, Object> data) {
// 设置模板数据
Context context = new Context();
context.setVariables(data);
// 填充数据到html
TemplateEngine templateEngine = new TemplateEngine();
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCacheable(true);
resolver.setApplicationContext(new AnnotationConfigReactiveWebApplicationContext());
templateEngine.setTemplateResolver(resolver);
return templateEngine.process("reportJY", context);
}
// 报告数据准备根据实际业务实现
private Map<String, Object> prepareReportData() {
Map<String, Object> data = new HashMap<>();
// 患者信息
Map<String, String> patient = new HashMap<>();
patient.put("name", "余文财");
patient.put("gender", "");
patient.put("age", "25岁");
patient.put("medicalRecord", "20240624001");
patient.put("department", "内科");
patient.put("bedNumber", "A301");
data.put("patient", patient);
// 检验项目数据
List<Map<String, Object>> items = new ArrayList<>();
addItem(items, "白细胞计数", "7.26", "3.5-9.5", "10^9/L", false);
addItem(items, "血红蛋白", "165", "115-150", "g/L", true);
// ...其他检验项
data.put("items", items);
// 系统信息
data.put("reportTime", new Date());
data.put("doctor", "李医师");
data.put("reviewer", "王主任");
return data;
}
private void addItem(List<Map<String, Object>> items, String name,
String value, String range, String unit, boolean abnormal) {
Map<String, Object> item = new HashMap<>();
item.put("name", name);
item.put("result", value);
item.put("referenceRange", range);
item.put("unit", unit);
item.put("isAbnormal", abnormal);
items.add(item);
}
}

View File

@ -0,0 +1,31 @@
package com.dpkj.modules.print.utils;
import com.itextpdf.text.pdf.BaseFont;
import java.io.InputStream;
/**
* @author 余文财
* @description 字体加载
* @date 2025.6.24
*/
public class FontLoader {
public static BaseFont loadFont(String resourcePath) {
try (InputStream fontStream = FontLoader.class.getResourceAsStream(resourcePath)) {
if (fontStream == null) {
throw new IllegalArgumentException("字体文件未找到: " + resourcePath);
}
// 创建支持中文的 BaseFont
return BaseFont.createFont(
resourcePath,
BaseFont.IDENTITY_H,
BaseFont.EMBEDDED,
false,
fontStream.readAllBytes(),
null
);
} catch (Exception e) {
throw new RuntimeException("字体加载失败: " + resourcePath, e);
}
}
}

Binary file not shown.

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>检验报告单</title>
</head>
<body>
<div>
<!-- 标题区域 -->
<div style="text-align:center; margin-bottom:20px;">
<h2 style="color:#003366;">澜沧拉祜族自治县中医医院血常规检验报告单</h2>
<p>报告时间: <span th:text="${#dates.format(reportTime, 'yyyy-MM-dd HH:mm')}"></span></p>
</div>
<!-- 患者信息表格 -->
<div>
<table style="width:100%; border-collapse:collapse;">
<tr>
<td style="width:15%; padding:5px; border:1px solid #ddd;">姓名</td>
<td style="width:35%; padding:5px; border:1px solid #ddd;" th:text="${patient.name}"></td>
<td style="width:15%; padding:5px; border:1px solid #ddd;">性别</td>
<td style="width:35%; padding:5px; border:1px solid #ddd;" th:text="${patient.gender}"></td>
</tr>
<tr>
<td style="padding:5px; border:1px solid #ddd;">年龄</td>
<td style="padding:5px; border:1px solid #ddd;" th:text="${patient.age}"></td>
<td style="padding:5px; border:1px solid #ddd;">病历号</td>
<td style="padding:5px; border:1px solid #ddd;" th:text="${patient.medicalRecord}"></td>
</tr>
<tr>
<td style="padding:5px; border:1px solid #ddd;">科室</td>
<td style="padding:5px; border:1px solid #ddd;" th:text="${patient.department}"></td>
<td style="padding:5px; border:1px solid #ddd;">床号</td>
<td style="padding:5px; border:1px solid #ddd;" th:text="${patient.bedNumber}"></td>
</tr>
</table>
</div>
<!-- 检测结果表格 -->
<table style="width:100%; border-collapse:collapse; margin-top:20px;">
<thead>
<tr>
<th style="border:1px solid #ddd; padding:8px; text-align:center; background-color:#f2f2f2;">检测项目</th>
<th style="border:1px solid #ddd; padding:8px; text-align:center; background-color:#f2f2f2;">结果</th>
<th style="border:1px solid #ddd; padding:8px; text-align:center; background-color:#f2f2f2;">参考范围</th>
<th style="border:1px solid #ddd; padding:8px; text-align:center; background-color:#f2f2f2;">单位</th>
</tr>
</thead>
<tbody>
<!-- 动态行渲染+异常值红色高亮 -->
<tr th:each="item : ${items}"
th:style="${item.isAbnormal} ? 'color:red;' : ''">
<td style="border:1px solid #ddd; padding:8px; text-align:center;" th:text="${item.name}"></td>
<td style="border:1px solid #ddd; padding:8px; text-align:center;" th:text="${item.result}"></td>
<td style="border:1px solid #ddd; padding:8px; text-align:center;" th:text="${item.referenceRange}"></td>
<td style="border:1px solid #ddd; padding:8px; text-align:center;" th:text="${item.unit}"></td>
</tr>
</tbody>
</table>
<!-- 页脚声明 -->
<div style="margin-top:30px; text-align:right;">
<div>检验医生: <span th:text="${doctor}"></span></div>
<div>审核医生: <span th:text="${reviewer}"></span></div>
<div>声明: 本报告仅对此标本负责如有疑问请3日内联系检验科</div>
</div>
</div>
</body>
</html>