From d7e3192804fca756d881af360ffd5eea157ef9ad Mon Sep 17 00:00:00 2001
From: 陈昶聿 <chychen@nbjetron.com>
Date: 星期三, 21 十月 2026 15:45:18 +0800
Subject: [PATCH] 【市一】电话挂断服务

---
 smartor/src/main/java/com/smartor/domain/PatArchiveReq.java                            |   19 ++
 smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java            |   51 +++++
 smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml                         |   32 +-
 smartor/src/main/resources/mapper/smartor/PatMedInhospMapper.xml                       |   55 +++---
 smartor/src/main/java/com/smartor/domain/PhoneCallReqYQVO.java                         |   21 ++
 smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java          |  267 ++++++++++++++++++++++++++---
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatMedInhospController.java |    9 
 ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java                 |   14 +
 ruoyi-common/src/main/java/com/ruoyi/common/utils/HttpUtil.java                        |   44 ++++
 smartor/src/main/java/com/smartor/mapper/PatMedInhospMapper.java                       |    9 +
 10 files changed, 435 insertions(+), 86 deletions(-)

diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatMedInhospController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatMedInhospController.java
index 0ef614f..7947abd 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatMedInhospController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/PatMedInhospController.java
@@ -70,13 +70,8 @@
         SysUser user = getLoginUser().getUser();
         patMedInhosp.setOrgid(user.getOrgid());
         List<PatMedInhosp> list = patMedInhospService.selectPatMedInhospList(patMedInhosp);
