|
|
@@ -0,0 +1,390 @@
|
|
|
+package com.fs.his.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.fs.his.domain.FsPrescribeDrug;
|
|
|
+import com.fs.his.domain.FsStoreOrder;
|
|
|
+import com.fs.his.dto.FsPrescribeUsageDTO;
|
|
|
+import com.fs.his.param.PrescribeXyImgParam;
|
|
|
+import com.fs.his.param.PrescribeZyImgParam;
|
|
|
+import com.fs.his.service.PrescriptionImageService;
|
|
|
+import com.fs.his.vo.FsInquiryOrderVO;
|
|
|
+import com.fs.his.vo.FsPrescribeVO;
|
|
|
+import com.fs.system.oss.CloudStorageService;
|
|
|
+import com.fs.system.oss.OSSFactory;
|
|
|
+import com.github.benmanes.caffeine.cache.Cache;
|
|
|
+import com.github.benmanes.caffeine.cache.Caffeine;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import javax.imageio.ImageIO;
|
|
|
+import java.awt.*;
|
|
|
+import java.awt.image.BufferedImage;
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.File;
|
|
|
+import java.net.URL;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 处方图片生成服务
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class PrescriptionImageServiceImpl implements PrescriptionImageService {
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 模板图片路径
|
|
|
+ */
|
|
|
+ private static final String TEMPLATE_PATH = "C:\\fs\\ysy_prescribe.jpg";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 模板图片缓存key
|
|
|
+ */
|
|
|
+ private static final String TEMPLATE_CACHE_KEY = "prescription_template";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 图片缓存(模板永久,签名24小时)
|
|
|
+ */
|
|
|
+ private final Cache<String, BufferedImage> imageCache = Caffeine.newBuilder()
|
|
|
+ .maximumSize(1000)
|
|
|
+ .expireAfterWrite(24, TimeUnit.HOURS)
|
|
|
+ .build();
|
|
|
+
|
|
|
+ @PostConstruct
|
|
|
+ public void init() {
|
|
|
+ try {
|
|
|
+ getTemplateImage();
|
|
|
+ log.info("处方模板图片预加载成功");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处方模板图片预加载失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String generatePrescriptionImage(PrescribeXyImgParam param) {
|
|
|
+ try {
|
|
|
+ BufferedImage template = getTemplateImage();
|
|
|
+
|
|
|
+ BufferedImage workImage = deepCopy(template);
|
|
|
+
|
|
|
+ Graphics2D pen = workImage.createGraphics();
|
|
|
+
|
|
|
+ // 设置字体和渲染质量
|
|
|
+ setupGraphics(pen);
|
|
|
+
|
|
|
+ // 绘制处方编号和时间
|
|
|
+ drawHeaderInfo(pen, param);
|
|
|
+
|
|
|
+ // 绘制患者信息
|
|
|
+ drawPatientInfo(pen, param);
|
|
|
+
|
|
|
+ // 绘制诊断
|
|
|
+ drawDiagnosis(pen, param);
|
|
|
+
|
|
|
+ // 绘制药品列表
|
|
|
+ int lastY = drawDrugList(pen, param.getPrescribeDrug());
|
|
|
+
|
|
|
+ // 绘制医嘱
|
|
|
+ drawAdvice(pen, param, lastY);
|
|
|
+
|
|
|
+ // 叠加签名图片
|
|
|
+ overlaySignatures(pen, param);
|
|
|
+
|
|
|
+ // 释放资源
|
|
|
+ pen.dispose();
|
|
|
+
|
|
|
+ String url = uploadToOSS(workImage);
|
|
|
+
|
|
|
+ log.info("处方图片生成成功, URL: {}", url);
|
|
|
+ return url;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("生成处方图片失败", e);
|
|
|
+ throw new RuntimeException("生成处方图片失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取模板图片(带缓存)
|
|
|
+ */
|
|
|
+ public BufferedImage getTemplateImage() {
|
|
|
+ return imageCache.get(TEMPLATE_CACHE_KEY, key -> {
|
|
|
+ try {
|
|
|
+ File templateFile = new File(TEMPLATE_PATH);
|
|
|
+ if (!templateFile.exists()) {
|
|
|
+ throw new RuntimeException("模板图片不存在: " + TEMPLATE_PATH);
|
|
|
+ }
|
|
|
+ BufferedImage image = ImageIO.read(templateFile);
|
|
|
+ log.info("模板图片加载成功, 尺寸: {}x{}", image.getWidth(), image.getHeight());
|
|
|
+ return image;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("加载模板图片失败", e);
|
|
|
+ throw new RuntimeException("加载模板图片失败", e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 深拷贝图片
|
|
|
+ */
|
|
|
+ private BufferedImage deepCopy(BufferedImage source) {
|
|
|
+ BufferedImage copy = new BufferedImage(
|
|
|
+ source.getWidth(),
|
|
|
+ source.getHeight(),
|
|
|
+ source.getType()
|
|
|
+ );
|
|
|
+ Graphics2D g = copy.createGraphics();
|
|
|
+ g.drawImage(source, 0, 0, null);
|
|
|
+ g.dispose();
|
|
|
+ return copy;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置Graphics2D渲染参数
|
|
|
+ */
|
|
|
+ private void setupGraphics(Graphics2D pen) {
|
|
|
+ pen.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
|
|
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
|
|
+ pen.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
|
|
+ RenderingHints.VALUE_ANTIALIAS_ON);
|
|
|
+ pen.setRenderingHint(RenderingHints.KEY_RENDERING,
|
|
|
+ RenderingHints.VALUE_RENDER_QUALITY);
|
|
|
+
|
|
|
+ pen.setFont(new Font("黑体", Font.PLAIN, 40));
|
|
|
+ pen.setColor(Color.BLACK);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绘制处方编号和时间
|
|
|
+ */
|
|
|
+ private void drawHeaderInfo(Graphics2D pen, PrescribeXyImgParam param) {
|
|
|
+ // 处方编号
|
|
|
+ if (StringUtils.isNotBlank(param.getPrescribeId())) {
|
|
|
+ pen.drawString(param.getPrescribeId(), 252, 205);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 时间
|
|
|
+ if (param.getTime() != null) {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ String timeStr = sdf.format(param.getTime());
|
|
|
+ pen.drawString(timeStr, 948, 205);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绘制患者信息
|
|
|
+ */
|
|
|
+ private void drawPatientInfo(Graphics2D pen, PrescribeXyImgParam param) {
|
|
|
+ int y = 317;
|
|
|
+
|
|
|
+ // 姓名
|
|
|
+ if (StringUtils.isNotBlank(param.getPatientName())) {
|
|
|
+ pen.drawString(param.getPatientName(), 177, y);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 性别
|
|
|
+ if (StringUtils.isNotBlank(param.getPatientGender())) {
|
|
|
+ String gender = "1".equals(param.getPatientGender()) ? "男" : "女";
|
|
|
+ pen.drawString(gender, 458, y);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 年龄
|
|
|
+ if (StringUtils.isNotBlank(param.getPatientAge())) {
|
|
|
+ pen.drawString(param.getPatientAge(), 679, y);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 电话
|
|
|
+ if (StringUtils.isNotBlank(param.getTelPhone())) {
|
|
|
+ pen.drawString(param.getTelPhone(), 953, y);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绘制诊断信息
|
|
|
+ */
|
|
|
+ private void drawDiagnosis(Graphics2D pen, PrescribeXyImgParam param) {
|
|
|
+ if (StringUtils.isNotBlank(param.getDiagnose())) {
|
|
|
+ int x = 133;
|
|
|
+ int y = 548;
|
|
|
+ int maxWidth = 760;
|
|
|
+ drawMultiLineText(pen, param.getDiagnose(), x, y, maxWidth);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绘制药品列表
|
|
|
+ * @return 返回最后一行的Y坐标
|
|
|
+ */
|
|
|
+ public int drawDrugList(Graphics2D pen, List<FsPrescribeDrug> drugs) {
|
|
|
+ if (drugs == null || drugs.isEmpty()) {
|
|
|
+ return 330;
|
|
|
+ }
|
|
|
+
|
|
|
+ int x = 133;
|
|
|
+ int y = 728;
|
|
|
+ int lineHeight = 30;
|
|
|
+
|
|
|
+ for (FsPrescribeDrug drug : drugs) {
|
|
|
+ // 格式: 药品名 规格 用法 频次 每次用量 x 数量
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+
|
|
|
+ if (StringUtils.isNotBlank(drug.getDrugName())) {
|
|
|
+ sb.append(drug.getDrugName()).append(" ");
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(drug.getDrugSpec())) {
|
|
|
+ sb.append(drug.getDrugSpec()).append(" ");
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(drug.getUsageMethod())) {
|
|
|
+ sb.append(drug.getUsageMethod()).append(" ");
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(drug.getUsageFrequencyUnit())) {
|
|
|
+ sb.append(drug.getUsageFrequencyUnit()).append(" ");
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(drug.getUsagePerUseCount())) {
|
|
|
+ sb.append(drug.getUsagePerUseCount());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(drug.getUsagePerUseUnit())) {
|
|
|
+ sb.append(drug.getUsagePerUseUnit()).append(" ");
|
|
|
+ }
|
|
|
+ if (drug.getDrugNum() != null) {
|
|
|
+ sb.append("x ").append(drug.getDrugNum());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(drug.getDrugUnit())) {
|
|
|
+ sb.append(drug.getDrugUnit());
|
|
|
+ }
|
|
|
+
|
|
|
+ String drugInfo = sb.toString().trim();
|
|
|
+ if (StringUtils.isNotBlank(drugInfo)) {
|
|
|
+ y = drawMultiLineText(pen, drugInfo, x, y, 760);
|
|
|
+ y += lineHeight;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return y;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绘制医嘱
|
|
|
+ */
|
|
|
+ private void drawAdvice(Graphics2D pen, PrescribeXyImgParam param, int startY) {
|
|
|
+ int y = 1100;
|
|
|
+
|
|
|
+ if (StringUtils.isNotBlank(param.getRemark())) {
|
|
|
+ drawMultiLineText(pen, param.getRemark(), 133, y, 500);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 叠加签名图片
|
|
|
+ */
|
|
|
+ private void overlaySignatures(Graphics2D pen, PrescribeXyImgParam param) {
|
|
|
+ // 医师签名
|
|
|
+ if (StringUtils.isNotBlank(param.getUrl())) {
|
|
|
+ try {
|
|
|
+ BufferedImage doctorSign = downloadSignatureImage(param.getUrl());
|
|
|
+ if (doctorSign != null) {
|
|
|
+ Image scaledSign = doctorSign.getScaledInstance(100, 60, Image.SCALE_SMOOTH);
|
|
|
+ pen.drawImage(scaledSign, 202, 1381, null);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("加载医师签名失败: {}", param.getUrl(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 药师签名
|
|
|
+ if (StringUtils.isNotBlank(param.getDrugDoctorUrl())) {
|
|
|
+ try {
|
|
|
+ BufferedImage pharmacistSign = downloadSignatureImage(param.getDrugDoctorUrl());
|
|
|
+ if (pharmacistSign != null) {
|
|
|
+ Image scaledSign = pharmacistSign.getScaledInstance(100, 60, Image.SCALE_SMOOTH);
|
|
|
+ pen.drawImage(scaledSign, 1076, 1556, null);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("加载药师签名失败: {}", param.getDrugDoctorUrl(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载签名图片(带缓存)
|
|
|
+ */
|
|
|
+ private BufferedImage downloadSignatureImage(String url) {
|
|
|
+ if (StringUtils.isBlank(url)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return imageCache.get(url, key -> {
|
|
|
+ try {
|
|
|
+ log.info("下载签名图片: {}", url);
|
|
|
+ URL imageUrl = new URL(url);
|
|
|
+ BufferedImage image = ImageIO.read(imageUrl);
|
|
|
+ return image;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("下载签名图片失败: {}", url, e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 多行文本绘制(自动换行)
|
|
|
+ * @return 返回最后一行的Y坐标
|
|
|
+ */
|
|
|
+ private int drawMultiLineText(Graphics2D pen, String text, int x, int y, int maxWidth) {
|
|
|
+ if (StringUtils.isBlank(text)) {
|
|
|
+ return y;
|
|
|
+ }
|
|
|
+
|
|
|
+ FontMetrics fm = pen.getFontMetrics();
|
|
|
+ String[] words = text.split("");
|
|
|
+ StringBuilder line = new StringBuilder();
|
|
|
+ int currentY = y;
|
|
|
+
|
|
|
+ for (String word : words) {
|
|
|
+ String testLine = line + word;
|
|
|
+ int lineWidth = fm.stringWidth(testLine);
|
|
|
+
|
|
|
+ if (lineWidth > maxWidth && line.length() > 0) {
|
|
|
+ // 绘制当前行
|
|
|
+ pen.drawString(line.toString(), x, currentY);
|
|
|
+ currentY += fm.getHeight();
|
|
|
+ line = new StringBuilder(word);
|
|
|
+ } else {
|
|
|
+ line.append(word);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (line.length() > 0) {
|
|
|
+ pen.drawString(line.toString(), x, currentY);
|
|
|
+ currentY += fm.getHeight();
|
|
|
+ }
|
|
|
+
|
|
|
+ return currentY;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上传图片到OSS
|
|
|
+ */
|
|
|
+ private String uploadToOSS(BufferedImage image) throws Exception {
|
|
|
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
+ ImageIO.write(image, "jpg", baos);
|
|
|
+
|
|
|
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
|
|
+ CloudStorageService storage = OSSFactory.build();
|
|
|
+ String url = storage.uploadSuffix(bais, ".jpg");
|
|
|
+
|
|
|
+ baos.close();
|
|
|
+ bais.close();
|
|
|
+
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+}
|