Ver Fonte

feat: ysy生成处方签名

xdd há 3 semanas atrás
pai
commit
712bf27e60

+ 78 - 2
fs-admin/src/test/java/com/fs/course/controller/OpenIMServiceTest.java

@@ -1,8 +1,21 @@
 package com.fs.course.controller;
 
+import cn.hutool.json.JSONUtil;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.FSApplication;
 import com.fs.common.annotation.DataSource;
+import com.fs.his.domain.FsPrescribe;
+import com.fs.his.domain.FsPrescribeDrug;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.dto.FsPrescribeUsageDTO;
+import com.fs.his.mapper.FsInquiryOrderMapper;
+import com.fs.his.mapper.FsPrescribeDrugMapper;
+import com.fs.his.mapper.FsPrescribeMapper;
+import com.fs.his.param.PrescribeXyImgParam;
+import com.fs.his.service.IFsStoreOrderService;
+import com.fs.his.service.PrescriptionImageService;
+import com.fs.his.vo.FsInquiryOrderVO;
+import com.fs.his.vo.FsPrescribeVO;
 import com.fs.im.dto.OpenImMsgDTO;
 import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
@@ -16,16 +29,79 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.test.context.ActiveProfiles;
 
+import java.util.List;
+
 import static org.junit.Assert.assertNotNull;
 
 @Slf4j