-        long count = PageUtils.count(new ISelect() {
-            @Override
-            public void doSelect() {
-                patMedInhospService.selectPatMedInhospList(patMedInhosp);
-            }
-        });
-        return getDataTable2(count, list);
+        // 鍒嗛〉鏌ヨ鏃� PageHelper 宸茬粡鎵ц杩� count锛岃繖閲岀洿鎺ョ敤 PageInfo 鍙栨�绘暟锛岄伩鍏嶅啀璺戜竴娆″叏閲忔煡璇�
+        return getDataTable(list);
     }
 
     /**
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
index ab16600..f5a54a1 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
@@ -50,6 +50,20 @@
     }
 
     /**
+     * 缂撳瓨瀵硅薄锛屼粎褰� key 涓嶅瓨鍦ㄦ椂鎵嶅啓鍏ワ紙鍘熷瓙鎿嶄綔锛夛紝鐢ㄤ簬骞傜瓑浜掓枼鏍囪
+     *
+     * @param key      缂撳瓨鐨勯敭鍊�
+     * @param value    缂撳瓨鐨勫��
+     * @param timeout  鏃堕棿
+     * @param timeUnit 鏃堕棿棰楃矑搴�
+     * @return true=鏈鍐欏叆鎴愬姛锛堜箣鍓嶄笉瀛樺湪锛夛紱false=key 宸插瓨鍦紝鏈啓鍏�
+     */
+    public <T> boolean setCacheObjectIfAbsent(final String key, final T value, final long timeout, final TimeUnit timeUnit) {
+        Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, timeUnit);
+        return Boolean.TRUE.equals(success);
+    }
+
+    /**
      * 璁剧疆鏈夋晥鏃堕棿
      *
      * @param key     Redis閿�
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/HttpUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/HttpUtil.java
index 0310368..ef2d3f3 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/HttpUtil.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/HttpUtil.java
@@ -95,6 +95,50 @@
         return httpClient;
     }
 
+    /**
+     * POST JSON锛屽彧鏍¢獙 HTTP 鐘舵�佺爜锛屽厑璁稿搷搴斾綋涓虹┖銆�
+     * <p>
+     * 鐢ㄤ簬 /hangup 杩欑被"鎴愬姛鍙洖 200銆佹病鏈夊搷搴斾綋"鐨勬帴鍙o細
+     * {@link #postJsonRequest(String, String)} 鍦ㄨ鍝嶅簲浣撴椂濡傛灉涓虹┖浼氭姏 RESPONSE_NULL_ERROR_CODE(999994)锛�
+     * 浼氭妸涓�娆℃垚鍔熺殑鎸傛柇璇垽鎴愬け璐ワ紙杩樹細瑙﹀彂鏃犳剰涔夌殑閲嶈瘯锛夈��
+     * <p>
+     * 闈� 2xx 鏃舵姏鍑虹殑 HttpRequestException 閲屽甫涓婄姸鎬佺爜鍜屽搷搴斾綋锛屾柟渚挎帓鏌ャ��
+     *
+     * @param url     璇锋眰鍦板潃
+     * @param request json 璇锋眰浣�
+     */
+    public static void postJsonRequestAllowEmptyResponse(String url, String request) throws HttpRequestException {
+        Assert.hasLength(url, "璇锋眰url涓嶈兘涓虹┖瀛楃涓层��");
+        EntityEnclosingMethod httpMethod = new PostMethod(url);
+        httpMethod.addRequestHeader("Content-Type", "application/json; charset=UTF-8");
+        setHeaderRequestId(httpMethod);
+        try {
+            RequestEntity entity = new StringRequestEntity(request, "application/json", "utf-8");
+            httpMethod.setRequestEntity(entity);
+            int resultCode = httpClient.executeMethod(httpMethod);
+            if (resultCode >= 200 && resultCode < 300) {
+                return;
+            }
+            String respJson = "";
+            try {
+                respJson = httpMethod.getResponseBodyAsString();
+            } catch (Exception ignore) {
+                // 璇讳笉鍒板搷搴斾綋涓嶅奖鍝嶅垽鏂紝鐘舵�佺爜宸茬粡鑳借鏄庨棶棰�
+            }
+            throw new HttpRequestException(resultCode, respJson);
+        } catch (UnsupportedEncodingException e) {
+            throw new HttpRequestException(ENCODING_ERROR_CODE, e);
+        } catch (HttpException e) {
+            throw new HttpRequestException(HTTP_ERROR_CODE, e);
+        } catch (IOException e) {
+            throw new HttpRequestException(IO_ERROR_CODE, e);
+        } finally {
+            if (httpMethod != null) {
+                httpMethod.releaseConnection();
+            }
+        }
+    }
+
     public static Boolean postJsonRequest(String url, String request) throws HttpRequestException {
         Assert.hasLength(url, "璇锋眰url涓嶈兘涓虹┖瀛楃涓层��");
         EntityEnclosingMethod httpMethod = new PostMethod(url);
diff --git a/smartor/src/main/java/com/smartor/domain/PatArchiveReq.java b/smartor/src/main/java/com/smartor/domain/PatArchiveReq.java
index ab80bd9..28d7cd5 100644
--- a/smartor/src/main/java/com/smartor/domain/PatArchiveReq.java
+++ b/smartor/src/main/java/com/smartor/domain/PatArchiveReq.java
@@ -232,4 +232,23 @@
     @ApiModelProperty("闄㈠尯")
     @Excel(name = " 闄㈠尯 ")
     private String campusid;
+
+    /**
+     * 鍒涘缓鏃堕棿锛堣寖鍥存煡璇�-寮�濮嬶級
+     * <p>
+     * PatArchiveMapper.xml 閲� selectPatArchiveInfoByAllhosp / ByInhosp / ByPhysical /
+     * ByInhospQC / ByPhysicalQC 绛変互鏈璞′负鍙傛暟锛屼笖閮戒娇鐢ㄤ簡
+     * {@code <if test="createTimeStart != null">}锛屽睘鎬х己澶辨椂 MyBatis 鍙� ContextMap
+     * 浼氳蛋 MetaObject.getValue 骞舵姏 ReflectionException锛圱here is no getter for property ...锛夈��
+     */
+    @ApiModelProperty(value = "鍒涘缓鏃堕棿-寮�濮�")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date createTimeStart;
+
+    /**
+     * 鍒涘缓鏃堕棿锛堣寖鍥存煡璇�-缁撴潫锛�
+     */
+    @ApiModelProperty(value = "鍒涘缓鏃堕棿-缁撴潫")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date createTimeEnd;
 }
