已修改17个文件
408 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/PatArchive.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceSubtask.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceSubtaskExport.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/ServiceSubtaskRes.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/VO/ServiceSubtaskVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/domain/entity/ServiceSubtaskEntity.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/IPatMedInhospService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/service/impl/ServiceTaskServiceImpl.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
@@ -908,6 +908,17 @@
    }
    /**
     * 手动发送问卷模板的微信功能
     *
     * @param subid
     * @return
     */
    @GetMapping(value = "/weChatSubTask/{subid}")
    public Map<String, Object> weChatSubTask(@PathVariable("subid") Long subid) {
        return serviceSubtaskService.weChatSubTask(subid);
    }
    /**
     * 跳转中间页
     *
     * @param taskId
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
@@ -4,7 +4,9 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
@@ -14,6 +16,7 @@
 *
 * @author ruoyi
 **/
@Slf4j
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisCache {
@@ -289,9 +292,25 @@
     * @return Long 大于0表示删除了几个,等于0表示没有删除,小于0表示在执行删除操作时发生了错误
     */
    public Long removeElementFromList(String key, String valueToRemove) {
        ListOperations<String, String> listOps = redisTemplate.opsForList();
        Long remove = listOps.remove(key, 0, valueToRemove);
        return remove;
        // 先判断 key 的类型,避免 key 被其它业务/旧版本代码写成了 String、Hash 等非 List 类型时抛 WRONGTYPE
        DataType keyType = redisTemplate.type(key);
        if (keyType == DataType.NONE) {
            return 0L;
        }
        if (keyType != DataType.LIST) {
            // 该 key 本身是作为队列使用的,类型不对说明是脏数据,直接删除,等下次写入时重新创建为 List
            log.warn("Redis key [{}] 的类型为 {},不是 List,已删除该脏数据,避免 WRONGTYPE 异常", key, keyType);
            redisTemplate.delete(key);
            return 0L;
        }
        try {
            Long remove = redisTemplate.opsForList().remove(key, 0, valueToRemove);
            return remove == null ? 0L : remove;
        } catch (DataAccessException e) {
            // 类型检查和删除之间 key 被其它应用改写为其它类型等极端情况兜底,缓存清理失败不应影响主业务流程
            log.warn("从 Redis List [{}] 移除元素 [{}] 失败:{}", key, valueToRemove, e.getMessage());
            return 0L;
        }
    }
    /**
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -1884,6 +1884,22 @@
        st.setNexttaskflag("1");
        st.setOrgid(orgid);
        List<ServiceTask> serviceTasks = serviceTaskMapper.selectServiceTaskList(st);
        // 随访中心科室
        String centerDept = configService.selectConfigByKey("sys.smartor.center.dept", orgid);
        SysDept sysDeptVo = new SysDept();
        SysDept centerSysDept = null;
        if(StringUtils.isNotEmpty(centerDept)){
            sysDeptVo.setDeptType("1");
            sysDeptVo.setOrgid(orgid);
            sysDeptVo.setDeptCode(centerDept);
            List<SysDept> sysDepts = sysDeptService.selectDeptList(sysDeptVo);
            if (CollectionUtils.isNotEmpty(sysDepts)) {
                centerSysDept = sysDepts.get(0);
            }
        }
        String config = configService.selectConfigByKey("visit.early.day");
        for (ServiceTask serviceTask : serviceTasks) {
            deadlineDay = -deadlineDay;
            Date deadline = DateUtils.addDays(new Date(), deadlineDay);
@@ -1895,6 +1911,7 @@
            serviceSubtaskVO.setVisitCount(1);
            serviceSubtaskVO.setTaskid(serviceTask.getTaskid());
            serviceSubtaskVO.setEndOutHospTime(deadline);
            List<ServiceSubtask> serviceSubtaskList
                    = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskVO);
            log.info("【dealSubtaskDeadline】获取到{}个超时任务, taskid={}", serviceSubtaskList.size(), serviceTask.getTaskid());
@@ -1915,7 +1932,6 @@
                }
                ServiceTask nextServiceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceTask.getNexttaskid());
                PatArchive patArchive = patArchiveService.selectPatArchiveByPatid(serviceSubtask.getPatid());
                String config = configService.selectConfigByKey("visit.early.day");
                Integer type = 1;
                //新增再次随访任务
@@ -1928,8 +1944,13 @@
                serviceSubtaskAgain.setCreateTime(new Date());
                serviceSubtaskAgain.setUpdateTime(new Date());
                serviceSubtaskAgain.setVisitType(serviceSubtask.getVisitType());
                serviceSubtaskAgain.setVisitDeptCode(serviceSubtask.getVisitDeptCode());
                serviceSubtaskAgain.setVisitDeptName(serviceSubtask.getVisitDeptName());
                if(ObjectUtils.isNotEmpty(centerSysDept)){
                    serviceSubtaskAgain.setVisitDeptCode(centerSysDept.getDeptCode());
                    serviceSubtaskAgain.setVisitDeptName(centerSysDept.getDeptName());
                }else {
                    serviceSubtaskAgain.setVisitDeptCode(serviceSubtask.getVisitDeptCode());
                    serviceSubtaskAgain.setVisitDeptName(serviceSubtask.getVisitDeptName());
                }
                int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain);
                if (i == 1) {
                    //再次随访任务创建成功后,原任务改为失败状态
smartor/src/main/java/com/smartor/domain/PatArchive.java
@@ -436,4 +436,17 @@
    @ApiModelProperty(value = "最后结束时间")
    private String lastEndTime;
    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date createTimeStart;
    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date createTimeEnd;
}
smartor/src/main/java/com/smartor/domain/ServiceSubtask.java
@@ -185,6 +185,13 @@
    private String suggest;
    /**
     * 医生处理意见
     */
    @Excel(name = "医生处理意见")
    @ApiModelProperty(value = "医生处理意见")
    private String doctorOpinion;
    /**
     * 发送日期
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
smartor/src/main/java/com/smartor/domain/ServiceSubtaskExport.java
@@ -200,5 +200,12 @@
    @ApiModelProperty(value = "备注")
    private String remark;
    /**
     * 医生处理意见
     */
    @Excel(name = "医生处理意见")
    @ApiModelProperty(value = "医生处理意见")
    private String doctorOpinion;
}
smartor/src/main/java/com/smartor/domain/ServiceSubtaskRes.java
@@ -60,6 +60,12 @@
    private String remark;
    /**
     * 医生处理意见
     */
    @ApiModelProperty(value = "医生处理意见")
    private String doctorOpinion;
    /**
     * 备注
     */
    @ApiModelProperty(value = "")
