| | |
| | | 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; |
| | |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.*; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.ScheduledExecutorService; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | |
| | | private IPatArchiveService patArchiveService; |
| | | |
| | | @Autowired |
| | | private ISysDeptService sysDeptService; |
| | | |
| | | @Autowired |
| | | private ScheduleSubtaskBuilder scheduleSubtaskBuilder; |
| | | |
| | | @Autowired |
| | |
| | | @Value("${hangup}") |
| | | private String hangup; |
| | | |
| | | /** |
| | | * 挂断请求最大重试次数(含首次) |
| | | */ |
| | | private static final int HANGUP_MAX_RETRY = 3; |
| | | |
| | | /** |
| | | * 挂断请求重试间隔(毫秒) |
| | | */ |
| | | private static final long HANGUP_RETRY_INTERVAL_MS = 300L; |
| | | |
| | | /** |
| | | * 兜底挂断的最小延时(毫秒):等平台把结束语播完再挂 |
| | | */ |
| | | private static final long HANGUP_FALLBACK_MIN_DELAY_MS = 8000L; |
| | | |
| | | /** |
| | | * 兜底挂断定时器。 |
| | | * <p> |
| | | * 雨绮这套的挂断原本依赖"平台播完结束语后再回调一次 PlayEventCallback(playstop=true)", |
| | | * 平台不回调电话就一直挂着。这里用一个守护线程池在结束语下发后按字数估算时长做兜底挂断, |
| | | * 与平台回调走同一个幂等标记,不会重复挂断。 |
| | | */ |
| | | private ScheduledExecutorService hangupFallbackExecutor; |
| | | |
| | | @Value("${app_key_yq}") |
| | | private String app_key_yq; |
| | | |
| | |
| | | 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; |
| | | |
| | | /** |
| | | * 查询单一任务(随访) |
| | |
| | | |
| | | @Override |
| | | public List<ServiceSubtask> patItem2(ServiceSubtaskEntity serviceSubtaskEntity) { |
| | | if (serviceSubtaskEntity.getSendstateView() != null) { |
| | | // 1:待随访(1 被领取、2 待发送、3 已发送、5 发送失败、7、超时);2:已完成( 6 已完成)、3:无需随访(4 不执行) |
| | | if (serviceSubtaskEntity.getSendstateView() == 1) |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 7L))); |
| | | if (serviceSubtaskEntity.getSendstateView() == 2) |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(6L))); |
| | | if (serviceSubtaskEntity.getSendstateView() == 3) |
| | | serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(4L))); |
| | | } |
| | | List<ServiceSubtask> serviceSubtaskList = new ArrayList<>(); |
| | | for (Long sendstate : serviceSubtaskEntity.getSendstates()) { |
| | | serviceSubtaskEntity.setSendstate(sendstate); |
| | | List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity); |
| | | serviceSubtaskList.addAll(serviceSubtasks); |
| | | if(CollectionUtils.isNotEmpty(serviceSubtaskEntity.getSendstates())){ |
| | | for (Long sendstate : serviceSubtaskEntity.getSendstates()) { |
| | | serviceSubtaskEntity.setSendstate(sendstate); |
| | | List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity); |
| | | serviceSubtaskList.addAll(serviceSubtasks); |
| | | } |
| | | } |
| | | return serviceSubtaskList; |
| | | } |
| | |
| | | result.putIfAbsent("xsf", 0); |
| | | result.putIfAbsent("dsf", 0); |
| | | result.putIfAbsent("ywc", 0); |
| | | result.putIfAbsent("cs", 0); |
| | | result.putIfAbsent("yc", 0); |
| | | result.putIfAbsent("jg", 0); |
| | | } |
| | |
| | | result.putIfAbsent("dsf", 0); |
| | | result.putIfAbsent("dfs", 0); |
| | | result.putIfAbsent("yfs", 0); |
| | | result.putIfAbsent("cs", 0); |
| | | result.putIfAbsent("ywc", 0); |
| | | result.putIfAbsent("yc", 0); |
| | | result.putIfAbsent("jg", 0); |
| | |
| | | * @return 结果 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public String addSubTaskAgain(ServiceSubtask serviceSubtask) { |
| | | ServiceSubtask serviceSubtask1 = selectServiceSubtaskById(serviceSubtask.getId()); |
| | | PatMedInhosp patMedInhosp = new PatMedInhosp(); |
| | |
| | | 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) { |
| | |
| | | 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); |
| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 结束语缓存 key(只要写过该 key 就说明本次对话已经收尾,结束语为空也算) |
| | | */ |
| | | private String jsyKey(String uuid) { |
| | | return uuid + "- jsy"; |
| | | } |
| | | |
| | | /** |
| | | * 本次对话是否已经收尾(即已经下发过结束语)。 |
| | | * <p> |
| | | * 注意:不能用 getCacheObject(...) != null 判断,结束语为空时写入的是空值, |
| | | * 读出来是 null 但 key 是存在的,必须用 hasKey。 |
| | | */ |
| | | private boolean isDialogueEnded(String uuid) { |
| | | return StringUtils.isNotEmpty(uuid) && Boolean.TRUE.equals(redisCache.hasKey(jsyKey(uuid))); |
| | | } |
| | | |
| | | /** |
| | | * 挂断电话:幂等 + 重试 + 异常不外抛 + 清理结束语缓存。 |
| | | * <p> |
| | | * 平台回调(PlayEventCallback 播放结束)和兜底定时任务都会调这里, |
| | | * 通过 redis 的 {uuid}- hangup-sent 标记保证同一次通话只真正挂断一次。 |
| | | * |
| | | * @param uuid 通话 uuid |
| | | * @param phone 被叫号码 |
| | | * @param reason 挂断原因(仅用于日志排查) |
| | | * @return true=本次确实发出了挂断请求 |
| | | */ |
| | | private boolean doHangup(String uuid, String phone, String reason) { |
| | | if (StringUtils.isEmpty(uuid)) { |
| | | log.warn("挂断跳过:uuid 为空, reason={}", reason); |
| | | return false; |
| | | } |
| | | String sentKey = uuid + "- hangup-sent"; |
| | | if (!redisCache.setCacheObjectIfAbsent(sentKey, "1", 30, TimeUnit.MINUTES)) { |
| | | log.info("挂断跳过:该通话已挂断过, uuid={}, reason={}", uuid, reason); |
| | | return false; |
| | | } |
| | | Map<String, String> req = new HashMap<>(); |
| | | req.put("uuid", uuid); |
| | | req.put("caller", phone); |
| | | String body = new Gson().toJson(req); |
| | | for (int i = 1; i <= HANGUP_MAX_RETRY; i++) { |
| | | try { |
| | | // 只校验 HTTP 状态码:/hangup 通常成功只回 200 且没有响应体, |
| | | // 用 postJsonRequest 会因"响应为null"把成功误判成失败 |
| | | HttpUtil.postJsonRequestAllowEmptyResponse(hangup, body); |
| | | log.info("挂断成功, uuid={}, reason={}, 第{}次, url={}", uuid, reason, i, hangup); |
| | | redisCache.setCacheObject(uuid + "- hangup", "1", 10, TimeUnit.MINUTES); |
| | | redisCache.deleteObject(jsyKey(uuid)); |
| | | redisCache.deleteObject(uuid + "- hangup-planned"); |
| | | return true; |
| | | } catch (Exception e) { |
| | | //把异常类型和信息放在同一行,单行 grep 就能看出是"连不上"还是"返回非200" |
| | | log.error("挂断失败, uuid={}, reason={}, 第{}次, url={}, body={}, 原因={}: {}", |
| | | uuid, reason, i, hangup, body, e.getClass().getSimpleName(), e.getMessage(), e); |
| | | if (i < HANGUP_MAX_RETRY) { |
| | | try { |
| | | Thread.sleep(HANGUP_RETRY_INTERVAL_MS); |
| | | } catch (InterruptedException ie) { |
| | | Thread.currentThread().interrupt(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | // 全部失败:释放幂等标记,允许平台下一次回调或下一个兜底任务重试 |
| | | redisCache.deleteObject(sentKey); |
| | | log.error("挂断最终失败,已释放重试标记, uuid={}, reason={}, url={}", uuid, reason, hangup); |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * 安排一次兜底挂断。 |
| | | * <p> |
| | | * 结束语下发后,平台正常应该再回调 PlayEventCallback(playstop=true),此时由 doHangup 立即挂断; |
| | | * 但平台字段缺失/不回调时电话会一直挂着,所以这里按结束语字数估算播放时长,到点兜底挂一次。 |
| | | * 同一个 uuid 只安排一次({uuid}- hangup-planned 幂等)。 |
| | | * |
| | | * @param uuid 通话 uuid |
| | | * @param phone 被叫号码 |
| | | * @param jsy 结束语(可能为空,为空时按最小延时处理) |
| | | */ |
| | | private void scheduleHangupFallback(String uuid, String phone, String jsy) { |
| | | if (StringUtils.isEmpty(uuid)) { |
| | | return; |
| | | } |
| | | if (!redisCache.setCacheObjectIfAbsent(uuid + "- hangup-planned", "1", 30, TimeUnit.MINUTES)) { |
| | | return; |
| | | } |
| | | // 中文 TTS 大约每秒 3~4 个字,留足余量后再加上最小延时 |
| | | int len = StringUtils.isEmpty(jsy) ? 0 : jsy.length(); |
| | | long delay = HANGUP_FALLBACK_MIN_DELAY_MS + len * 400L; |
| | | if (delay > 60000L) { |
| | | delay = 60000L; |
| | | } |
| | | log.info("安排兜底挂断, uuid={}, 结束语长度={}, 延时={}ms", uuid, len, delay); |
| | | getHangupFallbackExecutor().schedule(() -> { |
| | | try { |
| | | doHangup(uuid, phone, "结束语播完兜底"); |
| | | } catch (Exception e) { |
| | | log.error("兜底挂断异常, uuid={}", uuid, e); |
| | | } |
| | | }, delay, TimeUnit.MILLISECONDS); |
| | | } |
| | | |
| | | /** |
| | | * 兜底挂断线程池(懒加载,守护线程,进程退出不受影响) |
| | | */ |
| | | private ScheduledExecutorService getHangupFallbackExecutor() { |
| | | if (hangupFallbackExecutor == null) { |
| | | synchronized (this) { |
| | | if (hangupFallbackExecutor == null) { |
| | | hangupFallbackExecutor = Executors.newScheduledThreadPool(2, r -> { |
| | | Thread t = new Thread(r, "voice-hangup-fallback"); |
| | | t.setDaemon(true); |
| | | return t; |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | return hangupFallbackExecutor; |
| | | } |
| | | |
| | | /** |
| | | * 电话ASR通话回调(雨绮) |
| | | * |
| | | * @param phoneCallReqYQVO |
| | |
| | | //先把该subId从cache_exist中剔除,只要有回调就剔除(目的是,尽快让cache-exist为空,去缓存下一批要打的电话;这里剔除不影响电话的正常业务) |
| | | // redisCache.removeElementFromList("cache-exist", phoneCallReqYQVO.getTaskid()); |
| | | |
| | | //uuid / taskid / phone 是后面所有逻辑的前提,缺了就直接忽略,避免 Long.valueOf(null)、null.trim() 之类的 500 |
| | | if (StringUtils.isEmpty(phoneCallReqYQVO.getUuid()) || StringUtils.isEmpty(phoneCallReqYQVO.getTaskid()) |
| | | || StringUtils.isEmpty(phoneCallReqYQVO.getPhone())) { |
| | | log.warn("雨绮回调缺少 uuid/taskid/phone,直接忽略: operate={}, uuid={}, taskid={}, phone={}", |
| | | phoneCallReqYQVO.getOperate(), phoneCallReqYQVO.getUuid(), |
| | | phoneCallReqYQVO.getTaskid(), phoneCallReqYQVO.getPhone()); |
| | | return new PhoneCallBackYQVO(); |
| | | } |
| | | |
| | | //判断是否挂机 |
| | | String hangupState = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "- hangup"); |
| | | Object cacheObject1 = redisCache.getCacheObject(phoneCallReqYQVO.getTaskid() + "recordAccept-hungup"); |
| | |
| | | } |
| | | if ("AsrCallback".equals(phoneCallReqYQVO.getOperate()) && phoneCallReqYQVO.getEnd_time() != null && phoneCallReqYQVO.getEnd_time() != -1) { |
| | | //处理挂断 |
| | | if (phoneCallReqYQVO.getAsrtext().contains("拨叫的用户正忙")) { |
| | | if (StringUtils.isNotEmpty(phoneCallReqYQVO.getAsrtext()) && phoneCallReqYQVO.getAsrtext().contains("拨叫的用户正忙")) { |
| | | //判断 recordAccept-hungup 是否有值,如果有,则说明不用处理了,之前已经处理过了(recordAccept-hungup这个主要是给"通话记录用的,") |
| | | Object cacheObject = redisCache.getCacheObject(phoneCallReqYQVO.getTaskid() + "recordAccept-hungup"); |
| | | if (ObjectUtils.isNotEmpty(cacheObject) && String.valueOf(cacheObject).equals("1")) { |
| | |
| | | //如果发送状态为6,就说明已经打完了,也没有必要继续往下走了 |
| | | if (StringUtils.isNotEmpty(phoneCallReqYQVO.getTaskid())) { |
| | | ServiceSubtask serviceSubtask = serviceSubtaskMapper.selectServiceSubtaskById(Long.valueOf(phoneCallReqYQVO.getTaskid())); |
| | | if (serviceSubtask.getSendstate() == 6L) return new PhoneCallBackYQVO(); |
| | | if (serviceSubtask != null && serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 6L) { |
| | | return new PhoneCallBackYQVO(); |
| | | } |
| | | } |
| | | |
| | | ServiceSubtask serviceSubtask2 = new ServiceSubtask(); |
| | |
| | | serviceSubtask2.setSenduuid(phoneCallReqYQVO.getUuid()); |
| | | serviceSubtask2.setId(Long.valueOf(phoneCallReqYQVO.getTaskid())); |
| | | serviceSubtaskMapper.updateServiceSubtask(serviceSubtask2); |
| | | Map<String, String> req = new HashMap<>(); |
| | | req.put("uuid", phoneCallReqYQVO.getUuid()); |
| | | req.put("caller", phoneCallReqYQVO.getPhone()); |
| | | HttpUtil.postJsonRequest(hangup, new Gson().toJson(req)); |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "- hangup", "1", 2, TimeUnit.MINUTES); |
| | | //挂断(幂等 + 失败重试,异常不再往外抛,避免回调直接 500) |
| | | doHangup(phoneCallReqYQVO.getUuid(), phoneCallReqYQVO.getPhone(), "拨叫的用户正忙"); |
| | | //先把默认的recordAccept-hungup删除,再更新 |
| | | redisCache.deleteObject(phoneCallReqYQVO.getTaskid() + "recordAccept-hungup"); |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getTaskid() + "recordAccept-hungup", "1", 10, TimeUnit.MINUTES); |
| | |
| | | return new PhoneCallBackYQVO(); |
| | | } |
| | | } |
| | | //结束语已经下发过,说明本次对话已经收尾:安排一次兜底挂断。 |
| | | //平台正常情况下会再回调 PlayEventCallback(playstop=true) 让我们立刻挂断; |
| | | //但平台字段缺失/不回调时电话会一直挂着,所以这里按结束语字数估算播放时长到点兜底挂一次(幂等)。 |
| | | if (isDialogueEnded(phoneCallReqYQVO.getUuid())) { |
| | | scheduleHangupFallback(phoneCallReqYQVO.getUuid(), phoneCallReqYQVO.getPhone(), |
| | | redisCache.getCacheObject(jsyKey(phoneCallReqYQVO.getUuid()))); |
| | | } |
| | | //定义一个分数的变量 |
| | | Boolean aBoolean1 = redisCache.hasKey(phoneCallReqYQVO.getUuid() + "SCORE"); |
| | | if (!aBoolean1) redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "SCORE", 0.0, 120, TimeUnit.MINUTES); |
| | | PhoneCallBackYQVO phoneCallBackYQVO = new PhoneCallBackYQVO(); |
| | | //channel_create 通道创建的时候,可以执行一些其它操作,譬如发个短信之类的; 我们的业务可以不用管 PlayEventCallback 这个是播放语音的,暂时用不到 End_time()= -1或null表示当前的asrtext不是一句完整的话 |
| | | if (phoneCallReqYQVO.getOperate().equals("channel_create")) { |
| | | if ("channel_create".equals(phoneCallReqYQVO.getOperate())) { |
| | | return phoneCallBackYQVO; |
| | | } |
| | | //PlayEventCallback 这个是播放语音的 playstart:放音开始(问题播报开始) playstop: 放音结束(问题播报结束) |
| | | if (phoneCallReqYQVO.getOperate().equals("PlayEventCallback")) { |
| | | String cacheJSY = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "- jsy"); |
| | | if (phoneCallReqYQVO.getOperate().equals("PlayEventCallback") && phoneCallReqYQVO.getPlaystop() == false) { |
| | | //判断redis中是否有结束语 |
| | | if (StringUtils.isEmpty(cacheJSY)) { |
| | | if ("PlayEventCallback".equals(phoneCallReqYQVO.getOperate())) { |
| | | //结束语是否已下发(用 hasKey 判断,结束语为空也算已收尾) |
| | | boolean dialogueEnded = isDialogueEnded(phoneCallReqYQVO.getUuid()); |
| | | //playstop 可能是 null,平台也可能用 play_stop,统一由 VO 判断 |
| | | boolean playStop = phoneCallReqYQVO.playStopIsTrue(); |
| | | String cacheJSY = redisCache.getCacheObject(jsyKey(phoneCallReqYQVO.getUuid())); |
| | | log.info("PlayEventCallback: uuid={}, playstop={}, 是否已下发结束语={}", |
| | | phoneCallReqYQVO.getUuid(), playStop, dialogueEnded); |
| | | if (!playStop) { |
| | | //放音开始 |
| | | if (!dialogueEnded) { |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "PlayEventCallbackPlaystop", false, 120, TimeUnit.MINUTES); |
| | | log.error("PlayEventCallbackPlaystop的值为-------:{}", false); |
| | | } else { |
| | | //平台开始播结束语,兜底挂断在方法入口已经安排过,这里只留日志便于排查 |
| | | log.info("结束语开始播放, uuid={}, 结束语={}", phoneCallReqYQVO.getUuid(), cacheJSY); |
| | | } |
| | | } else { |
| | | //如果结束语不为空,则要挂电话了 |
| | | if (StringUtils.isNotEmpty(cacheJSY)) { |
| | | Map<String, String> req = new HashMap<>(); |
| | | req.put("uuid", phoneCallReqYQVO.getUuid()); |
| | | req.put("caller", phoneCallReqYQVO.getPhone()); |
| | | // HttpUtils.sendPost(hangup, new Gson().toJson(req)); |
| | | HttpUtil.postJsonRequest(hangup, new Gson().toJson(req)); |
| | | ServiceSubtask serviceSubtask = serviceSubtaskMapper.selectServiceSubtaskById(Long.valueOf(phoneCallReqYQVO.getTaskid())); |
| | | //记录电话发送状态 |
| | | setFailPreachForm(serviceSubtask, "3", "通话正常结束", "9"); |
| | | //删除结束语的缓存 |
| | | redisCache.deleteObject(phoneCallReqYQVO.getUuid() + "- jsy"); |
| | | //放音结束 |
| | | if (dialogueEnded) { |
| | | //结束语播完,立即挂断(幂等 + 失败重试,异常不外抛) |
| | | doHangup(phoneCallReqYQVO.getUuid(), phoneCallReqYQVO.getPhone(), "结束语播放结束"); |
| | | //记录电话发送状态:挂断成败都要落库,不能再让回调直接 500 |
| | | if (StringUtils.isNotEmpty(phoneCallReqYQVO.getTaskid())) { |
| | | try { |
| | | ServiceSubtask serviceSubtask = serviceSubtaskMapper.selectServiceSubtaskById(Long.valueOf(phoneCallReqYQVO.getTaskid())); |
| | | setFailPreachForm(serviceSubtask, "3", "通话正常结束", "9"); |
| | | } catch (Exception e) { |
| | | log.error("挂断后更新子任务状态失败, taskid={}", phoneCallReqYQVO.getTaskid(), e); |
| | | } |
| | | } |
| | | } else { |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "PlayEventCallbackPlaystop", true, 120, TimeUnit.MINUTES); |
| | | log.error("-------PlayEventCallbackPlaystop的值为:{}", true); |
| | |
| | | |
| | | //通过子任务ID获取到模板信息 |
| | | ServiceSubtask serviceSubtask = serviceSubtaskMapper.selectServiceSubtaskById(Long.valueOf(phoneCallReqYQVO.getTaskid())); |
| | | if (serviceSubtask == null) { |
| | | log.warn("回调对应的子任务不存在,直接忽略, taskid={}, operate={}", phoneCallReqYQVO.getTaskid(), phoneCallReqYQVO.getOperate()); |
| | | return phoneCallBackYQVO; |
| | | } |
| | | IvrTaskTemplate ivrTaskTemplate = ivrTaskTemplateService.selectIvrTaskTemplateByID(serviceSubtask.getTemplateid()); |
| | | //获取模板问题 |
| | | IvrTaskTemplateScript ivrTaskTemplateScript = new IvrTaskTemplateScript(); |
| | |
| | | //获取问题ID 和 序号 |
| | | String scriptId = redisCache.getCacheObject(phoneCallReqYQVO.getTaskid().trim() + "-" + phoneCallReqYQVO.getPhone().trim()); |
| | | log.error("scriptId是多少:{}", scriptId); |
| | | if (StringUtils.isEmpty(scriptId)) { |
| | | //当前没有"正在问的问题",说明本次通话已经收尾(结束语已下发后,平台还会补 Silent/Asr 回调), |
| | | //原来这里直接 Long.valueOf(null) 会抛 NumberFormatException 让回调 500。 |
| | | //这里直接忽略,并确保兜底挂断已经排上,避免电话继续挂着。 |
| | | log.warn("未取到当前问题ID,按通话已收尾处理, taskid={}, phone={}, operate={}", |
| | | phoneCallReqYQVO.getTaskid(), phoneCallReqYQVO.getPhone(), phoneCallReqYQVO.getOperate()); |
| | | if (isDialogueEnded(phoneCallReqYQVO.getUuid())) { |
| | | scheduleHangupFallback(phoneCallReqYQVO.getUuid(), phoneCallReqYQVO.getPhone(), |
| | | redisCache.getCacheObject(jsyKey(phoneCallReqYQVO.getUuid()))); |
| | | } |
| | | return phoneCallBackYQVO; |
| | | } |
| | | //当前题的信息 |
| | | IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO = iIvrTaskTemplateScriptService.getTaskTempScriptInfoByid(Long.valueOf(scriptId)); |
| | | if (ivrTaskTemplateScriptVO == null) { |
| | | log.warn("当前问题不存在,忽略回调, taskid={}, scriptId={}", phoneCallReqYQVO.getTaskid(), scriptId); |
| | | return phoneCallBackYQVO; |
| | | } |
| | | //判断UUID是否存在 |
| | | Boolean aBoolean = redisCache.hasKey(phoneCallReqYQVO.getUuid()); |
| | | if (!aBoolean) { |
| | |
| | | redisCache.deleteObject(phoneCallReqYQVO.getUuid() + "PlayEventCallbackPlaystop"); |
| | | // //获取下一题 |
| | | log.error("获取下一题的信息:{}", ivrTaskTemplateScriptVO); |
| | | if (ivrTaskTemplateScriptVO.getBranchFlag().equals("1") || ivrTaskTemplateScriptVO.getBranchFlag().equals("0") && ivrTaskTemplateScriptVO.getNextScriptno() != null && ivrTaskTemplateScriptVO.getNextScriptno() != 0) { |
| | | //branchFlag 可能为 null,统一按分支0处理,避免 NPE 把回调打 500 |
| | | if ("1".equals(ivrTaskTemplateScriptVO.getBranchFlag()) || "0".equals(ivrTaskTemplateScriptVO.getBranchFlag()) && ivrTaskTemplateScriptVO.getNextScriptno() != null && ivrTaskTemplateScriptVO.getNextScriptno() != 0) { |
| | | Long nextQuestion = null; |
| | | if (ivrTaskTemplateScriptVO.getBranchFlag().equals("1")) { |
| | | if ("1".equals(ivrTaskTemplateScriptVO.getBranchFlag())) { |
| | | nextQuestion = ivrTaskTemplateScriptVO.getIvrTaskScriptTargetoptionList().get(j).getNextQuestion(); |
| | | //更新分数 |
| | | double score = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "SCORE"); |
| | |
| | | if (ObjectUtils.isNotEmpty(countObj)) count = (Integer) countObj; |
| | | if (count != null && count >= ivrTaskTemplate.getMateNum()) { |
| | | //如果count已经大于或等于没有匹配次数 |
| | | if (ivrTaskTemplateScriptVO.getBranchFlag().equals("0") && ivrTaskTemplateScriptVO.getNextScriptno() == null || ivrTaskTemplateScriptVO.getBranchFlag().equals("0") && ivrTaskTemplateScriptVO.getNextScriptno() == 0 || ivrTaskTemplateScriptVO.getBranchFlag().equals("1") && ivrTaskTemplateScriptVO.getNextScriptno() == null || ivrTaskTemplateScriptVO.getBranchFlag().equals("1") && ivrTaskTemplateScriptVO.getNextScriptno() == 0) { |
| | | //branchFlag 可能为 null,统一按分支0处理,避免 NPE 把回调打 500 |
| | | if ("0".equals(ivrTaskTemplateScriptVO.getBranchFlag()) && ivrTaskTemplateScriptVO.getNextScriptno() == null || "0".equals(ivrTaskTemplateScriptVO.getBranchFlag()) && ivrTaskTemplateScriptVO.getNextScriptno() == 0 || "1".equals(ivrTaskTemplateScriptVO.getBranchFlag()) && ivrTaskTemplateScriptVO.getNextScriptno() == null || "1".equals(ivrTaskTemplateScriptVO.getBranchFlag()) && ivrTaskTemplateScriptVO.getNextScriptno() == 0) { |
| | | //如果是最后一道题,或者没有下一题了,就直接挂机 |
| | | ServiceTask serviceTask1 = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | phoneCallBackYQVO.setType("text"); |
| | |
| | | @Override |
| | | public Integer recordAccept(PhoneCallRecordVO phoneCallRecordVO) { |
| | | Object cacheObject = redisCache.getCacheObject(phoneCallRecordVO.getTaskid() + "recordAccept-hungup"); |
| | | if (phoneCallRecordVO.getHangup_cause().equals("NO_USER_RESPONSE") || ObjectUtils.isNotEmpty(cacheObject) && String.valueOf(cacheObject).equals("1")) { |
| | | // hangup_cause 平台不一定每次都带,这里判空,避免整条通话记录回调直接 NPE 500 |
| | | boolean noUserResponse = "NO_USER_RESPONSE".equals(phoneCallRecordVO.getHangup_cause()); |
| | | if (noUserResponse || ObjectUtils.isNotEmpty(cacheObject) && String.valueOf(cacheObject).equals("1")) { |
| | | //无人接听 |
| | | //记录电话发送状态 |
| | | ServiceSubtask serviceSubtask = serviceSubtaskMapper.selectServiceSubtaskById(Long.valueOf(phoneCallRecordVO.getTaskid())); |
| | |
| | | // if (phoneCallRecordVO.getAnswered() != null && phoneCallRecordVO.getAnswered() == true) { |
| | | log.error("有人接电话了,并且完了"); |
| | | //说明有人接了 |
| | | int startIndex = phoneCallRecordVO.getRecord_path().indexOf("voice") + "voice".length() + 1; // 加1是跳过/符号 |
| | | int endIndex = phoneCallRecordVO.getRecord_path().lastIndexOf("/"); |
| | | String result = phoneCallRecordVO.getRecord_path().substring(startIndex, endIndex); |
| | | //录音路径只做日志留痕:平台给空/格式不符时原来的 substring 会抛异常,导致整条回调 500、状态和录音都不处理 |
| | | String recordPath = phoneCallRecordVO.getRecord_path(); |
| | | if (StringUtils.isNotEmpty(recordPath) && recordPath.contains("voice") && recordPath.lastIndexOf("/") > 0) { |
| | | log.info("通话录音路径, taskid={}, record_path={}", phoneCallRecordVO.getTaskid(), recordPath); |
| | | } else { |
| | | log.warn("通话记录回调未取到有效录音路径, taskid={}, record_path={}", phoneCallRecordVO.getTaskid(), recordPath); |
| | | } |
| | | ServiceSubtask serviceSubtask = new ServiceSubtask(); |
| | | serviceSubtask.setRemark("电话拨打已完成"); |
| | | serviceSubtask.setSendstate(6L); |
| | |
| | | * @return |
| | | */ |
| | | @Override |
| | | @Cacheable(value = "sfStatistics", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#serviceSubtaskCountReq.toString().getBytes())", unless = "#result == null or #result.isEmpty()") |
| | | // @Cacheable(value = "sfStatistics", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#serviceSubtaskCountReq.toString().getBytes())", unless = "#result == null or #result.isEmpty()") |
| | | public List<ServiceSubtaskStatistic> getSfStatistics(ServiceSubtaskCountReq serviceSubtaskCountReq) { |
| | | log.info("getSfStatistics的入参为:{}", serviceSubtaskCountReq); |
| | | String drcode = "drcode"; |
| | |
| | | ssRate.setOrgid(serviceSubtaskCountReq.getOrgid()); |
| | | ssRate.setStarttime(serviceSubtaskCountReq.getStartTime()); |
| | | ssRate.setEndtime(serviceSubtaskCountReq.getEndTime()); |
| | | ssRate.setStartOutHospTime(serviceSubtaskCountReq.getHospitalStartTime()); |
| | | ssRate.setEndOutHospTime(serviceSubtaskCountReq.getHospitalEndTime()); |
| | | ssRate.setVisitCount(serviceSubtaskCountReq.getVisitCount()); |
| | | ssRate.setServiceTypeList(serviceSubtaskCountReq.getServiceType()); |
| | | if (serviceSubtaskCountReq.getEndTime() != null && new Date().before(serviceSubtaskCountReq.getEndTime())) { |
| | |
| | | if (serviceSubtaskCountReq.getEndTime() != null && new Date().before(serviceSubtaskCountReq.getEndTime())) { |
| | | ss.setEndtime(new Date()); |
| | | } |
| | | ss.setStartOutHospTime(serviceSubtaskCountReq.getHospitalStartTime()); |
| | | ss.setEndOutHospTime(serviceSubtaskCountReq.getHospitalEndTime()); |
| | | Double jsRate = serviceSubtaskMapper.selectTimelyRate(ss); |
| | | statistic.setRate(jsRate); |
| | | if (ObjectUtils.isNotEmpty(statistic)) { |
| | |
| | | //应随访人次 |
| | | if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) { |
| | | serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L); |
| | | } |
| | | |
| | | //人工已拨通总量 |
| | | if (ObjectUtils.isNotEmpty(serviceSubtask.getIsManuallyCalled()) |
| | | && serviceSubtask.getIsManuallyCalled().equals(1L)) { |
| | | serviceSubtaskStatistic.setManuallyCalled(serviceSubtaskStatistic.getManuallyCalled() + 1L); |
| | | } |
| | | |
| | | //首次出院随访 |
| | |
| | | * taskSituation4Info - taskSituation = 4 |
| | | * taskSituation5Info - taskSituation = 5 |
| | | * taskSituation6Info - taskSituation = 7 |
| | | * manuallyCalledInfo - isManuallyCalled = 1L |
| | | * filterCountList - taskSituation = 6 && sendstate = 4 |
| | | * |
| | | */ |
| | |
| | | serviceSubtaskCountReq.setVisitCount(1); |
| | | serviceSubtaskCountReq.setCurrentPreachform("4"); |
| | | break; |
| | | case "wechatInfo": |
| | | case "weChatInfo": |
| | | serviceSubtaskCountReq.setVisitCount(1); |
| | | serviceSubtaskCountReq.setCurrentPreachform("5"); |
| | | break; |
| | |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setCurrentPreachform("4"); |
| | | break; |
| | | case "wechatSuccessInfo": |
| | | case "weChatSuccessInfo": |
| | | serviceSubtaskCountReq.setVisitCount(1); |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setCurrentPreachform("5"); |
| | |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setCurrentPreachform("4"); |
| | | break; |
| | | case "wechatAgainInfo": |
| | | case "weChatAgainInfo": |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setCurrentPreachform("5"); |
| | | break; |
| | |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setCurrentPreachform("4"); |
| | | break; |
| | | case "wechatAgainSuccessInfo": |
| | | case "weChatAgainSuccessInfo": |
| | | serviceSubtaskCountReq.setVisitCount(2); |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L))); |
| | | serviceSubtaskCountReq.setCurrentPreachform("5"); |
| | |
| | | case "filterCountList": |
| | | serviceSubtaskCountReq.setTaskSituation(6); |
| | | serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(4L))); |
| | | break; |
| | | case "manuallyCalledInfo": |
| | | serviceSubtaskCountReq.setIsManuallyCalled(1L); |
| | | break; |
| | | default: |
| | | break; |
| | |
| | | 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); |
| | |
| | | serviceSubtask.setCurrentPreachform("4"); |
| | | } |
| | | } |
| | | 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; |
| | | } |
| | | |
| | |
| | | String voiceText = phoneCallReqYQVO.getAsrtext(); |
| | | String questionText = StringUtils.isNotEmpty(ivrTaskTemplateScriptVO.getIvrtext()) ? ivrTaskTemplateScriptVO.getIvrtext() : ivrTaskTemplateScriptVO.getScriptContent(); |
| | | Integer matched = -1; |
| | | log.info("AI匹配:url = {},questionText = {},voiceText = {},options = {},uuid ={}", |
| | | url,questionText,voiceText,options,uuid); |
| | | matched = matchOptionIndex(url, questionText, voiceText, options, uuid); |
| | | //说明匹配正确了 |
| | | if (matched != null && matched >= 0) { |
| | |
| | | phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + serviceTask.getJsy()); |
| | | //记录状态 |
| | | setFailPreachForm(serviceSubtask, "3", "电话拨打已完成", "9"); |
| | | return phoneCallBackYQVO; |
| | | back = phoneCallBackYQVO; |
| | | return back; |
| | | } |
| | | |
| | | flag = 1; |
| | |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "- jsy", serviceTask.getJsy(), 120, TimeUnit.MINUTES); |
| | | phoneCallBackYQVO.setType("text"); |
| | | phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + serviceTask.getJsy()); |
| | | //将结果写进detail表(本题的明细在上面匹配成功时已经组装并存入serviceSubtaskDetailList,这里直接落库,避免重复新增) |
| | | serviceSubTaskDetailReq.setServiceSubtaskDetailList(serviceSubtaskDetailList); |
| | | serviceSubTaskDetailReq.setGuid(phoneCallReqYQVO.getGuid()); |
| | | serviceSubTaskDetailReq.setOrgid(phoneCallReqYQVO.getOrgid()); |
| | | saveQuestionAnswerPhone(serviceSubTaskDetailReq); |
| | | return phoneCallBackYQVO; |
| | | //明细在本题匹配成功时已落库(见上方 saveQuestionAnswerPhone),这里不再重复保存 |
| | | back = phoneCallBackYQVO; |
| | | return back; |
| | | } |
| | | |
| | | for (IvrTaskTemplateScript script : ivrTaskTemplateScripts) { |
| | |
| | | redisCache.deleteObject(phoneCallReqYQVO.getTaskid().trim() + "&&" + "mate" + "&&" + phoneCallReqYQVO.getUuid()); |
| | | } |
| | | } |
| | | //下一题脚本没找到时,兜底重问当前题,避免调用方误判为未处理而退回正则识别 |
| | | if (StringUtils.isEmpty(phoneCallBackYQVO.getValue())) { |
| | | log.error("下一题脚本未找到,重问当前题,nextQuestion:{}", nextQuestion); |
| | | phoneCallBackYQVO.setType("text"); |
| | | phoneCallBackYQVO.setSilent_interval(ivrTaskTemplate.getSilencetime().intValue()); |
| | | phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + getObject(serviceSubtask, questionText)); |
| | | } |
| | | back = phoneCallBackYQVO; |
| | | return back; |
| | | } else if (ivrTaskTemplateScriptVO.getNextScriptno() == null || ivrTaskTemplateScriptVO.getNextScriptno() == 0) { |
| | | ServiceTask serviceTask1 = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | phoneCallBackYQVO.setType("text"); |
| | |
| | | //在redis中保存一下结束语,在调用挂电话的方法时删除 |
| | | ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "- jsy", serviceTask.getJsy(), 120, TimeUnit.MINUTES); |
| | | // return phoneCallBackYQVO; |
| | | //结束语已拼进返回值,挂机由平台按返回值执行 |
| | | back = phoneCallBackYQVO; |
| | | return back; |
| | | |
| | | } |
| | | } else { |
| | | log.info("没有匹配上----------------------------"); |
| | | //flag=0,说明没 匹配上,也要把患者说的话记录下来 |
| | | if (matched == ivrTaskTemplateScriptVO.getIvrTaskScriptTargetoptionList().size() - 1 && flag == 0) { |
| | | ServiceSubTaskDetailReq serviceSubTaskDetailReq = new ServiceSubTaskDetailReq(); |
| | | List<ServiceSubtaskDetail> serviceSubtaskDetailList = new ArrayList<>(); |
| | | ServiceSubtaskDetail serviceSubtaskDetail = getServiceSubtaskDetail(phoneCallReqYQVO, ivrTaskTemplateScriptVO, serviceSubtask, ivrTaskTemplate); |
| | | //修改一下语音路径(改成前端可以访问的,存到数据库中) |
| | | if (StringUtils.isNotEmpty(serviceSubtaskDetail.getQuestionvoice())) { |
| | | String questionvoice = serviceSubtaskDetail.getQuestionvoice(); |
| | | String[] split = questionvoice.split("\\\\"); |
| | | if (split.length > 0) { |
| | | String lastPart = split[split.length - 1]; |
| | | serviceSubtaskDetail.setQuestionvoice(voicePathPrefix + lastPart); |
| | | //AI没有匹配上:由AI分支自己按"没问清就重问"的次数处理,不再退回正则识别 |
| | | log.info("ai没有匹配上----------------------------"); |
| | | //没匹配上也要把患者说的话记录下来(matchedtext置空,与正则路径口径一致) |
| | | ServiceSubTaskDetailReq serviceSubTaskDetailReq = new ServiceSubTaskDetailReq(); |
| | | List<ServiceSubtaskDetail> serviceSubtaskDetailList = new ArrayList<>(); |
| | | ServiceSubtaskDetail serviceSubtaskDetail = getServiceSubtaskDetail(phoneCallReqYQVO, ivrTaskTemplateScriptVO, serviceSubtask, ivrTaskTemplate); |
| | | //修改一下语音路径(改成前端可以访问的,存到数据库中) |
| | | if (StringUtils.isNotEmpty(serviceSubtaskDetail.getQuestionvoice())) { |
| | | String questionvoice = serviceSubtaskDetail.getQuestionvoice(); |
| | | String[] split = questionvoice.split("\\\\"); |
| | | if (split.length > 0) { |
| | | String lastPart = split[split.length - 1]; |
| | | serviceSubtaskDetail.setQuestionvoice(voicePathPrefix + lastPart); |
| | | } |
| | | } |
| | | serviceSubtaskDetailList.add(serviceSubtaskDetail); |
| | | //如果没有 匹配上,这个必须为null |
| | | serviceSubtaskDetailList.get(0).setMatchedtext(""); |
| | | serviceSubTaskDetailReq.setServiceSubtaskDetailList(serviceSubtaskDetailList); |
| | | serviceSubTaskDetailReq.setGuid(phoneCallReqYQVO.getGuid()); |
| | | serviceSubTaskDetailReq.setOrgid(phoneCallReqYQVO.getOrgid()); |
| | | saveQuestionAnswerPhone(serviceSubTaskDetailReq); |
| | | |
| | | String mateKey = phoneCallReqYQVO.getTaskid().trim() + "&&" + "mate" + "&&" + phoneCallReqYQVO.getUuid(); |
| | | Integer count = null; |
| | | Object countObj = redisCache.getCacheObject(mateKey); |
| | | if (ObjectUtils.isNotEmpty(countObj)) count = (Integer) countObj; |
| | | if (count != null && count >= ivrTaskTemplate.getMateNum()) { |
| | | //没匹配次数已经用满,不能再重问了 |
| | | boolean noNextQuestion = ("0".equals(ivrTaskTemplateScriptVO.getBranchFlag()) || "1".equals(ivrTaskTemplateScriptVO.getBranchFlag())) |
| | | && (ivrTaskTemplateScriptVO.getNextScriptno() == null || ivrTaskTemplateScriptVO.getNextScriptno() == 0); |
| | | if (noNextQuestion) { |
| | | //已经是最后一题,直接挂机 |
| | | ServiceTask serviceTask1 = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | phoneCallBackYQVO.setType("text"); |
| | | phoneCallBackYQVO.setSilent_interval(ivrTaskTemplate.getSilencetime().intValue()); |
| | | phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + serviceTask1.getJsy()); |
| | | //更新一下分数 |
| | | Double score = null; |
| | | Object scoreObj = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "SCORE"); |
| | | if (ObjectUtils.isNotEmpty(scoreObj)) score = (Double) scoreObj; |
| | | serviceSubtask.setScore(String.valueOf(score)); |
| | | serviceSubtask.setFinishtime(new Date()); |
| | | serviceSubtask.setSendstate(6L); |
| | | serviceSubtask.setRemark("电话拨打已完成"); |
| | | serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); |
| | | //记录状态 |
| | | setFailPreachForm(serviceSubtask, "3", "电话拨打已完成", "9"); |
| | | //在redis中保存一下结束语,在调用挂电话的方法时删除 |
| | | ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid()); |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "- jsy", serviceTask.getJsy(), 120, TimeUnit.MINUTES); |
| | | //去redis中,把该子任务ID删除 |
| | | Long id = serviceSubtask.getId(); |
| | | Map<String, String> map = delRedisValue(null, id.toString()); |
| | | if (ObjectUtils.isNotEmpty(map)) { |
| | | redisCache.setCacheObject(map.get("cacheName"), map.get("val")); |
| | | redisCache.deleteObject(serviceSubtask.getId() + "-" + serviceSubtask.getPhone()); |
| | | redisCache.deleteObject(phoneCallReqYQVO.getUuid() + "SCORE"); |
| | | } |
| | | } else { |
| | | //还有下一题,按nextScriptno继续提问 |
| | | for (IvrTaskTemplateScript script : ivrTaskTemplateScripts) { |
| | | if (script.getSort() == ivrTaskTemplateScriptVO.getNextScriptno().intValue()) { |
| | | phoneCallBackYQVO.setType("text"); |
| | | phoneCallBackYQVO.setSilent_interval(ivrTaskTemplate.getSilencetime().intValue()); |
| | | String scriptContent = StringUtils.isNotEmpty(script.getIvrtext()) ? script.getIvrtext() : script.getScriptContent(); |
| | | phoneCallBackYQVO.setValue(phoneCallBackYQVO.getCommonValue() + getObject(serviceSubtask, scriptContent)); |
| | | //将该患者的Redis中的题目ID,进行修改 |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getTaskid().trim() + "-" + phoneCallReqYQVO.getPhone().trim(), script.getId().toString(), 120, TimeUnit.MINUTES); |
| | | //更新一下分数 |
| | | Double score = null; |
| | | Object scoreObj = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "SCORE"); |
| | | if (ObjectUtils.isNotEmpty(scoreObj)) score = (Double) scoreObj; |
| | | score = BigDecimal.valueOf(score).add(new BigDecimal(script.getScore())).doubleValue(); |
| | | redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "SCORE", score, 120, TimeUnit.MINUTES); |
| | | //换题成功,没匹配次数清零 |
| | | redisCache.deleteObject(mateKey); |
| | | } |
| | | } |
| | | } |
| | | serviceSubtaskDetailList.add(serviceSubtaskDetail); |
| | | //如果没有 匹配上,这个必须为null |
| | | serviceSubtaskDetailList.get(0).setMatchedtext(""); |
| | | serviceSubTaskDetailReq.setServiceSubtaskDetailList(serviceSubtaskDetailList); |
| | | serviceSubTaskDetailReq.setGuid(phoneCallReqYQVO.getGuid()); |
| | | serviceSubTaskDetailReq.setOrgid(phoneCallReqYQVO.getOrgid()); |
| | | saveQuestionAnswerPhone(serviceSubTaskDetailReq); |
| | | } else { |
| | | //还没问满次数,用noMatchText引导患者重答 |
| | | redisCache.setCacheObject(mateKey, count == null ? 1 : count + 1, 120, TimeUnit.MINUTES); |
| | | phoneCallBackYQVO.setType("text"); |
| | | phoneCallBackYQVO.setSilent_interval(ivrTaskTemplate.getSilencetime().intValue()); |
| | | phoneCallBackYQVO.setValue(ivrTaskTemplateScriptVO.getNoMatchText() + getObject(serviceSubtask, StringUtils.isNotEmpty(ivrTaskTemplateScriptVO.getIvrtext()) ? ivrTaskTemplateScriptVO.getIvrtext() : ivrTaskTemplateScriptVO.getScriptContent())); |
| | | } |
| | | back = phoneCallBackYQVO; |
| | | return back; |
| | | } |
| | | return back; |
| | | } |