diff --git a/smartor/src/main/java/com/smartor/domain/PhoneCallReqYQVO.java b/smartor/src/main/java/com/smartor/domain/PhoneCallReqYQVO.java
index defbbb5..f0ab112 100644
--- a/smartor/src/main/java/com/smartor/domain/PhoneCallReqYQVO.java
+++ b/smartor/src/main/java/com/smartor/domain/PhoneCallReqYQVO.java
@@ -1,5 +1,6 @@
 package com.smartor.domain;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
@@ -165,4 +166,24 @@
     @ApiModelProperty(value = "鏀鹃煶鐨勫師濮嬫枃鏈唴瀹�")
     private String origin;
 
+    /**
+     * 鎾斁缁撴潫锛堜笅鍒掔嚎瀛楁鍏煎锛�
+     * <p>
+     * 骞冲彴涓婂叾瀹冨瓧娈甸兘鏄笅鍒掔嚎椋庢牸锛坋nd_time銆乤sr_text銆乺ecord_path鈥︼級锛岄儴鍒嗙増鏈帹閫佹挱鏀剧姸鎬佹椂鐢ㄧ殑鏄�
+     * play_stop 鑰屼笉鏄� playstop銆傚瓧娈电粦涓嶄笂鏃� playstop 浼氫繚鎸侀粯璁ゅ�� false锛屽鑷�"鎾畬浜�"琚綋鎴�
+     * "鍒氬紑濮嬫挱"锛屾案杩滀笉瑙﹀彂鎸傛柇锛屾墍浠ヨ繖閲屾妸涓ょ鍐欐硶閮芥帴浣忋��
+     */
+    @JsonProperty("play_stop")
+    @ApiModelProperty(value = "鎾斁缁撴潫(涓嬪垝绾垮吋瀹瑰瓧娈�)")
+    private Boolean playStopUnderline;
+
+    /**
+     * 鏈鍥炶皟鏄惁琛ㄧず"鎾斁缁撴潫"銆�
+     * <p>
+     * playstop / play_stop 浠讳竴涓� true 鍗宠涓烘挱鏀剧粨鏉燂紝閬垮厤瀛楁鍚嶅樊寮傛垨瀛楁缂哄け瀵艰嚧鍒ゆ柇澶辩湡銆�
+     */
+    public boolean playStopIsTrue() {
+        return Boolean.TRUE.equals(playstop) || Boolean.TRUE.equals(playStopUnderline);
+    }
+
 }
diff --git a/smartor/src/main/java/com/smartor/mapper/PatMedInhospMapper.java b/smartor/src/main/java/com/smartor/mapper/PatMedInhospMapper.java
index 52a80b7..112dfef 100644
--- a/smartor/src/main/java/com/smartor/mapper/PatMedInhospMapper.java
+++ b/smartor/src/main/java/com/smartor/mapper/PatMedInhospMapper.java
@@ -2,6 +2,7 @@
 
 import java.util.List;
 
+import com.smartor.domain.PatArchivetag;
 import com.smartor.domain.PatMedInhosp;
 import com.smartor.domain.PatMedInhospVO;
 import com.smartor.domain.PatMedReq;
@@ -30,6 +31,14 @@
     public String getTagnameBypatid(Long patid);
 
     /**
+     * 鎸夋。妗圛D鎵归噺鏌ヨ鏍囩鍚嶇О锛堢敤浜庡垪琛ㄩ〉鏇挎崲閫愯鏌ヨ鐨� N+1锛�
+     *
+     * @param patids 妗fID闆嗗悎
+     * @return 姣忎釜妗fID涓�鏉¤褰曪紝tagname 涓鸿妗f鐨勬爣绛惧悕鎷兼帴涓�
+     */
+    public List<PatArchivetag> selectTagnameByPatids(@Param("patids") List<Long> patids);
+
+    /**
      * 鏌ヨ鎮h�呬綇闄㈣褰曞垪琛�
      *
      * @param patMedInhosp 鎮h�呬綇闄㈣褰�
diff --git a/smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java
index d30b56e..4511145 100644
--- a/smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/PatMedInhospServiceImpl.java
@@ -37,6 +37,12 @@
 @Slf4j
 @Service
 public class PatMedInhospServiceImpl implements IPatMedInhospService {
+
+    /**
+     * 鎵归噺鏌ユ爣绛炬椂鍗曟 IN 鐨勬渶澶т釜鏁�
+     */
+    private static final int TAG_QUERY_BATCH_SIZE = 500;
+
     @Autowired
     private PatMedInhospMapper patMedInhospMapper;
     @Autowired