smartor/src/main/java/com/smartor/domain/VO/ServiceSubtaskVO.java
@@ -638,6 +638,12 @@
    @ApiModelProperty(value = "备注")
    private String remark;
    /**
     * 医生处理意见
     */
    @ApiModelProperty(value = "医生处理意见")
    private String doctorOpinion;
    /**
     * 出院时间
smartor/src/main/java/com/smartor/domain/entity/ServiceSubtaskEntity.java
@@ -657,6 +657,12 @@
    @ApiModelProperty(value = "备注")
    private String remark;
    /**
     * 医生处理意见
     */
    @ApiModelProperty(value = "医生处理意见")
    private String doctorOpinion;
    /**
     * 出院时间
smartor/src/main/java/com/smartor/mapper/ServiceSubtaskMapper.java
@@ -323,4 +323,14 @@
     */
    List<MzMydScoreRankDTO> selectMzMydScoreRank(MzMydScoreRankVO mzMydScoreRankVO);
    /**
     * 条件更新原任务的“是否再次随访”标识,用于二次随访幂等控制。
     * 仅当当前值不是目标值时才更新,返回受影响行数。
     *
     * @param id           子任务 ID
     * @param isVisitAgain 目标值(2-已再次随访)
     * @return 受影响行数,1 表示抢到生成权,0 表示已被其他请求处理
     */
    int updateIsVisitAgainById(@Param("id") Long id, @Param("isVisitAgain") Integer isVisitAgain);
}
smartor/src/main/java/com/smartor/service/IPatMedInhospService.java
@@ -128,4 +128,13 @@
    public void addOperationSubTask(String config);
    public ServiceSubtask boxedServiceSubtask(ServiceTask serviceTask, PatMedInhosp patMedInhosp1, PatArchive patArchive, String config, Integer type);
    /**
     * 按任务配置的发送方式(preachform_desc)初始化子任务发送方式明细。
     *
     * @param serviceSubtask 子任务
     * @param sendstate      发送方式明细初始状态,为空时默认 1
     * @return 处理条数
     */
    public Integer addServiceSubtaskPreachform(ServiceSubtask serviceSubtask, String sendstate);
}
smartor/src/main/java/com/smartor/service/IServiceSubtaskService.java
@@ -229,6 +229,8 @@
    Map<String, Object> smsSubTask(Long subid);
    Map<String, Object> weChatSubTask(Long subid);
    /**
     * 统计问题的选项的百分比 (用于服务类型 2)
     *
smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java
@@ -1012,7 +1012,8 @@
        }
    }
    private Integer addServiceSubtaskPreachform(ServiceSubtask serviceSubtask, String sendstate) {
    @Override
    public Integer addServiceSubtaskPreachform(ServiceSubtask serviceSubtask, String sendstate) {
        //将preachformDesc的json对象,放到list集合中
        ServiceTask serviceTask = serviceTaskMapper.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
        if (Objects.isNull(serviceTask)) {
smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -18,7 +18,9 @@
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysDeptService;
import com.smartor.common.FtpService;
import com.smartor.common.MtSubmitSmUtil;
import com.smartor.common.ScheduleSubtaskBuilder;
@@ -137,6 +139,9 @@
    private IPatArchiveService patArchiveService;
    @Autowired
    private ISysDeptService sysDeptService;
    @Autowired
    private ScheduleSubtaskBuilder scheduleSubtaskBuilder;
    @Autowired
@@ -216,8 +221,14 @@
    private ServiceOutPathMapper serviceOutPathMapper;
    private static final String KEY_SEP = "\u0001";
    /** 二次随访生成锁前缀,防止同一原任务并发生成多条二次随访 */
    private static final String AGAIN_LOCK_KEY_PREFIX = "smartor:again:lock:";
    private static final long AGAIN_LOCK_SECONDS = 30L;
    @Autowired
    private PatMedOperationItemMapper patMedOperationItemMapper;
    @Autowired
    private SysDeptMapper sysDeptMapper;
    /**
     * 查询单一任务(随访)
@@ -782,6 +793,7 @@
     * @return 结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String addSubTaskAgain(ServiceSubtask serviceSubtask) {
        ServiceSubtask serviceSubtask1 = selectServiceSubtaskById(serviceSubtask.getId());
        PatMedInhosp patMedInhosp = new PatMedInhosp();
@@ -791,7 +803,7 @@
        PatArchive patArchive = patArchiveMapper.selectPatArchiveByPatid(serviceSubtask1.getPatid());
        //景宁 新增再次随访不能触发患者再入院
        boolean inhospAgain = true;
        if (serviceSubtask1.getOrgid().equals("47255004333112711A1001")) {
        if ("47255004333112711A1001".equals(serviceSubtask1.getOrgid())) {
            inhospAgain = false;
        }
        if (patMedInhospList.size() > 0 && inhospAgain) {
@@ -805,13 +817,17 @@
            serviceSubtask1.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";无需再次随访,任务被标记不在执行" : "无需再次随访,任务被标记不在执行");
            serviceSubtaskMapper.updateServiceSubtask(serviceSubtask1);
            return "无需再次随访,任务被标记不在执行";
        } else if (StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) {
        } else if (patArchive != null && StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) {
            serviceSubtask1.setIsVisitAgain(2);
            serviceSubtaskMapper.updateServiceSubtask(serviceSubtask1);
            serviceSubtask1.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";无需再次随访,患者被标记不需要被服务" : "无需再次随访,患者被标记不需要被服务");
            return "无需再次随访,患者被标记不需要被服务";
        } else if (ObjectUtils.isNotEmpty(serviceSubtask1.getIsVisitAgain()) && serviceSubtask1.getIsVisitAgain() == 2) {
            return "无需再次随访,该任务已经再次随访过了";
        }
        //丽水中医院根据下次任务ID生成再次随访
        if ("47231022633110211A2101".equals(serviceSubtask1.getOrgid())) {
            return addSubTaskAgainNextTaskId(serviceSubtask1);
        }
        //新增再次随访任务
        ServiceSubtask serviceSubtask2 = DtoConversionUtils.sourceToTarget(serviceSubtask, ServiceSubtask.class);
@@ -832,6 +848,155 @@
            return "再次随访任务创建成功";
        }
        return "再次随访任务创建失败,再请次重试";
    }
    public String addSubTaskAgainNextTaskId(ServiceSubtask serviceSubtask) {
        if (ObjectUtils.isEmpty(serviceSubtask.getTaskid())) {
            log.error("再次随访任务创建失败,缺少TaskId, 再请次重试");
            return "再次随访任务创建失败,缺少TaskId, 再请次重试";
        }
        // 同一原任务串行生成,防止并发重复插入
        String lockToken = tryLockAgainTask(serviceSubtask.getId());
        if (lockToken == null) {
            return "再次随访任务正在创建中,请勿重复提交";
        }
        try {
            ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
            if (ObjectUtils.isEmpty(serviceTask)) {
                log.error("再次随访任务创建失败,找不到对应任务 taskid = {}, 再请次重试", serviceSubtask.getTaskid());
                return "再次随访任务创建失败,找不到对应任务 taskid = " + serviceSubtask.getTaskid() + ", 再请次重试";
            }
            // 幂等校验:同一原任务只允许生成一条二次随访
            ServiceSubtaskEntity againQuery = new ServiceSubtaskEntity();
            againQuery.setUpid(serviceSubtask.getId().intValue());
            if (CollectionUtils.isNotEmpty(serviceSubtaskMapper.selectServiceSubtaskList(againQuery))) {
                return "无需再次随访,该任务已经再次随访过了";
            }
            // 查询出院记录
            PatMedInhosp patMedInhospVo = new PatMedInhosp();
            patMedInhospVo.setOrgid(serviceSubtask.getOrgid());
            patMedInhospVo.setInhospid(serviceSubtask.getInhospid());
            patMedInhospVo.setCry(1);
            List<PatMedInhosp> patMedInhospList = patMedInhospService.selectPatMedInhospList(patMedInhospVo);
            PatMedInhosp patMedInhosp = new PatMedInhosp();
            if (CollectionUtils.isNotEmpty(patMedInhospList)) {
                patMedInhosp = patMedInhospList.get(0);
            } else {
                log.error("再次随访任务创建失败,任务缺少 对应的出院记录, taskid = {}, 再请次重试", serviceSubtask.getTaskid());
                return "再次随访任务创建失败,任务缺少 对应的出院记录, taskid = " + serviceSubtask.getTaskid() + ", 再请次重试";
            }
            if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) {
                log.error("再次随访任务创建失败,任务缺少 nexttaskid, taskid = {}, 再请次重试", serviceSubtask.getTaskid());
                return "再次随访任务创建失败,任务缺少 nexttaskid, taskid = " + serviceSubtask.getTaskid() + ", 再请次重试";
            }
            ServiceTask nextServiceTask = serviceTaskService.selectServiceTaskByTaskid(serviceTask.getNexttaskid());
            if (ObjectUtils.isEmpty(nextServiceTask)) {
                log.error("再次随访任务创建失败,找不到对应任务 nexttaskid = {}, 再请次重试", serviceTask.getNexttaskid());
                return "再次随访任务创建失败,找不到对应任务 nexttaskid = " + serviceTask.getNexttaskid() + ", 再请次重试";
            }
            PatArchive patArchive = patArchiveService.selectPatArchiveByPatid(serviceSubtask.getPatid());
            if (ObjectUtils.isEmpty(patArchive)) {
                log.error("再次随访任务创建失败,未找到患者档案 patid = {}, subTaskId = {}", serviceSubtask.getPatid(), serviceSubtask.getId());
                return "再次随访任务创建失败,未找到患者档案, subTaskId = " + serviceSubtask.getId() + ", 再请次重试";
            }
            String config = configService.selectConfigByKey("visit.early.day");
            Integer type = 1;
            // 随访中心科室
            String centerDept = configService.selectConfigByKey("sys.smartor.center.dept", serviceSubtask.getOrgid());
            if (ObjectUtils.isEmpty(centerDept)) {
                log.error("再次随访任务创建失败,未配置随访中心科室参数 sys.smartor.center.dept ,subTaskId = {}, 再请次重试", serviceSubtask.getId());
                return "再次随访任务创建失败,未配置随访中心科室参数 sys.smartor.center.dept ,subTaskId = " + serviceSubtask.getId() + ", 再请次重试";
            }
            SysDept sysDeptVo = new SysDept();
            sysDeptVo.setDeptType("1");
            sysDeptVo.setOrgid(serviceSubtask.getOrgid());
            sysDeptVo.setDeptCode(centerDept);
            List<SysDept> sysDepts = sysDeptService.selectDeptList(sysDeptVo);
            SysDept sysDept = new SysDept();
            if (CollectionUtils.isNotEmpty(sysDepts)) {
                sysDept = sysDepts.get(0);
            } else {
                log.error("再次随访任务创建失败,未找到参数 sys.smartor.center.dept 配置的随访中心科室 deptCode = {}, 再请次重试", centerDept);
                return "再次随访任务创建失败,未找到参数 sys.smartor.center.dept 配置的随访中心科室 deptCode = " + centerDept + ", 再请次重试";
            }
            // 按下一任务生成二次随访子任务(表单、发送方式取自下一任务)
            ServiceSubtask serviceSubtaskAgain = patMedInhospService.boxedServiceSubtask(nextServiceTask,
                    patMedInhosp, patArchive, config, type);
            if (ObjectUtils.isEmpty(serviceSubtaskAgain) || ObjectUtils.isEmpty(serviceSubtaskAgain.getTaskid())) {
                log.error("再次随访任务创建失败,下一任务生成子任务为空, subTaskId = {}, nextTaskId = {}", serviceSubtask.getId(), serviceTask.getNexttaskid());
                return "再次随访任务创建失败,患者出院记录不完整或下一任务配置异常, 再请次重试";
            }
            Date now = new Date();
            serviceSubtaskAgain.setId(null);
            serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue());
            serviceSubtaskAgain.setVisitCount(ObjectUtils.isNotEmpty(serviceSubtask.getVisitCount())
                    ? serviceSubtask.getVisitCount() + 1 : 1);
            serviceSubtaskAgain.setSendstate(2L);
            serviceSubtaskAgain.setIsVisitAgain(1);
            serviceSubtaskAgain.setCreateTime(now);
            serviceSubtaskAgain.setUpdateTime(now);
//            // 二次随访时间:上次随访完成时间(未完成则当前时间)+ 下一任务发送天数;过去时间兜底为当前时间
//            Date baseTime = ObjectUtils.isNotEmpty(serviceSubtask.getFinishtime()) ? serviceSubtask.getFinishtime() : now;
//            int intervalDays = ObjectUtils.isNotEmpty(nextServiceTask.getSendDay())
//                    ? nextServiceTask.getSendDay().intValue() : 0;
//            Date nextVisitTime = DateUtils.addDays(baseTime, intervalDays);
//            if (nextVisitTime.before(now)) {
//                nextVisitTime = now;
//            }
//            serviceSubtaskAgain.setVisitTime(nextVisitTime);
//            serviceSubtaskAgain.setLongSendTime(nextVisitTime);
            serviceSubtaskAgain.setVisitType(serviceSubtask.getVisitType());
            serviceSubtaskAgain.setVisitDeptCode(sysDept.getDeptCode());
            serviceSubtaskAgain.setVisitDeptName(sysDept.getDeptName());
            int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain);
            if (i != 1) {
                return "再次随访任务创建失败,再请次重试";
            }
            // 初始化发送方式明细,补偿(compensateTask)和超时判断依赖该表
            patMedInhospService.addServiceSubtaskPreachform(serviceSubtaskAgain, null);
            // 条件更新原任务:0 行说明已被其他请求生成,抛异常回滚本次插入的重复数据
            int updated = serviceSubtaskMapper.updateIsVisitAgainById(serviceSubtask.getId(), 2);
            if (updated != 1) {
                throw new BaseException("无需再次随访,该任务已经再次随访过了");
            }
            return "再次随访任务创建成功";
        } finally {
            unlockAgainTask(serviceSubtask.getId(), lockToken);
        }
    }
    /**
     * 获取二次随访生成锁。Redis 异常时降级为数据库幂等校验,不阻断业务。
     */
    private String tryLockAgainTask(Long subId) {
        String token = UUID.randomUUID().toString();
        try {
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(
                    AGAIN_LOCK_KEY_PREFIX + subId, token, AGAIN_LOCK_SECONDS, TimeUnit.SECONDS);
            return Boolean.TRUE.equals(locked) ? token : null;
        } catch (Exception e) {
            log.warn("再次随访获取Redis锁失败,降级使用数据库幂等校验, subId={}", subId, e);
            return token;
        }
    }
    /**
     * 释放二次随访生成锁,仅释放自己持有的锁。
     */
    private void unlockAgainTask(Long subId, String token) {
        if (token == null) {
            return;
        }
        try {
            String lockKey = AGAIN_LOCK_KEY_PREFIX + subId;
            Object current = redisTemplate.opsForValue().get(lockKey);
            if (token.equals(current)) {
                redisTemplate.delete(lockKey);
            }
        } catch (Exception e) {
            log.warn("再次随访释放Redis锁失败, subId={}", subId, e);
        }
    }
    /**
@@ -5168,12 +5333,6 @@
        String subId = rsaPublicKeyExample.encryptedData(serviceSubtask.getId().toString(), pub_key);
        String taskId = rsaPublicKeyExample.encryptedData(serviceSubtask.getTaskid().toString(), pub_key);
        //丽水中医院发送的是微信公众号
        if (StringUtils.isNotEmpty(serviceSubtask.getOrgid()) && serviceSubtask.getOrgid().equals("47231022633110211A2101")) {
            resultMap = wechatSubTask(subid);
            return resultMap;
        }
        //发送数据的封装
        ServiceOutPath serviceOutPath = new ServiceOutPath();
        serviceOutPath.setParam1(taskId);
@@ -5218,6 +5377,25 @@
        return resultMap;
    }
    @Override
    public Map<String, Object> weChatSubTask(Long subid) {
        log.info("smsSubTask的入参为:{}", subid);
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", "500");
        resultMap.put("msg", "发送失败");
        //获取患者的基本信息
        ServiceSubtask serviceSubtask = selectServiceSubtaskById(subid);
        RSAPublicKeyExample rsaPublicKeyExample = new RSAPublicKeyExample();
        String patid = rsaPublicKeyExample.encryptedData(serviceSubtask.getPatid().toString(), pub_key);
        String subId = rsaPublicKeyExample.encryptedData(serviceSubtask.getId().toString(), pub_key);
        String taskId = rsaPublicKeyExample.encryptedData(serviceSubtask.getTaskid().toString(), pub_key);
        //丽水中医院发送的是微信公众号
        resultMap = wechatSubTask(subid);
        return resultMap;
    }
    public Map<String,Object> wechatSubTask(Long subid){
        log.info("wechatSubTask的入参为:{}", subid);
        Map<String, Object> resultMap = new HashMap<>();
smartor/src/main/java/com/smartor/service/impl/ServiceTaskServiceImpl.java
@@ -255,23 +255,6 @@
            }
        }
        if (CollectionUtils.isNotEmpty(selectServiceSubtaskList)) {
            //将当前方式的发送状态改成已领取
            ServiceSubtask serviceSubtask = selectServiceSubtaskList.get(0);
            ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform();
            serviceSubtaskPreachform.setPreachform(serviceSubtask.getCurrentPreachform());
            serviceSubtaskPreachform.setSubid(serviceSubtask.getId());
            serviceSubtaskPreachform.setTaskid(serviceTask.getTaskid());
            List<ServiceSubtaskPreachform> serviceSubtaskPreachforms = serviceSubtaskPreachformMapper.selectServiceSubtaskPreachformList(serviceSubtaskPreachform);
            if (CollectionUtils.isNotEmpty(serviceSubtaskPreachforms)) {
                ServiceSubtaskPreachform ssp = serviceSubtaskPreachforms.get(0);
                //人工无-3已领取状态(service_subtask_preachform.sendstate = 3)
                if(StringUtils.isNotEmpty(ssp.getPreachform()) && !ssp.getPreachform().equals("1")){
                    ssp.setSendstate("3");
                    serviceSubtaskPreachformMapper.updateServiceSubtaskPreachform(ssp);
                }
            }
        }
        if (serviceTask != null) {
            if (StringUtils.isNotEmpty(serviceTask.getKcb())) map.put("kcb", serviceTask.getKcb());
            map.put("script", info);
smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml
@@ -214,6 +214,12 @@
            <if test="casePersonAge != null ">and case_person_age = #{casePersonAge}</if>
            <if test="filterDrname != null ">and filter_drname = #{filterDrname}</if>
            <if test="filterDrcode != null ">and filter_drcode = #{filterDrcode}</if>
            <if test="createTimeStart != null">
                AND date_format(create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
            </if>
            <if test="createTimeEnd != null">
                AND date_format(create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
            </if>
        <if test="pageSize != null  and pageNum != null">limit ${pageSize} OFFSET ${pageNum}</if>
    </select>
@@ -262,6 +268,12 @@
            <if test="idcardno != null  and idcardno != ''">and idcardno = #{idcardno}</if>
            <if test="telcode != null  and telcode != ''">and telcode = #{telcode}</if>
            <if test="campusid != null  and campusid != ''">and campusid = #{campusid}</if>
            <if test="createTimeStart != null">
                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
            </if>
            <if test="createTimeEnd != null">
                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
            </if>
            <if test="tagIds != null  and tagIds != ''">and c.tagid in
                <foreach collection="tagIds" item="tagId" open="(" separator="," close=")">
                    #{tagId}
@@ -543,6 +555,12 @@
                    #{tagId}
                </foreach>
            </if>
            <if test="createTimeStart != null">
                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
            </if>
            <if test="createTimeEnd != null">
                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
            </if>
        order by a.update_time desc
        <if test="pn != null  and ps != null">limit ${pn},${ps}</if>
@@ -774,6 +792,12 @@
                    #{pid}
                </foreach>
            </if>
            <if test="createTimeStart != null">
                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
            </if>
            <if test="createTimeEnd != null">
                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
            </if>
            <if test="isFilter == true">
                AND NOT exists ( SELECT 1 FROM pat_filterlist f where d.patid=f.patid)
            </if>
@@ -865,6 +889,12 @@
                    #{pid}
                </foreach>
            </if>
            <if test="createTimeStart != null">
                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
            </if>
            <if test="createTimeEnd != null">
                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
            </if>
            <if test="isFilter == true">
                AND NOT exists ( SELECT 1 FROM pat_filterlist f where d.patid=f.patid)
            </if>
@@ -941,6 +971,12 @@
            <if test="pid != null  and pid != ''">and a.id = #{id}</if>
            <if test="telcode != null  and telcode != ''">and a.telcode = #{telcode}</if>
            <if test="notrequiredFlag != null  and notrequiredFlag != ''">and a.notrequired_flag = #{notrequiredFlag}
            </if>
            <if test="createTimeStart != null">
                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
            </if>
            <if test="createTimeEnd != null">
                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
            </if>
            <if test="hospitalname != null and hospitalname != ''">
                AND d.hospitalname = #{hospitalname}
@@ -1191,6 +1227,12 @@
                    #{pid}
                </foreach>
            </if>
            <if test="createTimeStart != null">
                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
            </if>
            <if test="createTimeEnd != null">
                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
            </if>
            <if test="isFilter == true">
                AND NOT exists ( SELECT 1 FROM pat_filterlist f where d.patid=f.patid)
            </if>
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -73,6 +73,7 @@
        <result property="leavehospitaldistrictname" column="leavehospitaldistrictname"/>
        <result property="excep" column="excep"/>
        <result property="remark" column="remark"/>
        <result property="doctorOpinion" column="doctor_opinion"/>
        <result property="suggest" column="suggest"/>
        <result property="sendstate" column="sendstate"/>
        <result property="inhospid" column="inhospid"/>
@@ -183,6 +184,7 @@
               long_send_time,
               openid,
               remark,
               doctor_opinion,
               service_type,
               libtemplateid,
               libtemplatename,
@@ -594,6 +596,7 @@
            AND visit_count > 1
        </if>
        <if test="remark != null">and remark = #{remark}</if>
        <if test="doctorOpinion != null and doctorOpinion != ''">and doctor_opinion like concat('%', #{doctorOpinion}, '%')</if>
        <if test="suggest != null">and suggest = #{suggest}</if>
        <if test="createBy != null">and create_by = #{createBy}</if>
        <if test="taskGuid != null">and task_guid = #{taskGuid}</if>
@@ -1053,6 +1056,7 @@
            <if test="nurseName != null ">nurse_name,</if>
            <if test="nurseId != null ">nurse_id,</if>
            <if test="remark != null ">remark,</if>
            <if test="doctorOpinion != null ">doctor_opinion,</if>
            <if test="suggest != null ">suggest,</if>
            <if test="sendstate != null ">sendstate,</if>
            <if test="inhospid != null ">inhospid,</if>
@@ -1152,6 +1156,7 @@
            <if test="nurseName != null ">#{nurseName},</if>
            <if test="nurseId != null ">#{nurseId},</if>
            <if test="remark != null ">#{remark},</if>
            <if test="doctorOpinion != null ">#{doctorOpinion},</if>
            <if test="suggest != null ">#{suggest},</if>
            <if test="sendstate != null ">#{sendstate},</if>
            <if test="inhospid != null ">#{inhospid},</if>
@@ -1182,6 +1187,15 @@
            <if test="isManuallyCalled != null ">#{isManuallyCalled},</if>
        </trim>
    </insert>
    <!-- 二次随访幂等:仅当 is_visit_again 不是目标值时更新,返回受影响行数 -->
    <update id="updateIsVisitAgainById">
        update service_subtask
        set is_visit_again = #{isVisitAgain},
            update_time = now()
        where id = #{id}
          and (is_visit_again is null or is_visit_again &lt;&gt; #{isVisitAgain})
    </update>
    <update id="updateServiceSubtask" parameterType="com.smartor.domain.ServiceSubtask">
        update service_subtask
@@ -1257,6 +1271,7 @@
            <if test="nurseName != null ">nurse_name=#{nurseName},</if>
            <if test="nurseId != null ">nurse_id=#{nurseId},</if>
            <if test="remark != null ">remark=#{remark},</if>
            <if test="doctorOpinion != null ">doctor_opinion=#{doctorOpinion},</if>
            <if test="suggest != null ">suggest=#{suggest},</if>
            <if test="inhospid != null ">inhospid=#{inhospid},</if>
            <if test="patfrom != null ">patfrom=#{patfrom},</if>
@@ -1363,6 +1378,7 @@
            <if test="nurseName != null ">nurse_name=#{nurseName},</if>
            <if test="nurseId != null ">nurse_id=#{nurseId},</if>
            <if test="remark != null ">remark=#{remark},</if>
            <if test="doctorOpinion != null ">doctor_opinion=#{doctorOpinion},</if>
            <if test="suggest != null ">suggest=#{suggest},</if>
            <if test="inhospid != null ">inhospid=#{inhospid},</if>
            <if test="patfrom != null ">patfrom=#{patfrom},</if>
@@ -1468,6 +1484,7 @@
            <if test="nurseName != null ">nurse_name=#{nurseName},</if>
            <if test="nurseId != null ">nurse_id=#{nurseId},</if>
            <if test="remark != null ">remark=#{remark},</if>
            <if test="doctorOpinion != null ">doctor_opinion=#{doctorOpinion},</if>
            <if test="suggest != null ">suggest=#{suggest},</if>
            <if test="inhospid != null ">inhospid=#{inhospid},</if>
            <if test="patfrom != null ">patfrom=#{patfrom},</if>
@@ -1580,6 +1597,7 @@
            <if test="nurseName != null ">nurse_name=#{nurseName},</if>
            <if test="nurseId != null ">nurse_id=#{nurseId},</if>
            <if test="remark != null ">remark=#{remark},</if>
            <if test="doctorOpinion != null ">doctor_opinion=#{doctorOpinion},</if>
            <if test="suggest != null ">suggest=#{suggest},</if>
            <if test="inhospid != null ">inhospid=#{inhospid},</if>
            <if test="patfrom != null ">patfrom=#{patfrom},</if>
@@ -1951,6 +1969,7 @@
        continue_time_next,
        task_situation,
        remark,
        doctor_opinion,
        is_manually_called
        from service_subtask
        where 1=1
@@ -2455,6 +2474,7 @@
               nurse_id,
               nurse_name,
               remark,
               doctor_opinion,
               inhospid,
               patfrom,
               task_guid,
@@ -3348,7 +3368,8 @@
        continue_count,
        continue_time_next,
        task_situation,
        remark
        remark,
        doctor_opinion
        from service_subtask
        where 1=1
        and del_flag = 0