|
@@ -0,0 +1,306 @@
|
|
|
+package com.fs.store.service;
|
|
|
+
|
|
|
+import com.fs.common.config.FSSysConfig;
|
|
|
+import com.fs.store.dto.MedicalRecordDTO;
|
|
|
+import com.fs.system.oss.CloudStorageService;
|
|
|
+import com.fs.system.oss.OSSFactory;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import net.coobird.thumbnailator.Thumbnails;
|
|
|
+import net.coobird.thumbnailator.resizers.configurations.Antialiasing;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.imageio.ImageIO;
|
|
|
+import java.awt.*;
|
|
|
+import java.awt.image.BufferedImage;
|
|
|
+import java.io.*;
|
|
|
+import java.net.URL;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Date;
|
|
|
+
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class MedicalRecordService {
|
|
|
+ @Autowired
|
|
|
+ private FSSysConfig fsSysConfig;
|
|
|
+ /**
|
|
|
+ * 生成病历单图片并上传到OSS
|
|
|
+ * @param dto 病历单数据
|
|
|
+ * @return OSS图片URL
|
|
|
+ */
|
|
|
+ public String generateMedicalRecord(MedicalRecordDTO dto) {
|
|
|
+ try {
|
|
|
+ // 创建图片
|
|
|
+ BufferedImage image = createMedicalRecordImage(dto);
|
|
|
+
|
|
|
+ // 转换为字节流
|
|
|
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
|
+ ImageIO.write(image, "png", os);
|
|
|
+
|
|
|
+ // 上传到OSS
|
|
|
+ InputStream inputStream = new ByteArrayInputStream(os.toByteArray());
|
|
|
+ CloudStorageService storage = OSSFactory.build();
|
|
|
+ String url = storage.uploadSuffix(inputStream, ".jpg");
|
|
|
+
|
|
|
+ log.info("病历单生成成功,URL: {}", url);
|
|
|
+ return url;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("生成病历单失败", e);
|
|
|
+ throw new RuntimeException("生成病历单失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建病历单图片
|
|
|
+ */
|
|
|
+ private BufferedImage createMedicalRecordImage(MedicalRecordDTO dto) {
|
|
|
+ // 创建A4大小的图片 (210mm x 297mm, 72dpi)
|
|
|
+ int width = 595; // A4宽度
|
|
|
+ int height = 842; // A4高度
|
|
|
+
|
|
|
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
|
|
+ Graphics2D g2d = image.createGraphics();
|
|
|
+
|
|
|
+ // 设置抗锯齿,提高文字清晰度
|
|
|
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
|
+ g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
|
|
+
|
|
|
+ // 填充白色背景
|
|
|
+ g2d.setColor(Color.WHITE);
|
|
|
+ g2d.fillRect(0, 0, width, height);
|
|
|
+
|
|
|
+ // 设置黑色画笔
|
|
|
+ g2d.setColor(Color.BLACK);
|
|
|
+
|
|
|
+ // 绘制标题
|
|
|
+ Font titleFont = new Font("黑体", Font.PLAIN,20);
|
|
|
+ g2d.setFont(titleFont);
|
|
|
+ drawCenteredString(g2d, "成都御君方互联网医院电子病历", width, 40);
|
|
|
+
|
|
|
+ // 设置内容字体
|
|
|
+ Font labelFont = new Font("黑体", Font.PLAIN, 12);
|
|
|
+ Font contentFont = new Font("黑体", Font.PLAIN, 12);
|
|
|
+
|
|
|
+ // 起始坐标
|
|
|
+ int leftMargin = 50;
|
|
|
+ int rightMargin = width - 50;
|
|
|
+ int yPos = 80;
|
|
|
+ int lineHeight = 25;
|
|
|
+ int columnWidth = (rightMargin - leftMargin) / 2;
|
|
|
+
|
|
|
+ // 绘制日期和基本信息行
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("就诊日期:", leftMargin, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(formatDate(dto.getVisitDate()), leftMargin + 70, yPos);
|
|
|
+
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("科别:", leftMargin + columnWidth, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(dto.getDepartment() != null ? dto.getDepartment() : "", leftMargin + columnWidth + 40, yPos);
|
|
|
+
|
|
|
+ yPos += lineHeight;
|
|
|
+
|
|
|
+ // 门诊号和付款类型
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("门诊号:", leftMargin, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(dto.getOutpatientNo() != null ? dto.getOutpatientNo() : "", leftMargin + 55, yPos);
|
|
|
+
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("付款类型:", leftMargin + columnWidth, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(dto.getPaymentType() != null ? dto.getPaymentType() : "", leftMargin + columnWidth + 65, yPos);
|
|
|
+
|
|
|
+ yPos += lineHeight+30;
|
|
|
+
|
|
|
+ // 患者基本信息
|
|
|
+ drawPatientInfo(g2d, dto, leftMargin, rightMargin, yPos, lineHeight, labelFont, contentFont);
|
|
|
+ yPos += lineHeight * 3;
|
|
|
+
|
|
|
+ // 分隔线
|
|
|
+ g2d.drawLine(leftMargin, yPos, rightMargin, yPos);
|
|
|
+ yPos += 30;
|
|
|
+
|
|
|
+ // 病情信息
|
|
|
+ yPos = drawSection(g2d, "主诉:", dto.getChiefComplaint(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "现病史:", dto.getPresentIllness(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "既往史:", dto.getPastHistory(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "过敏史:", dto.getAllergyHistory(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "生命体征:", dto.getVitalSigns(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "辅助检查:", dto.getAuxiliaryExam(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "诊断:", dto.getDiagnosis(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "医嘱:", dto.getMedicalAdvice(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "健康教育:", dto.getHealthEducation(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "处理意见:", dto.getTreatmentPlan(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+ yPos = drawSection(g2d, "处方1:", dto.getPrescription(), leftMargin, rightMargin, yPos, labelFont, contentFont);
|
|
|
+
|
|
|
+ // 医生签名
|
|
|
+ yPos += 60;
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("医生签名:", rightMargin - 200, yPos);
|
|
|
+
|
|
|
+ try{
|
|
|
+ URL imageUrl = new URL(fsSysConfig.getDoctorSignImgUrl());
|
|
|
+ BufferedImage scaledImg = Thumbnails.of(imageUrl)
|
|
|
+ .size(120, 55)
|
|
|
+ .outputQuality(1.0)
|
|
|
+ .antialiasing(Antialiasing.ON)
|
|
|
+ .asBufferedImage();
|
|
|
+ g2d.drawImage(scaledImg, rightMargin - 150, yPos-25, null);
|
|
|
+
|
|
|
+ }catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 底部日期
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("就诊日期:", leftMargin, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(formatDate(dto.getVisitDate()), leftMargin + 70, yPos);
|
|
|
+
|
|
|
+ g2d.dispose();
|
|
|
+ return image;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绘制患者基本信息
|
|
|
+ */
|
|
|
+ private void drawPatientInfo(Graphics2D g2d, MedicalRecordDTO dto, int leftMargin, int rightMargin,
|
|
|
+ int yPos, int lineHeight, Font labelFont, Font contentFont) {
|
|
|
+ int columnWidth = (rightMargin - leftMargin) / 3;
|
|
|
+
|
|
|
+ // 第一行:姓名、性别、年龄
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("姓名:", leftMargin, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(dto.getPatientName() != null ? dto.getPatientName() : "", leftMargin + 40, yPos);
|
|
|
+
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("性别:", leftMargin + columnWidth, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(dto.getGender() != null ? dto.getGender() : "", leftMargin + columnWidth + 40, yPos);
|
|
|
+
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("年龄:", leftMargin + columnWidth * 2, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(dto.getAge() != null ? dto.getAge() + "岁" : "", leftMargin + columnWidth * 2 + 40, yPos);
|
|
|
+
|
|
|
+ // 第二行:身高、体重、联系电话
|
|
|
+ yPos += lineHeight;
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("身高/体重:", leftMargin, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ String heightWeight = (dto.getHeight() != null ? dto.getHeight() : "") + "/" +
|
|
|
+ (dto.getWeight() != null ? dto.getWeight() : "");
|
|
|
+ g2d.drawString(heightWeight, leftMargin + 75, yPos);
|
|
|
+
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("联系电话:", leftMargin + columnWidth, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(dto.getPhone() != null ? dto.getPhone() : "", leftMargin + columnWidth + 65, yPos);
|
|
|
+
|
|
|
+ // 第三行:单位或地址
|
|
|
+ yPos += lineHeight;
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString("单位或地址:", leftMargin, yPos);
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ g2d.drawString(dto.getAddress() != null ? dto.getAddress() : "", leftMargin + 85, yPos);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绘制文本段落(支持自动换行)
|
|
|
+ */
|
|
|
+ private int drawSection(Graphics2D g2d, String label, String content, int leftMargin, int rightMargin,
|
|
|
+ int yPos, Font labelFont, Font contentFont) {
|
|
|
+ g2d.setFont(labelFont);
|
|
|
+ g2d.drawString(label, leftMargin, yPos);
|
|
|
+
|
|
|
+ if (content == null || content.isEmpty()) {
|
|
|
+ return yPos + 25;
|
|
|
+ }
|
|
|
+
|
|
|
+ g2d.setFont(contentFont);
|
|
|
+ FontMetrics fm = g2d.getFontMetrics();
|
|
|
+ int labelWidth = fm.stringWidth(label);
|
|
|
+ int contentX = leftMargin + labelWidth;
|
|
|
+ int maxWidth = rightMargin - contentX - 10;
|
|
|
+
|
|
|
+ // 自动换行处理
|
|
|
+ String[] lines = wrapText(content, fm, maxWidth);
|
|
|
+ for (int i = 0; i < lines.length; i++) {
|
|
|
+ if (i == 0) {
|
|
|
+ g2d.drawString(lines[i], contentX, yPos);
|
|
|
+ } else {
|
|
|
+ yPos += 20;
|
|
|
+ g2d.drawString(lines[i], leftMargin + 20, yPos);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return yPos + 25;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 文本自动换行
|
|
|
+ */
|
|
|
+ private String[] wrapText(String text, FontMetrics fm, int maxWidth) {
|
|
|
+ java.util.List<String> lines = new java.util.ArrayList<>();
|
|
|
+
|
|
|
+ // 先处理制表符,将 \t 替换为4个空格(可根据需要调整)
|
|
|
+ text = text.replace("\t", " ");
|
|
|
+
|
|
|
+ // 按换行符分割,保留原有的换行
|
|
|
+ String[] paragraphs = text.split("\n", -1);
|
|
|
+
|
|
|
+ for (String paragraph : paragraphs) {
|
|
|
+ if (paragraph.isEmpty()) {
|
|
|
+ lines.add(""); // 保留空行
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 对每个段落进行宽度换行处理
|
|
|
+ StringBuilder line = new StringBuilder();
|
|
|
+ String[] chars = paragraph.split("");
|
|
|
+
|
|
|
+ for (String ch : chars) {
|
|
|
+ String testLine = line.toString() + ch;
|
|
|
+ if (fm.stringWidth(testLine) > maxWidth) {
|
|
|
+ if (line.length() > 0) {
|
|
|
+ lines.add(line.toString());
|
|
|
+ line = new StringBuilder(ch);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ line.append(ch);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (line.length() > 0) {
|
|
|
+ lines.add(line.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return lines.toArray(new String[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 居中绘制文字
|
|
|
+ */
|
|
|
+ private void drawCenteredString(Graphics2D g2d, String text, int width, int y) {
|
|
|
+ FontMetrics fm = g2d.getFontMetrics();
|
|
|
+ int x = (width - fm.stringWidth(text)) / 2;
|
|
|
+ g2d.drawString(text, x, y);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化日期
|
|
|
+ */
|
|
|
+ private String formatDate(Date date) {
|
|
|
+ if (date == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
|
|
|
+ return sdf.format(date);
|
|
|
+ }
|
|
|
+}
|