@@ -116,14 +122,51 @@
         }
 
         List<PatMedInhosp> patMedInhospList = patMedInhospMapper.selectPatMedInhospList(patMedInhosp);
-        for (PatMedInhosp patMedInhosp1 : patMedInhospList) {
-            String tagname = patMedInhospMapper.getTagnameBypatid(patMedInhosp1.getPatid());
-            patMedInhosp1.setTagname(tagname);
-        }
+        fillTagnames(patMedInhospList);
         return patMedInhospList;
     }
 
     /**
+     * 鎵归噺鍥炲~鎮h�呮爣绛俱��
+     * <p>
+     * 鍘熸潵鏄嬁鍒板垪琛ㄥ悗閫愯璋冪敤 getTagnameBypatid锛屽睘浜庡吀鍨嬬殑 N+1锛氬垎椤� 10 鏉″氨鏄� 10 娆℃煡璇紝
+     * 闈炲垎椤佃皟鐢紙瀵煎嚭銆佸畾鏃朵换鍔★級鍑犲崈鏉″氨鏄嚑鍗冩鏌ヨ銆傝繖閲屾敼鎴愭寜 patid 鍘婚噸鍚庡垎鎵� IN 鏌ヨ锛�
+     * 涓�鎵规渶澶� {@link #TAG_QUERY_BATCH_SIZE} 涓紝閬垮厤 IN 鍒楄〃杩囬暱銆�
+     *
+     * @param patMedInhospList 寰呭洖濉殑浣忛櫌璁板綍鍒楄〃
+     */
+    private void fillTagnames(List<PatMedInhosp> patMedInhospList) {
+        if (CollectionUtils.isEmpty(patMedInhospList)) {
+            return;
+        }
+        List<Long> patids = patMedInhospList.stream()
+                .map(PatMedInhosp::getPatid)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (patids.isEmpty()) {
+            return;
+        }
+
+        Map<Long, String> tagMap = new HashMap<>(patids.size());
+        for (int i = 0; i < patids.size(); i += TAG_QUERY_BATCH_SIZE) {
+            List<Long> batch = patids.subList(i, Math.min(i + TAG_QUERY_BATCH_SIZE, patids.size()));
+            List<PatArchivetag> tags = patMedInhospMapper.selectTagnameByPatids(batch);
+            if (CollectionUtils.isEmpty(tags)) {
+                continue;
+            }
+            for (PatArchivetag tag : tags) {
+                if (tag.getPatid() != null) {
+                    tagMap.put(tag.getPatid(), tag.getTagname());
+                }
+            }
+        }
+        for (PatMedInhosp patMedInhosp : patMedInhospList) {
+            patMedInhosp.setTagname(tagMap.get(patMedInhosp.getPatid()));
+        }
+    }
+
+    /**
      * 褰掍竴鍖栧叆闄㈠簥浣嶆帓搴忓弬鏁�
      * <p>
      * asc / 1 -> asc锛堝簥浣嶆搴忥級锛宒esc / 2 -> desc锛堝簥浣嶅�掑簭锛夛紝鍏朵綑锛堝惈涓嶄紶锛�-> null锛堜笉鎺掑簭锛夈��
diff --git a/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
index 3f9709d..c7f170a 100644
--- a/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskServiceImpl.java
@@ -60,6 +60,8 @@
 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;
@@ -185,6 +187,30 @@
 
     @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;
@@ -1996,6 +2022,130 @@
     }
 
     /**
+     * 缁撴潫璇紦瀛� 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>
+     * 骞冲彴鍥炶皟锛圥layEventCallback 鎾斁缁撴潫锛夊拰鍏滃簳瀹氭椂浠诲姟閮戒細璋冭繖閲岋紝
+     * 閫氳繃 redis 鐨� {uuid}- hangup-sent 鏍囪淇濊瘉鍚屼竴娆¢�氳瘽鍙湡姝f寕鏂竴娆°��
+     *
+     * @param uuid   閫氳瘽 uuid
+     * @param phone  琚彨鍙风爜
+     * @param reason 鎸傛柇鍘熷洜锛堜粎鐢ㄤ簬鏃ュ織鎺掓煡锛�
+     * @return true=鏈纭疄鍙戝嚭浜嗘寕鏂姹�
+     */
+    private boolean doHangup(String uuid, String phone, String reason) {
+        if (StringUtils.isEmpty(uuid)) {
+            log.warn("鎸傛柇璺宠繃锛歶uid 涓虹┖, 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 浼氬洜"鍝嶅簲涓簄ull"鎶婃垚鍔熻鍒ゆ垚澶辫触
+                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
@@ -2005,6 +2155,15 @@
         //鍏堟妸璇ubId浠巆ache_exist涓墧闄わ紝鍙鏈夊洖璋冨氨鍓旈櫎锛堢洰鐨勬槸锛屽敖蹇cache-exist涓虹┖锛屽幓缂撳瓨涓嬩竴鎵硅鎵撶殑鐢佃瘽;杩欓噷鍓旈櫎涓嶅奖鍝嶇數璇濈殑姝e父涓氬姟锛�
 //        redisCache.removeElementFromList("cache-exist", phoneCallReqYQVO.getTaskid());
 
+        //uuid / taskid / phone 鏄悗闈㈡墍鏈夐�昏緫鐨勫墠鎻愶紝缂轰簡灏辩洿鎺ュ拷鐣ワ紝閬垮厤 Long.valueOf(null)銆乶ull.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");
@@ -2013,7 +2172,7 @@
         }
         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")) {
@@ -2024,7 +2183,9 @@
                 //濡傛灉鍙戦�佺姸鎬佷负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();
@@ -2032,11 +2193,8 @@
                 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(), "鎷ㄥ彨鐨勭敤鎴锋蹇�");
                 //鍏堟妸榛樿鐨剅ecordAccept-hungup鍒犻櫎锛屽啀鏇存柊
                 redisCache.deleteObject(phoneCallReqYQVO.getTaskid() + "recordAccept-hungup");
                 redisCache.setCacheObject(phoneCallReqYQVO.getTaskid() + "recordAccept-hungup", "1", 10, TimeUnit.MINUTES);
@@ -2049,36 +2207,53 @@
                 return new PhoneCallBackYQVO();
             }
         }