-@ActiveProfiles("druid-fby-test")
+@ActiveProfiles("druid-syysy-test")
 @RunWith(org.springframework.test.context.junit4.SpringRunner.class)
 @SpringBootTest(classes = FSApplication.class)
 public class OpenIMServiceTest {
 
     @Autowired
     private OpenIMService openIMService;
+    @Autowired
+    private PrescriptionImageService prescriptionImageService;
+    @Autowired
+    private FsPrescribeMapper fsPrescribeMapper;
+
+    @Autowired
+    private IFsStoreOrderService storeOrderService;
+
+    @Autowired
+    private FsInquiryOrderMapper fsInquiryOrderMapper;
+
+    @Autowired
+    private FsPrescribeDrugMapper fsPrescribeDrugMapper;
+
+    @Test
+    public void generateImg() {
+        FsPrescribeVO f = fsPrescribeMapper.selectFsPrescribeByPrescribeIdVO(11L);
+
+        FsStoreOrder fsStoreOrder = storeOrderService.selectFsStoreOrderByOrderId(f.getStoreOrderId());
+        FsInquiryOrderVO order = fsInquiryOrderMapper.selectFsInquiryOrderVOByOrderId(f.getInquiryOrderId());
+        FsPrescribeDrug d = new FsPrescribeDrug();
+        d.setPrescribeId(f.getPrescribeId());
+        List<FsPrescribeDrug> list = fsPrescribeDrugMapper.selectFsPrescribeDrugList(d);
+        PrescribeXyImgParam o = new PrescribeXyImgParam();
+        o.setTotalPrice(fsStoreOrder.getPayPrice());
+        o.setTime(f.getCreateTime());
+        o.setPrescribeId(f.getPrescribeCode());
+        o.setPatientName(f.getPatientName());
+        o.setPatientGender(f.getPatientGender());
+        o.setPatientAge(f.getPatientAge());
+        o.setOutpatientId(f.getPrescribeCode());
+        o.setUrl(f.getPrescribeDoctorSignUrl());
+        o.setDrugDoctorUrl(f.getDrugDoctorSignUrl());
+        if (order != null) {
+            o.setBedId(order.getDeptName());
+        } else {
+            o.setBedId("中医科");
+        }
+        String remark = "";
+        if (f.getUsageJson() != null) {
+            FsPrescribeUsageDTO usage = JSONUtil.toBean(f.getUsageJson(), FsPrescribeUsageDTO.class);
+            remark = usage.getRemark();
+        }
+        if (remark != null && remark != "") {
+            o.setRemark(remark);
+        } else {
+            o.setRemark("请按照用药说明书服用药品,如有不适,请及时就医!");
+        }
+        o.setHistoryAllergic(f.getHistoryAllergic());
+        o.setDiagnose("慢性咽炎。");
+        o.setPrescribeDrug(list); // 在这里添加处方药品列表的值
+        o.setDoctorName(f.getDoctorName());
+        o.setAuditDoctor(f.getDoctorDrugName());
+        o.setDispatcher("");
+        o.setCheckDoctor("");
+        o.setDoctorName(f.getDoctorName());
+        o.setTelPhone("15560889998");
+        String s = prescriptionImageService.generatePrescriptionImage(o);
+        log.info(s);
+    }
+
     @Test
     public void openIMSendMsg() {
         OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
@@ -94,7 +170,7 @@ public class OpenIMServiceTest {
         OpenImResponseDTO actualResponse = openIMService.sendCourse(
                 userId, companyUserId, url, title, linkImageUrl, cropId
         );
-        log.info("返回结果: {}",actualResponse);
+        log.info("返回结果: {}", actualResponse);
     }
 
     @Test

+ 14 - 14
fs-quartz/src/main/java/com/fs/quartz/service/impl/SysJobServiceImpl.java

@@ -19,7 +19,7 @@ import com.fs.quartz.util.ScheduleUtils;
 
 /**
  * 定时任务调度信息 服务层
- * 
+ *
 
  */
 @Service
@@ -34,7 +34,7 @@ public class SysJobServiceImpl implements ISysJobService
     /**
      * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
      */
-    @PostConstruct
+//    @PostConstruct
     public void init() throws SchedulerException, TaskException
     {
         scheduler.clear();
@@ -47,7 +47,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 获取quartz调度器的计划任务列表
-     * 
+     *
      * @param job 调度信息
      * @return
      */
@@ -59,7 +59,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 通过调度任务ID查询调度信息
-     * 
+     *
      * @param jobId 调度任务ID
      * @return 调度任务对象信息
      */
@@ -71,7 +71,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 暂停任务
-     * 
+     *
      * @param job 调度信息
      */
     @Override
@@ -91,7 +91,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 恢复任务
-     * 
+     *
      * @param job 调度信息
      */
     @Override
@@ -111,7 +111,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 删除任务后,所对应的trigger也将被删除
-     * 
+     *
      * @param job 调度信息
      */
     @Override
@@ -130,7 +130,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 批量删除调度信息
-     * 
+     *
      * @param jobIds 需要删除的任务ID
      * @return 结果
      */
@@ -147,7 +147,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 任务调度状态修改
-     * 
+     *
      * @param job 调度信息
      */
     @Override
@@ -169,7 +169,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 立即运行任务
-     * 
+     *
      * @param job 调度信息
      */
     @Override
@@ -187,7 +187,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 新增任务
-     * 
+     *
      * @param job 调度信息 调度信息
      */
     @Override
@@ -205,7 +205,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 更新任务的时间表达式
-     * 
+     *
      * @param job 调度信息
      */
     @Override
@@ -223,7 +223,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 更新任务
-     * 
+     *
      * @param job 任务对象
      * @param jobGroup 任务组名
      */
@@ -242,7 +242,7 @@ public class SysJobServiceImpl implements ISysJobService
 
     /**
      * 校验cron表达式是否有效
-     * 
+     *
      * @param cronExpression 表达式
      * @return 结果
      */

+ 71 - 0
fs-service/src/main/java/com/fs/his/domain/PrescriptionTaskRecord.java

@@ -0,0 +1,71 @@
+package com.fs.his.domain;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+* 处方单生成定时任务中间表
+*/
+@Data
+public class PrescriptionTaskRecord {
+
+   /**
+    * 主键ID
+    */
+   private Long id;
+
+   /**
+    * 处方单号
+    */
+   private Long prescribeId;
+
+   /**
+    * 处方URL
+    */
+   private String prescribeUrl;
+
+   /**
+    * 当前重试次数
+    */
+   private Integer retryCount;
+
+   /**
+    * 执行状态:0-待执行,1-执行中,2-成功,3-失败
+    */
+   private Integer executeStatus;
+
+   /**
+    * 失败消息
+    */
+   private String errorMessage;
+
+   /**
+    * 创建时间
+    */
+   private LocalDateTime createTime;
+
+   /**
+    * 更新时间
+    */
+   private LocalDateTime updateTime;
+
+   /**
+    * 首次执行时间
+    */
+   private LocalDateTime firstExecuteTime;
+
+   /**
+    * 最后执行时间
+    */
+   private LocalDateTime lastExecuteTime;
+
+   /**
+    * 成功时间
+    */
+   private LocalDateTime successTime;
+
+   /**
+    * 备注信息
+    */
+   private String remark;
+}

+ 100 - 0
fs-service/src/main/java/com/fs/his/mapper/PrescriptionTaskRecordMapper.java

@@ -0,0 +1,100 @@
+package com.fs.his.mapper;
+
+import com.fs.his.domain.PrescriptionTaskRecord;
+import org.apache.ibatis.annotations.*;
+import java.util.List;
+
+/**
+ * 处方单生成定时任务中间表Mapper
+ */
+@Mapper
+public interface PrescriptionTaskRecordMapper {
+
+    /**
+     * 插入记录
+     */
+    @Insert("<script>" +
+            "INSERT INTO prescription_task_record " +
+            "(prescribe_id, prescribe_url, retry_count, execute_status, error_message, " +
+            "first_execute_time, last_execute_time, success_time, remark) " +
+            "VALUES (#{prescribeId}, #{prescribeUrl}, #{retryCount}, #{executeStatus}, #{errorMessage}, " +
+            "#{firstExecuteTime}, #{lastExecuteTime}, #{successTime}, #{remark})" +
+            "</script>")
+    @Options(useGeneratedKeys = true, keyProperty = "id")
+    int insert(PrescriptionTaskRecord record);
+
+    /**
+     * 根据ID更新
+     */
+    @Update("<script>" +
+            "UPDATE prescription_task_record " +
+            "<set>" +
+            "<if test='prescribeUrl != null'>prescribe_url = #{prescribeUrl},</if>" +
+            "<if test='retryCount != null'>retry_count = #{retryCount},</if>" +
+            "<if test='executeStatus != null'>execute_status = #{executeStatus},</if>" +
+            "<if test='errorMessage != null'>error_message = #{errorMessage},</if>" +
+            "<if test='firstExecuteTime != null'>first_execute_time = #{firstExecuteTime},</if>" +
+            "<if test='lastExecuteTime != null'>last_execute_time = #{lastExecuteTime},</if>" +
+            "<if test='successTime != null'>success_time = #{successTime},</if>" +
+            "<if test='remark != null'>remark = #{remark},</if>" +
+            "</set>" +
+            "WHERE id = #{id}" +
+            "</script>")
+    int updateById(PrescriptionTaskRecord record);
+
+    /**
+     * 根据处方单号更新
+     */
+    @Update("<script>" +
+            "UPDATE prescription_task_record " +
+            "<set>" +
+            "<if test='prescribeUrl != null'>prescribe_url = #{prescribeUrl},</if>" +
+            "<if test='retryCount != null'>retry_count = #{retryCount},</if>" +
+            "<if test='executeStatus != null'>execute_status = #{executeStatus},</if>" +
+            "<if test='errorMessage != null'>error_message = #{errorMessage},</if>" +
+            "<if test='firstExecuteTime != null'>first_execute_time = #{firstExecuteTime},</if>" +
+            "<if test='lastExecuteTime != null'>last_execute_time = #{lastExecuteTime},</if>" +
+            "<if test='successTime != null'>success_time = #{successTime},</if>" +
+            "<if test='remark != null'>remark = #{remark},</if>" +
+            "</set>" +
+            "WHERE prescribe_id = #{prescribeId}" +
+            "</script>")
+    int updateByPrescribeId(PrescriptionTaskRecord record);
+
+    /**
+     * 根据ID查询
+     */
+    @Select("SELECT * FROM prescription_task_record WHERE id = #{id}")
+    PrescriptionTaskRecord selectById(Long id);
+
+    /**
+     * 根据处方单号查询
+     */
+    @Select("SELECT * FROM prescription_task_record WHERE prescribe_id = #{prescribeId}")
+    PrescriptionTaskRecord selectByPrescribeId(String prescribeId);
+
+    /**
+     * 根据执行状态查询
+     */
+    @Select("SELECT * FROM prescription_task_record WHERE execute_status = #{executeStatus}")
+    List<PrescriptionTaskRecord> selectByExecuteStatus(Integer executeStatus);
+
+    /**
+     * 查询待重试的任务
+     */
+    @Select("<script>" +
+            "SELECT * FROM prescription_task_record " +
+            "WHERE execute_status = #{executeStatus} " +
+            "<if test='maxRetryCount != null'>" +
+            "AND retry_count &lt; #{maxRetryCount} " +
+            "</if>" +
+            "ORDER BY create_time ASC" +
+            "</script>")
+    List<PrescriptionTaskRecord> selectRetryTasks(@Param("executeStatus") Integer executeStatus,
+                                                   @Param("maxRetryCount") Integer maxRetryCount);
+
+
+
+    @Select("select * from prescription_task_record where retry_count< 3 and execute_status in (0,3) limit 10")
+    List<PrescriptionTaskRecord> selectPendingData();
+}

+ 7 - 0
fs-service/src/main/java/com/fs/his/param/PrescribeXyImgParam.java

@@ -23,6 +23,7 @@ public class PrescribeXyImgParam {
     /** 患者姓名 */
     @Excel(name = "患者姓名")
     private String patientName;
+    private String telPhone;
 
     /** 患者性别*/
     @Excel(name = "患者性别(传数字,1男 2⼥)")
@@ -58,8 +59,14 @@ public class PrescribeXyImgParam {
     /** 发药 */
     private String checkDoctor;
 
+    /**
+     * 医生签名
+     */
     private String url;
 
+    /**
+     * 药师签名
+     */
     private String drugDoctorUrl;
 
     @Excel(name = "医嘱")

+ 4 - 0
fs-service/src/main/java/com/fs/his/service/IFsPrescribeService.java

@@ -85,6 +85,7 @@ public interface IFsPrescribeService
     R audit(FsPrescribeAuditParam param);
 
      String PrescribeImg(Long prescribeId);
+     String PrescribeImgYsy(Long prescribeId);
 
     Long insertFsPrescribeByPackageOrder(FsPackageOrder packageOrder);
 
@@ -107,4 +108,7 @@ public interface IFsPrescribeService
     void PrescribeStoreImg(Long id);
 
     List<FsPrescribeListDVO> selectFsPrescribeListDVOByCompanyUser(FsPrescribeListDCompanyParam param);
+
+    void confirmPrescribe(FsPrescribeParam param);
+
 }

+ 17 - 0
fs-service/src/main/java/com/fs/his/service/PrescriptionImageService.java

@@ -0,0 +1,17 @@
+package com.fs.his.service;
+
+import com.fs.his.param.PrescribeXyImgParam;
+import org.springframework.stereotype.Service;
+
+@Service
+public interface PrescriptionImageService {
+
+
+    /**
+     * 生成处方图片并上传OSS
+     * @param param 处方数据
+     * @return OSS图片URL
+     */
+    public String generatePrescriptionImage(PrescribeXyImgParam param);
+
+}

+ 5 - 0
fs-service/src/main/java/com/fs/his/service/PrescriptionTaskRecordService.java

@@ -0,0 +1,5 @@
+package com.fs.his.service;
+
+public interface PrescriptionTaskRecordService {
+    public void generatePrescript();
+}

+ 92 - 15
fs-service/src/main/java/com/fs/his/service/impl/FsPrescribeServiceImpl.java

@@ -31,11 +31,16 @@ import com.fs.im.service.IImService;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 import com.google.zxing.WriterException;
+import org.apache.http.util.Asserts;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 import javax.imageio.ImageIO;
 import java.awt.*;
@@ -45,6 +50,7 @@ import java.lang.reflect.Field;
 import java.math.BigDecimal;
 import java.net.URL;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.List;
 
@@ -57,6 +63,7 @@ import java.util.List;
 @Service
 public class FsPrescribeServiceImpl implements IFsPrescribeService
 {
+    private static final Logger log = LoggerFactory.getLogger(FsPrescribeServiceImpl.class);
     @Autowired
     private FsPrescribeMapper fsPrescribeMapper;
     @Autowired
@@ -64,22 +71,24 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
     @Autowired
     private FsInquiryOrderMapper fsInquiryOrderMapper;
     @Autowired
-    IFsDoctorService doctorService;
+    private IFsDoctorService doctorService;
     @Autowired
-    FsDoctorMapper doctorMapper;
+    private FsDoctorMapper doctorMapper;
     @Lazy
     @Autowired
-    IFsStoreOrderService storeOrderService;
+    private IFsStoreOrderService storeOrderService;
     @Autowired
     private IFsPrescribeDrugService prescribeDrugService;
     @Autowired
-    IImService imService;
+    private IImService imService;
     @Autowired
-    FsPackageMapper fsPackageMapper;
+    private FsPackageMapper fsPackageMapper;
     @Autowired
-    ConfigUtil configUtil;
+    private ConfigUtil configUtil;
     @Autowired
-    FsExportTaskMapper fsExportTaskMapper;
+    private FsExportTaskMapper fsExportTaskMapper;
+    @Autowired
+    private PrescriptionTaskRecordMapper prescriptionTaskRecordMapper;
     /**
      * 查询处方
      *
@@ -394,14 +403,6 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
             FsPrescribeDrug d = new FsPrescribeDrug();
             d.setPrescribeId(f.getPrescribeId());
             List<FsPrescribeDrug> list = fsPrescribeDrugMapper.selectFsPrescribeDrugList(d);
-//            List<FsPrescribeDrug> list =new ArrayList<>();
-//            for (FsPrescribeDrug fsPrescribeDrug : FsPrescribeDruglist) {
-//                if (fsPrescribeDrug.getIsDrug()!=null&&fsPrescribeDrug.getIsDrug()!=0){
-//                    list.add(fsPrescribeDrug);
-//                }else if (fsPrescribeDrug.getIsDrug()==null){
-//                    list.add(fsPrescribeDrug);
-//                }
-//            }
             if (list.size()==0){
                 return "";
             }
@@ -486,6 +487,66 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
         return f.getPrescribeImgUrl();
     }
 
+    @Override
+    public String PrescribeImgYsy(Long prescribeId) {
+        FsPrescribeVO f = fsPrescribeMapper.selectFsPrescribeByPrescribeIdVO(prescribeId);
+        Asserts.notNull(f,String.format("处方 %d 未找到!",prescribeId));
+
+        FsStoreOrder fsStoreOrder = storeOrderService.selectFsStoreOrderByOrderId(f.getStoreOrderId());
+        FsInquiryOrderVO order = fsInquiryOrderMapper.selectFsInquiryOrderVOByOrderId(f.getInquiryOrderId());
+        if (StringUtils.isBlank(f.getPrescribeImgUrl())) {
+            FsPrescribeDrug d = new FsPrescribeDrug();
+            d.setPrescribeId(f.getPrescribeId());
+            List<FsPrescribeDrug> list = fsPrescribeDrugMapper.selectFsPrescribeDrugList(d);
+
+            if (CollectionUtils.isEmpty(list)) {
+                throw new IllegalArgumentException(String.format("处方单 %d 对应药品为空!",prescribeId));
+            }
+            PrescribeXyImgParam o = new PrescribeXyImgParam();
+            o.setTotalPrice(fsStoreOrder.getPayPrice());
+            o.setTime(f.getCreateTime());
+            o.setPrescribeId(f.getPrescribeCode());
+            o.setPatientName(f.getPatientName());
+            o.setPatientGender(f.getPatientGender());
+            o.setPatientAge(f.getPatientAge());
+            o.setOutpatientId(f.getPrescribeCode());
+            o.setUrl(f.getPrescribeDoctorSignUrl());
+            o.setDrugDoctorUrl(f.getDrugDoctorSignUrl());
+            if (order != null) {
+                o.setBedId(order.getDeptName());
+            } else {
+                o.setBedId("中医科");
+            }
+            String remark = "";
+            if (f.getUsageJson() != null) {
+                FsPrescribeUsageDTO usage = JSONUtil.toBean(f.getUsageJson(), FsPrescribeUsageDTO.class);
+                remark = usage.getRemark();
+            }
+            if (remark != null && remark != "") {
+                o.setRemark(remark);
+            } else {
+                o.setRemark("请按照用药说明书服用药品,如有不适,请及时就医!");
+            }
+            o.setHistoryAllergic(f.getHistoryAllergic());
+            o.setDiagnose(f.getDiagnose());
+            // 在这里添加处方药品列表的值
+            o.setPrescribeDrug(list);
+            o.setDoctorName(f.getDoctorName());
+            o.setAuditDoctor(f.getDoctorDrugName());
+            o.setDispatcher("");
+            o.setCheckDoctor("");
+            o.setDoctorName(f.getDoctorName());
+            String s = getFsPrescribeXyImg(o);
+            FsPrescribe fsPrescribe = new FsPrescribe();
+            fsPrescribe.setPrescribeImgUrl(s);
+            fsPrescribe.setPrescribeId(f.getPrescribeId());
+            fsPrescribeMapper.updateFsPrescribe(fsPrescribe);
+        } else {
+            log.info("处方 {} 对应处方单已经被生成!",prescribeId);
+        }
+        return f.getPrescribeImgUrl();
+    }
+
     @Override
     public Long insertFsPrescribeByPackageOrder(FsPackageOrder packageOrder) {
         FsPackage fsPackage=fsPackageMapper.selectFsPackageByPackageId(packageOrder.getPackageId());
@@ -970,4 +1031,20 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
 
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
+    public void confirmPrescribe(FsPrescribeParam param) {
+        FsPrescribe fsPrescribe = fsPrescribeMapper.selectFsPrescribeByPrescribeId(param.getPrescribeId());
+        fsPrescribe.setDoctorConfirm(1);
+        fsPrescribeMapper.updateFsPrescribe(fsPrescribe);
+
+        // 医生确认后生成处方单
+        PrescriptionTaskRecord record = new PrescriptionTaskRecord();
+        record.setPrescribeId(param.getPrescribeId());
+        record.setCreateTime(LocalDateTime.now());
+        record.setExecuteStatus(0);
+        record.setRetryCount(0);
+        prescriptionTaskRecordMapper.insert(record);
+    }
+
 }

+ 390 - 0
fs-service/src/main/java/com/fs/his/service/impl/PrescriptionImageServiceImpl.java

@@ -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;
+    }
+}

+ 56 - 0
fs-service/src/main/java/com/fs/his/service/impl/PrescriptionTaskRecordServiceImpl.java

@@ -0,0 +1,56 @@
+package com.fs.his.service.impl;
+
+import com.fs.his.domain.FsPrescribe;
+import com.fs.his.domain.PrescriptionTaskRecord;
+import com.fs.his.mapper.PrescriptionTaskRecordMapper;
+import com.fs.his.service.IFsPrescribeService;
+import com.fs.his.service.PrescriptionTaskRecordService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+
+@Slf4j
+@Service
+public class PrescriptionTaskRecordServiceImpl implements PrescriptionTaskRecordService {
+
+    @Autowired
+    private PrescriptionTaskRecordMapper prescriptionTaskRecordMapper;
+
+    @Autowired
+    private IFsPrescribeService fsPrescribeService;
+
+    /**
+     * 生成处方签名
+     */
+    @Override
+    public void generatePrescript() {
+        List<PrescriptionTaskRecord> prescriptionTaskRecords = prescriptionTaskRecordMapper.selectPendingData();
+        for (PrescriptionTaskRecord record : prescriptionTaskRecords) {
+            try{
+                FsPrescribe fsPrescribe = fsPrescribeService.selectFsPrescribeByPrescribeId(record.getPrescribeId());
+                if(fsPrescribe == null) {
+                    throw new IllegalArgumentException(String.format("处方 %d 没有找到!",record.getPrescribeId()));
+                }
+                String prescribeImgUrl = fsPrescribeService.PrescribeImgYsy(record.getPrescribeId());
+                fsPrescribe.setPrescribeImgUrl(prescribeImgUrl);
+                fsPrescribeService.updateFsPrescribe(fsPrescribe);
+
+                record.setPrescribeUrl(prescribeImgUrl);
+                record.setExecuteStatus(2);
+            }catch (Exception e) {
+                log.error("生成处方 {} 失败!",record.getPrescribeId(),e);
+                record.setExecuteStatus(3);
+                record.setRetryCount(record.getRetryCount()+1);
+                record.setUpdateTime(LocalDateTime.now());
+                record.setErrorMessage(ExceptionUtils.getFullStackTrace(e));
+            }finally {
+                prescriptionTaskRecordMapper.updateById(record);
+            }
+        }
+    }
+}