+        //缁撴潫璇凡缁忎笅鍙戣繃锛岃鏄庢湰娆″璇濆凡缁忔敹灏撅細瀹夋帓涓�娆″厹搴曟寕鏂��
+        //骞冲彴姝e父鎯呭喌涓嬩細鍐嶅洖璋� 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鎴杗ull琛ㄧず褰撳墠鐨刟srtext涓嶆槸涓�鍙ュ畬鏁寸殑璇�
-        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 {
+                    //骞冲彴寮�濮嬫挱缁撴潫璇紝鍏滃簳鎸傛柇鍦ㄦ柟娉曞叆鍙e凡缁忓畨鎺掕繃锛岃繖閲屽彧鐣欐棩蹇椾究浜庢帓鏌�
+                    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", "閫氳瘽姝e父缁撴潫", "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", "閫氳瘽姝e父缁撴潫", "9");
+                        } catch (Exception e) {
+                            log.error("鎸傛柇鍚庢洿鏂板瓙浠诲姟鐘舵�佸け璐�, taskid={}", phoneCallReqYQVO.getTaskid(), e);
+                        }
+                    }
                 } else {
                     redisCache.setCacheObject(phoneCallReqYQVO.getUuid() + "PlayEventCallbackPlaystop", true, 120, TimeUnit.MINUTES);
                     log.error("-------PlayEventCallbackPlaystop鐨勫�间负锛歿}", true);
@@ -2098,6 +2273,10 @@
 
         //閫氳繃瀛愪换鍔D鑾峰彇鍒版ā鏉夸俊鎭�
         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();
@@ -2106,8 +2285,24 @@
         //鑾峰彇闂ID 鍜� 搴忓彿
         String scriptId = redisCache.getCacheObject(phoneCallReqYQVO.getTaskid().trim() + "-" + phoneCallReqYQVO.getPhone().trim());
         log.error("scriptId鏄灏戯細{}", scriptId);
+        if (StringUtils.isEmpty(scriptId)) {
+            //褰撳墠娌℃湁"姝e湪闂殑闂"锛岃鏄庢湰娆¢�氳瘽宸茬粡鏀跺熬锛堢粨鏉熻宸蹭笅鍙戝悗锛屽钩鍙拌繕浼氳ˉ Silent/Asr 鍥炶皟锛夛紝
+            //鍘熸潵杩欓噷鐩存帴 Long.valueOf(null) 浼氭姏 NumberFormatException 璁╁洖璋� 500銆�
+            //杩欓噷鐩存帴蹇界暐锛屽苟纭繚鍏滃簳鎸傛柇宸茬粡鎺掍笂锛岄伩鍏嶇數璇濈户缁寕鐫�銆�
+            log.warn("鏈彇鍒板綋鍓嶉棶棰業D锛屾寜閫氳瘽宸叉敹灏惧鐞�, 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) {
@@ -2380,9 +2575,10 @@
                             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");
@@ -2508,7 +2704,8 @@
                     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");
@@ -2938,7 +3135,9 @@
     @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()));
@@ -2951,9 +3150,13 @@
 //        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);
diff --git a/smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml b/smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml
index 329cf76..21a0367 100644
--- a/smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml
+++ b/smartor/src/main/resources/mapper/smartor/PatArchiveMapper.xml
@@ -215,10 +215,10 @@
             <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')
+                AND create_time &gt;= date(#{createTimeStart})
             </if>
             <if test="createTimeEnd != null">
-                AND date_format(create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
+                AND create_time &lt; date(#{createTimeEnd}) + interval 1 day
             </if>
         <if test="pageSize != null  and pageNum != null">limit ${pageSize} OFFSET ${pageNum}</if>
     </select>
@@ -269,10 +269,10 @@
             <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')
+                AND a.create_time &gt;= date(#{createTimeStart})
             </if>
             <if test="createTimeEnd != null">
-                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
+                AND a.create_time &lt; date(#{createTimeEnd}) + interval 1 day
             </if>
             <if test="tagIds != null  and tagIds != ''">and c.tagid in
                 <foreach collection="tagIds" item="tagId" open="(" separator="," close=")">
@@ -556,10 +556,10 @@
                 </foreach>
             </if>
             <if test="createTimeStart != null">
-                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
+                AND a.create_time &gt;= date(#{createTimeStart})
             </if>
             <if test="createTimeEnd != null">
-                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
+                AND a.create_time &lt; date(#{createTimeEnd}) + interval 1 day
             </if>
         order by a.update_time desc
         <if test="pn != null  and ps != null">limit ${pn},${ps}</if>
@@ -631,7 +631,7 @@
             and d.orgid = #{orgid}
             <if test="name != null  and name != ''">and a.name like concat('%', #{name}, '%')</if>
             <if test="idcardno != null  and idcardno != ''">and a.idcardno = #{idcardno}</if>
-            <if test="pid != null  and pid != ''">and a.id = #{id}</if>
+            <if test="pid != null  and pid != ''">and a.id = #{pid}</if>
             <if test="campusid != null  and campusid != ''">and a.campusid = #{campusid}</if>
             <if test="telcode != null  and telcode != ''">and a.telcode = #{telcode}</if>
             <if test="notrequiredFlag != null  and notrequiredFlag != ''">and a.notrequired_flag = #{notrequiredFlag}
@@ -793,10 +793,10 @@
                 </foreach>
             </if>
             <if test="createTimeStart != null">
-                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
+                AND a.create_time &gt;= date(#{createTimeStart})
             </if>
             <if test="createTimeEnd != null">
-                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
+                AND a.create_time &lt; date(#{createTimeEnd}) + interval 1 day
             </if>
             <if test="isFilter == true">
                 AND NOT exists ( SELECT 1 FROM pat_filterlist f where d.patid=f.patid)
@@ -890,10 +890,10 @@
                 </foreach>
             </if>
             <if test="createTimeStart != null">
-                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
+                AND a.create_time &gt;= date(#{createTimeStart})
             </if>
             <if test="createTimeEnd != null">
-                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
+                AND a.create_time &lt; date(#{createTimeEnd}) + interval 1 day
             </if>
             <if test="isFilter == true">
                 AND NOT exists ( SELECT 1 FROM pat_filterlist f where d.patid=f.patid)
@@ -968,15 +968,15 @@
             and d.orgid = #{orgid}
             <if test="name != null  and name != ''">and a.name like concat('%', #{name}, '%')</if>
             <if test="idcardno != null  and idcardno != ''">and a.idcardno = #{idcardno}</if>
-            <if test="pid != null  and pid != ''">and a.id = #{id}</if>
+            <if test="pid != null  and pid != ''">and a.id = #{pid}</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')
+                AND a.create_time &gt;= date(#{createTimeStart})
             </if>
             <if test="createTimeEnd != null">
-                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
+                AND a.create_time &lt; date(#{createTimeEnd}) + interval 1 day
             </if>
             <if test="hospitalname != null and hospitalname != ''">
                 AND d.hospitalname = #{hospitalname}
@@ -1228,10 +1228,10 @@
                 </foreach>
             </if>
             <if test="createTimeStart != null">
-                AND date_format(a.create_time,'%y%m%d') &gt;= date_format(#{createTimeStart},'%y%m%d')
+                AND a.create_time &gt;= date(#{createTimeStart})
             </if>
             <if test="createTimeEnd != null">
-                AND date_format(a.create_time,'%y%m%d') &lt;= date_format(#{createTimeEnd},'%y%m%d ')
+                AND a.create_time &lt; date(#{createTimeEnd}) + interval 1 day
             </if>
             <if test="isFilter == true">
                 AND NOT exists ( SELECT 1 FROM pat_filterlist f where d.patid=f.patid)
diff --git a/smartor/src/main/resources/mapper/smartor/PatMedInhospMapper.xml b/smartor/src/main/resources/mapper/smartor/PatMedInhospMapper.xml
index 9a47f1f..f241ff6 100644
--- a/smartor/src/main/resources/mapper/smartor/PatMedInhospMapper.xml
+++ b/smartor/src/main/resources/mapper/smartor/PatMedInhospMapper.xml
@@ -247,18 +247,15 @@
         AND b.del_flag = 0
         AND a.idcardno IS NOT NULL
         <if test="inhospid != null">and b.inhospid = #{inhospid}</if>
-        <if test="startOutHospTime != null ">and date_format(b.endtime,'%y%m%d') &gt;=
-            date_format(#{startOutHospTime},'%y%m%d')
-        </if>
-        <if test="endOutHospTime != null ">and date_format(b.endtime,'%y%m%d') &lt;=
-            date_format(#{endOutHospTime},'%y%m%d')
-        </if>
-        <if test="startInHospTime != null ">and date_format(b.starttime,'%y%m%d') &gt;=
-            date_format(#{startInHospTime},'%y%m%d')
-        </if>
-        <if test="endInHospTime != null ">and date_format(b.starttime,'%y%m%d') &lt;=
-            date_format(#{endInHospTime},'%y%m%d')
-        </if>
+        <!--
+          鏃堕棿鑼冨洿涓嶈鍐欐垚 date_format(b.endtime,'%y%m%d') >= date_format(#{...},'%y%m%d')锛�
+          鍒椾笂濂楀嚱鏁颁細璁� endtime / starttime 涓婄殑绱㈠紩褰诲簳澶辨晥锛堣�屼笖 %y 鏄袱浣嶅勾浠斤級銆�
+          杩欓噷鎶婂嚱鏁版尓鍒板弬鏁颁晶锛岃涔夌瓑浠凤細>= 褰撳ぉ 0 鐐癸紝< 娆℃棩 0 鐐广��
+        -->
+        <if test="startOutHospTime != null ">and b.endtime &gt;= date(#{startOutHospTime})</if>
+        <if test="endOutHospTime != null ">and b.endtime &lt; date(#{endOutHospTime}) + interval 1 day</if>
+        <if test="startInHospTime != null ">and b.starttime &gt;= date(#{startInHospTime})</if>
+        <if test="endInHospTime != null ">and b.starttime &lt; date(#{endInHospTime}) + interval 1 day</if>
         <if test="hospitalname != null  and hospitalname != ''">and hospitalname like concat('%', #{hospitalname},
             '%')
         </if>
@@ -276,7 +273,6 @@
         <if test="orgid != null ">and b.orgid = #{orgid}</if>
         <if test="campusid != null ">and b.campusid = #{campusid}</if>
         <if test="serialnum != null ">and b.serialnum = #{serialnum}</if>
-        <if test="endtime != null ">and b.endtime = #{endtime}</if>
         <if test="patno != null ">and b.patno = #{patno}</if>
         <if test="nurseId != null ">and b.nurse_id = #{nurseId}</if>
         <if test="nurseName != null and nurseName != ''">and b.nurse_name = #{nurseName}</if>
@@ -353,20 +349,12 @@
         <if test="medicalCode != null and medicalCode != ''">and b.medical_code = #{medicalCode}</if>
         <if test="medicalName != null and medicalName != ''">and b.medical_name = #{medicalName}</if>
         <if test="medicalCompany != null and medicalCompany != ''">and b.medical_company = #{medicalCompany}</if>
-        GROUP BY
-        b.inhospid,
-        a.age,
-        a.age_unit,
-        a.telcode,
-        a.id,
-        a.sex
-        <if test="cry != null and cry==1">
-            order by b.endtime desc
-        </if>
-        <if test="cry != null and cry==2">
-            order by b.endtime desc
-        </if>
-        <if test="cry != null and cry==3">
+        <!--
+          鍘熸潵杩欓噷鏈変竴娈� GROUP BY b.inhospid, a.age, a.age_unit, a.telcode, a.id, a.sex銆�
+          b.inhospid 鏄富閿�乤.id = b.patid 鏄竴瀵逛竴鍏宠仈锛屽垎缁勪笉浼氬悎骞朵换浣曡锛屽彧浼氳 MySQL
+          澶氬缓涓�寮犱复鏃惰〃銆佸苟璁╁垎椤电殑 count 鍙樻垚娲剧敓琛ㄣ�傝繖閲岀洿鎺ュ幓鎺夈��
+        -->
+        <if test="cry != null and (cry == 1 or cry == 2 or cry == 3)">
             order by b.endtime desc
         </if>
         <if test="cry != null and cry==0">
@@ -661,6 +649,19 @@
           AND d.del_flag = 0
     </select>
 
+    <!-- 鎵归噺鏌ユ爣绛撅紝鏇夸唬鍒楄〃閫愯璋冪敤 getTagnameBypatid 鐨� N+1 鏌ヨ -->
+    <select id="selectTagnameByPatids" resultType="com.smartor.domain.PatArchivetag">
+        SELECT d.patid      AS patid,
+               GROUP_CONCAT(d.tagname, ', ') AS tagname
+        FROM pat_archivetag d
+        WHERE d.del_flag = 0
+          AND d.patid IN
+        <foreach collection="patids" item="patid" open="(" separator="," close=")">
+            #{patid}
+        </foreach>
+        GROUP BY d.patid
+    </select>
+
 
     <select id="selectPatMedInhospByInhospid" parameterType="Long" resultMap="PatMedInhospResult">
         <include refid="selectPatMedInhospVo"/>

--
Gitblit v1.9.3