From 68ff33beff11310e830b0615b5a81f83fad0f683 Mon Sep 17 00:00:00 2001
From: yxh <172933527@qq.com>
Date: 星期日, 08 十一月 2026 23:33:14 +0800
Subject: [PATCH] ai优化

---
 jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java |  546 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 533 insertions(+), 13 deletions(-)

diff --git a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java
index ec1e59b..1305092 100644
--- a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java
+++ b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java
@@ -2,14 +2,18 @@
 
 import cn.lihu.jh.module.ecg.Utils;
 import cn.lihu.jh.module.ecg.dal.dataobject.checktype.CheckTypeDO;
+import cn.lihu.jh.module.ecg.dal.dataobject.queuesequence.SeqCounterDO;
 import cn.lihu.jh.module.ecg.dal.mysql.checktype.CheckTypeMapper;
+import cn.lihu.jh.module.ecg.enums.SeqRuleEnum;
 import cn.lihu.jh.module.infra.api.config.ConfigApi;
 import cn.lihu.jh.module.system.api.dict.DictDataApi;
 import cn.lihu.jh.module.system.api.dict.dto.DictDataRespDTO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDate;
 import java.time.LocalTime;
 import java.util.*;
 import cn.lihu.jh.module.ecg.controller.admin.queuesequence.vo.*;
@@ -17,6 +21,7 @@
 import cn.lihu.jh.framework.common.pojo.PageResult;
 import cn.lihu.jh.framework.common.pojo.PageParam;
 import cn.lihu.jh.framework.common.util.object.BeanUtils;
+import cn.lihu.jh.framework.mybatis.core.query.LambdaQueryWrapperX;
 
 import cn.lihu.jh.module.ecg.dal.mysql.queuesequence.QueueSequenceMapper;
 
@@ -33,7 +38,16 @@
  */
 @Service
 @Validated
+@Slf4j
 public class QueueSequenceServiceImpl implements QueueSequenceService {
+
+    /**
+     * 鍚戝悗鏌ユ壘鍙敤鏃舵鐨勬渶澶ф鏁帮紙绾� 3 灏忔椂锛夈��
+     * <p>
+     * 鍙� 6 鐨勫師鍥狅細瀛楀吀鏃舵涓� 30 鍒嗛挓绮掑害锛�6 涓害 3 灏忔椂锛�
+     * 鏃㈣兘璺ㄨ繃"灏戞暟鏃舵缂哄け/宸叉弧"鐨勬儏鍐碉紝鍙堜笉浼氭妸涓婂崍鐨勬偅鑰呮帓鍒颁笅鍗堥�犳垚浣撻獙闂銆�
+     */
+    private static final int MAX_TIMESLOT_LOOKAHEAD = 6;
 
     @Resource
     private ConfigApi configApi;
@@ -47,11 +61,55 @@
     @Resource
     private CheckTypeMapper checkTypeMapper;
 
+    /**
+     * 鐢ㄤ簬鎶娿�岃鏁板櫒鎺ㄨ繘 + 璇诲洖銆嶆樉寮忓寘杩�**鍚屼竴涓簨鍔�**銆�
+     *
+     * <h4>涓轰粈涔堝繀椤荤敤 TransactionTemplate 鑰屼笉鏄� @Transactional锛堥噸瑕侊級</h4>
+     * 杩欎袱姝ュ湪 {@link #nextCheckInSeqNo} 杩欎釜 **private 鏂规硶**閲岋紝涓旂敱**鍚岀被鍐呴儴璋冪敤**
+     * 锛坽@code distributeSeqNoByRule} 鈫� {@code nextCheckInSeqNo}锛夈��
+     * Spring 鐨� {@code @Transactional} 鍩轰簬浠g悊锛�**鍚岀被鑷皟鐢ㄤ笉浼氳蛋浠g悊 鈬� 娉ㄨВ瀹屽叏涓嶇敓鏁�**銆�
+     * 浜庢槸"鎺ㄨ繘"涓�"璇诲洖"浼氬悇鑷嫭绔嬫彁浜わ紝鍦� REPEATABLE READ 涓嬭鍒扮殑鍙兘鏄棫蹇収
+     * 鈬� **骞跺彂閲嶅彿**锛堟棭鍏堝疄娴嬶細鐙珛浜嬪姟鍙湁 75/160 涓敮涓�鍊硷紱鍚屼簨鍔′负 160/160锛夈��
+     *
+     * 鏁呰繖閲岀敤 {@link TransactionTemplate} **鏄惧紡**寮�鍚簨鍔★紝涓嶄緷璧� AOP 浠g悊锛屽繀鐒剁敓鏁堛��
+     * 浼犳挱琛屼负鐢ㄩ粯璁ょ殑 REQUIRED锛氬灞傚凡鏈変簨鍔★紙濡� {@code distributeSeqNoByRule}
+     * 鏍囨敞浜� {@code @Transactional}锛夋椂骞跺叆锛屾病鏈夋椂鏂板缓銆�
+     */
+    @Resource
+    private org.springframework.transaction.support.TransactionTemplate counterTransactionTemplate;
+
+    @Override
+    public void initQueueSequenceTable() {
+        queueSequenceMapper.clearQueueSequenceTableNotCurrent();
+        Integer rowCount = queueSequenceMapper.getQueueSequenceTableRowCount();
+        if (null == rowCount || 0 == rowCount) {
+            resetQueueSequence();
+        }
+    }
+
+    /**
+     * 鏂板棰勭害鏃舵瑙勫垯銆�
+     *
+     * <p>鏍¢獙銆屽悓涓�妫�鏌ョ被鍨� + 鍚屼竴鏃舵銆嶄笉閲嶅 鈥斺�� 琛ㄤ笂娌℃湁璇ュ敮涓�绾︽潫锛堝巻鍙查仐鐣欙級锛�
+     * 鑰屽彇鍙� SQL {@code select queue_no ... where check_type=? and time_slot=? for update}
+     * 鏈熸湜鍙懡涓竴琛岋紱涓�鏃﹀嚭鐜伴噸澶嶈锛屽彇鍙蜂細鍛戒腑涓嶇‘瀹氱殑涓�琛屻��
+     * 杩欓噷鍦ㄧ淮鎶ゅ叆鍙e鎷︽埅锛岀粰鍑烘槑纭彁绀鸿�屼笉鏄闂杩涘叆鍙栧彿闃舵銆�
+     */
     @Override
     public Integer createQueueSequence(QueueSequenceSaveReqVO createReqVO) {
+        validateTimeslotNotDuplicated(createReqVO.getCheckType(), createReqVO.getTimeSlot(), null);
         // 鎻掑叆
         QueueSequenceDO queueSequence = BeanUtils.toBean(createReqVO, QueueSequenceDO.class);
+        if (null == queueSequence.getTimeSlot() || 0 != queueSequence.getTimeSlot()) {
+            // 銆愭湰闇�姹傘�戝彿娈佃捣鐐� / 棰勭暀璧风偣鐢辩郴缁熻绠楋紝涓嶆帴鍙楀墠绔殢鎰忎紶鍊硷紙鍚﹀垯鍙锋浼氶噸鍙狅級
+            queueSequence.setQueueSet(null);
+            queueSequence.setQueueVipSet(null);
+        }
         queueSequenceMapper.insert(queueSequence);
+        // 銆愭湰闇�姹傘�戞柊澧�/缂栬緫鍚庣粺涓�閲嶆帓璇ユ鏌ョ被鍨嬬殑鍙锋锛堜繚璇�"娈典笌娈甸灏剧浉鎺�"锛�
+        if (null != createReqVO.getCheckType()) {
+            recalcSegmentBounds(createReqVO.getCheckType());
+        }
         // 杩斿洖
         return queueSequence.getId();
     }
@@ -60,17 +118,142 @@
     public void updateQueueSequence(QueueSequenceSaveReqVO updateReqVO) {
         // 鏍¢獙瀛樺湪
         validateQueueSequenceExists(updateReqVO.getId());
+        // 銆愭牎楠屻�戞敼浜� checkType 鎴� timeSlot 鏃朵笉鑳戒笌鍏跺畠琛屾挒杞︼紙鎺掗櫎鑷韩锛�
+        validateTimeslotNotDuplicated(updateReqVO.getCheckType(), updateReqVO.getTimeSlot(),
+                updateReqVO.getId());
         // 鏇存柊
         QueueSequenceDO updateObj = BeanUtils.toBean(updateReqVO, QueueSequenceDO.class);
+        // 銆愭湰闇�姹傘�戣捣濮嬪簭鍙风敱绯荤粺閲嶆帓锛屽拷鐣ュ墠绔紶鍊硷紙鍓嶇宸茬疆涓哄彧璇伙級
+        updateObj.setQueueSet(null);
+        updateObj.setQueueVipSet(null);
         queueSequenceMapper.updateById(updateObj);
+        if (null != updateReqVO.getCheckType()) {
+            recalcSegmentBounds(updateReqVO.getCheckType());
+        }
+    }
+
+    /**
+     * 銆愭湰闇�姹傘�戞寜銆屾椂娈靛崌搴忋�嶉噸鎺掓煇妫�鏌ョ被鍨嬬殑鍙锋锛�
+     * <pre>
+     *   娈� i锛歲ueue_set = 涓婁竴娈电殑 queue_vip_full锛堥娈� 0锛�
+     *         queue_full = queue_set + P
+     *         queue_vip_set = queue_full
+     *         queue_vip_full = queue_vip_set + V
+     *   鍏朵腑 P / V = 璇ヨ褰撳墠鐨勶紙queue_full - queue_set锛夈�侊紙queue_vip_full - queue_full锛夛紝
+     *   鍗冲悇娈电殑"棰濆害"淇濇寔涓嶅彉锛屽彧鎶婅捣鐐逛覆璧锋潵銆�
+     * </pre>
+     * 杩欐牱鏃犺鏂板/鍒犻櫎/缂栬緫鏃舵锛岄兘涓嶄細鍑虹幇鍙锋閲嶅彔鎴栫┖娲為敊浣嶃��
+     * <p>鎬婚鐣欐睜锛坱ime_slot = 0锛変笉鍙備笌閲嶆帓锛屽彧鎶婅捣鐐瑰榻愬埌鏈�鍚庝竴娈典箣鍚庛��
+     */
+    private void recalcSegmentBounds(Integer checkType) {
+        if (null == checkType) {
+            return;
+        }
+        List<QueueSequenceDO> all = queueSequenceMapper.selectTimeslotByCheckType(checkType);
+        if (null == all || all.isEmpty()) {
+            return;
+        }
+        all.sort(Comparator.comparingInt(r -> null == r.getTimeSlot() ? 0 : r.getTimeSlot()));
+
+        CheckTypeDO ct = checkTypeMapper.getCheckTypeByValue(checkType);
+
+        int cursor = 0;
+        int slotCount = 0;
+        for (QueueSequenceDO row : all) {
+            int slot = null == row.getTimeSlot() ? 0 : row.getTimeSlot();
+            if (0 == slot) {
+                continue;   // 鎬婚鐣欐睜鏈�鍚庡崟鐙鐞�
+            }
+            slotCount++;
+
+            // 棰濆害锛堝锛夛細浼樺厛娌跨敤璇ヨ鐜版湁鍙锋瀹藉害锛涙湭鐭�/闈炴鏃跺洖閫�鍒版鏌ョ被鍨嬮厤缃�
+            int p = width(row.getQueueSet(), row.getQueueFull());
+            if (p <= 0) {
+                p = (null == ct || null == ct.getTimeslotBookNum()) ? 0 : ct.getTimeslotBookNum();
+            }
+            int v = width(row.getQueueFull(), row.getQueueVipFull());
+            if (v < 0) {
+                v = (null == ct || null == ct.getTimeslotReservedNum()) ? 0 : ct.getTimeslotReservedNum();
+            }
+
+            QueueSequenceDO update = new QueueSequenceDO();
+            update.setId(row.getId());
+            update.setQueueSet(cursor);
+            update.setQueueFull(cursor + p);
+            update.setQueueVipSet(cursor + p);
+            update.setQueueVipFull(cursor + p + v);
+            // 娓告爣鍙�**涓婃姮**鍒板彿娈佃捣鐐癸紝缁濅笉**涓嬭皟**锛�
+            // 涓嬭皟浼氳宸插彂鍑虹殑鍙疯鍐嶆鍙戝嚭锛堥噸鍙凤級銆傝嫢娓告爣宸茶秴杩囦笂闄愶紝璇存槑璇ユ宸茶秴鍙戯紝
+            // 鍙栧彿鏃惰嚜鐒朵細钀藉埌銆岄鐣欏彿 鈫� 鎬婚鐣欐睜銆嶃��
+            update.setQueueNo(raiseAtLeast(row.getQueueNo(), cursor));
+            update.setQueueVipNo(raiseAtLeast(row.getQueueVipNo(), cursor + p));
+            queueSequenceMapper.updateById(update);
+
+            cursor = cursor + p + v;
+        }
+
+        // 鎬婚鐣欐睜锛氳捣鐐瑰榻愬埌鏈�鍚庝竴娈典箣鍚�
+        QueueSequenceDO pool = queueSequenceMapper.selectTotalPool(checkType);
+        if (null != pool) {
+            int poolFull = Math.max(null == pool.getQueueVipFull() ? 0 : pool.getQueueVipFull(), cursor);
+            QueueSequenceDO update = new QueueSequenceDO();
+            update.setId(pool.getId());
+            update.setQueueVipSet(cursor);
+            update.setQueueVipFull(poolFull);
+            update.setQueueVipNo(raiseAtLeast(pool.getQueueVipNo(), cursor));
+            queueSequenceMapper.updateById(update);
+        }
+        log.info("[recalcSegmentBounds] 宸查噸鎺掑彿娈�. checkType={} 娈垫暟={} 鏈熬鏈�澶у彿={}",
+                checkType, slotCount, cursor);
+    }
+
+    /** 鍙锋瀹藉害锛堢粓姝� - 璧峰锛夛紝浠讳竴涓� null 杩斿洖 -1 琛ㄧず"鏈煡" */
+    private int width(Integer from, Integer to) {
+        if (null == from || null == to) {
+            return -1;
+        }
+        return to - from;
+    }
+
+    /**
+     * 娓告爣鍙笂鎶�佷笉涓嬭皟锛堜笅璋冧細瀵艰嚧宸插彂鍑虹殑鍙疯鍐嶆鍙戝嚭 鈬� 閲嶅彿锛夈��
+     */
+    private Integer raiseAtLeast(Integer value, int min) {
+        int v = null == value ? min : value;
+        return Math.max(v, min);
+    }
+
+    /**
+     * 鏍¢獙銆屾鏌ョ被鍨� + 鏃舵銆嶅湪琛ㄤ腑鍞竴銆�
+     *
+     * @param excludeId 闇�鎺掗櫎鐨勮 id锛堟洿鏂版椂浼犺嚜韬� id锛涙柊澧炴椂浼� null锛�
+     */
+    private void validateTimeslotNotDuplicated(Integer checkType, Integer timeSlot, Integer excludeId) {
+        if (null == checkType || null == timeSlot) {
+            return;   // 鐢� VO 涓婄殑 @NotNull 鍏滃簳锛岃繖閲屼笉閲嶅鎶ラ敊
+        }
+        QueueSequenceDO exist = queueSequenceMapper.selectOne(
+                new LambdaQueryWrapperX<QueueSequenceDO>()
+                        .eq(QueueSequenceDO::getCheckType, checkType)
+                        .eq(QueueSequenceDO::getTimeSlot, timeSlot));
+        if (null != exist && !exist.getId().equals(excludeId)) {
+            log.warn("[validateTimeslotNotDuplicated] 鏃舵瑙勫垯閲嶅. checkType={} timeSlot={} "
+                            + "宸插瓨鍦╥d={} 鏈id={}", checkType, timeSlot, exist.getId(), excludeId);
+            throw exception(QUEUE_SEQUENCE_TIMESLOT_DUPLICATE);
+        }
     }
 
     @Override
     public void deleteQueueSequence(Integer id) {
         // 鏍¢獙瀛樺湪
-        validateQueueSequenceExists(id);
+        QueueSequenceDO exist = queueSequenceMapper.selectById(id);
+        if (null == exist) {
+            throw exception(QUEUE_SEQUENCE_NOT_EXISTS);
+        }
         // 鍒犻櫎
         queueSequenceMapper.deleteById(id);
+        // 銆愭湰闇�姹傘�戝垹闄ゅ悗閲嶆帓鍙锋锛堝惁鍒欏悗缁椂娈电殑璧风偣浼氱暀绌烘礊锛�
+        recalcSegmentBounds(exist.getCheckType());
     }
 
     private void validateQueueSequenceExists(Integer id) {
@@ -82,6 +265,11 @@
     @Override
     public QueueSequenceDO getQueueSequence(Integer id) {
         return queueSequenceMapper.selectById(id);
+    }
+
+    @Override
+    public List<QueueSequenceDO> selectGivenCheckTypeTimeslot(Integer checkType) {
+        return queueSequenceMapper.selectTimeslotByCheckType( checkType );
     }
 
     @Override
@@ -122,33 +310,365 @@
     @Override
     @Transactional
     public Integer distributeSeqNo(Integer checkType, Integer timeslot) {
+        // 銆愭湰闇�姹傦細鎸夋椂娈甸绾︺�戞櫘閫氭偅鑰呬笁绾у彇鍙凤紝**涓嶅啀璺ㄦ鍊熷彿**锛�
+        //   鈶� 鏈椂娈垫櫘閫氬彿锛坬ueue_no   < queue_full锛�
+        //   鈶� 鏈椂娈甸鐣欏彿锛坬ueue_vip_no < queue_vip_full锛�
+        //   鈶� 鎬婚鐣欐睜锛坱ime_slot=0锛�**涓嶈涓婇檺**锛�
+        // 浠讳綍涓�绾ф垚鍔熷嵆杩斿洖锛涒憿 姘镐笉澶辫触 鈬� 涓嶅瓨鍦�"绛句笉涓�"銆�
+        // 鏃犻绾︽椂娈碉紙timeslot 涓� null/0/瀛楀吀澶栫殑鍊硷級鏃� 鈶犫憽 鑷劧鏌ヤ笉鍒拌锛岀洿鎺ヨ惤鍒� 鈶€��
+
+        // 鈶� 鏈鏅�氬彿
         Integer curSeqNo = queueSequenceMapper.selectQueueNoForUpdate(checkType, timeslot);
-        if (null == curSeqNo) {
-            throw exception(QUEUE_SEQUENCE_TIMESLOT_FULL);
+        if (null != curSeqNo) {
+            Integer updateNum = queueSequenceMapper.updateGivenCheckTypeTimeslotSeqNo(checkType, timeslot, curSeqNo);
+            if (null != updateNum && updateNum > 0) {
+                return curSeqNo + 1;
+            }
+            // 骞跺彂琚姠绌� 鈫� 钀藉埌 鈶�
+            log.info("[distributeSeqNo] 鏈鏅�氬彿骞跺彂琚姠绌猴紝闄嶇骇鍒伴鐣欏彿. checkType={} timeslot={}", checkType, timeslot);
         }
 
-        Integer updateRowNum = queueSequenceMapper.updateGivenCheckTypeTimeslotSeqNo(checkType, timeslot, curSeqNo);
-        if (null == updateRowNum || updateRowNum == 0) {
-            throw exception(QUEUE_SEQUENCE_TIMESLOT_EXCEPTION);
+        // 鈶� 鏈棰勭暀鍙�
+        Integer curVipNo = queueSequenceMapper.selectQueueVipNoForUpdate(checkType, timeslot);
+        if (null != curVipNo) {
+            Integer updateNum = queueSequenceMapper.updateGivenCheckTypeTimeslotVipSeqNo(checkType, timeslot, curVipNo);
+            if (null != updateNum && updateNum > 0) {
+                log.info("[distributeSeqNo] 鏅�氬彿宸叉弧锛屾敼鍙戞湰娈甸鐣欏彿. checkType={} timeslot={} seqNo={}",
+                        checkType, timeslot, curVipNo + 1);
+                return curVipNo + 1;
+            }
+            log.info("[distributeSeqNo] 鏈棰勭暀鍙峰苟鍙戣鎶㈢┖锛岄檷绾у埌鎬婚鐣欐睜. checkType={} timeslot={}", checkType, timeslot);
         }
 
-        return curSeqNo + 1;
+        // 鈶� 鎬婚鐣欐睜锛堜笉璁句笂闄愶紝淇濊瘉涓�瀹氳兘绛惧埌锛�
+        return nextTotalPoolSeqNo(checkType, timeslot);
     }
-
+    /**
+     * 銆愭湰闇�姹傘�慥IP / 涓存椂鍔犲彿鎮h�呭彇鍙凤細**鐩存帴鍙栨湰娈甸鐣欏彿**锛涙湰娈甸鐣欎篃婊� 鈫� 鎬婚鐣欐睜銆�
+     * <p>
+     * 涓庢櫘閫氶�氶亾鐨勫樊鍒彧鍦ㄤ簬"璺宠繃鏈鏅�氬彿"锛屽叾浣欙紙棰勭暀鍙枫�佹�绘睜鍏滃簳锛夊畬鍏ㄤ竴鑷淬��
+     */
     @Override
     @Transactional
     public Integer distributeVipSeqNo(Integer checkType, Integer timeslot) {
         Integer curVipSeqNo = queueSequenceMapper.selectQueueVipNoForUpdate(checkType, timeslot);
-        if (null == curVipSeqNo) {
-            throw exception(QUEUE_SEQUENCE_TIMESLOT_FULL);
+        if (null != curVipSeqNo) {
+            Integer updateRowNum = queueSequenceMapper.updateGivenCheckTypeTimeslotVipSeqNo(checkType, timeslot, curVipSeqNo);
+            if (null != updateRowNum && updateRowNum > 0) {
+                return curVipSeqNo + 1;
+            }
+            log.info("[distributeVipSeqNo] 鏈棰勭暀鍙峰苟鍙戣鎶㈢┖锛岄檷绾у埌鎬婚鐣欐睜. checkType={} timeslot={}", checkType, timeslot);
+        }
+        return nextTotalPoolSeqNo(checkType, timeslot);
+    }
+
+    /**
+     * 銆愭湰闇�姹傘�戞�婚鐣欐睜鍙栧彿锛坽@code time_slot = 0} 閭d竴琛岋級鈥斺�� **姘镐笉澶辫触**銆�
+     *
+     * <h3>涓轰粈涔堜笉浼氭弧</h3>
+     * 鎬绘睜鐨� {@code queue_vip_full} 鍦ㄥ彂鍙锋椂鍚屾"椤堕珮"锛堣 Mapper 鐨�
+     * {@code advanceTotalPoolVipNo}锛夛紝鍥犳 {@code queue_vip_no} 姘歌繙鍙互缁х画 +1銆�
+     * 杩欐槸"缁濅笉鍑虹幇绛句笉涓�"鐨勬渶鍚庝竴閬撳厹搴曘��
+     *
+     * <h3>鎬绘睜琛岀己澶辨椂鑷姩琛�</h3>
+     * 鍙锋鏀惧湪褰撳ぉ鎵�鏈夋椂娈电殑鏈�澶у彿涔嬪悗锛坽@code max(queue_vip_full)}锛夛紝
+     * 淇濊瘉鎬绘睜鍙� **澶т簬鎵�鏈夋椂娈靛彿** 鈬� 鎺掑簭澶╃劧鎺掑湪鍏ㄥぉ鏈�鍚庛��
+     */
+    private Integer nextTotalPoolSeqNo(Integer checkType, Integer timeslot) {
+        Integer curVipNo = null;
+        try {
+            curVipNo = queueSequenceMapper.selectTotalPoolVipNo(checkType);
+        } catch (Exception e) {
+            log.warn("[nextTotalPoolSeqNo] 璇诲彇鎬婚鐣欐睜寮傚父锛屾寜缂哄け澶勭悊. checkType={}", checkType, e);
         }
 
-        Integer updateRowNum = queueSequenceMapper.updateGivenCheckTypeTimeslotVipSeqNo(checkType, timeslot, curVipSeqNo);
-        if (null == updateRowNum || updateRowNum == 0) {
+        if (null == curVipNo) {
+            // 鐜板満鍏滃簳锛氳嚜鍔ㄨˉ涓�琛屾�婚鐣欐睜锛堝箓绛夛細浠� 妫�鏌ョ被鍨� + time_slot=0 鍞竴锛�
+            Integer base = queueSequenceMapper.selectMaxVipFullOfCheckType(checkType);
+            if (null == base) {
+                base = 0;
+            }
+            QueueSequenceDO pool = new QueueSequenceDO();
+            pool.setCheckType(checkType);
+            pool.setTimeSlot(0);
+            pool.setQueueSet(0);
+            pool.setQueueNo(0);
+            pool.setQueueFull(0);
+            pool.setQueueVipSet(base);
+            pool.setQueueVipNo(base);
+            pool.setQueueVipFull(base);
+            try {
+                queueSequenceMapper.insert(pool);
+            } catch (Exception e) {
+                // 骞跺彂涓嬪彲鑳藉凡琚埆鐨勭嚎绋嬫彃鍏ワ紝蹇界暐鍚庣户缁
+                log.warn("[nextTotalPoolSeqNo] 琛ユ�婚鐣欐睜琛屽け璐ワ紙鍙兘骞跺彂宸叉彃鍏ワ級锛岀户缁彇鍙�. checkType={}", checkType, e);
+            }
+            if (null == queueSequenceMapper.selectTotalPoolVipNo(checkType)) {
+                // 鎻掑叆鍚庝粛璇讳笉鍒帮紙鏋佺鎯呭喌锛夆啋 鐢ㄥ綋澶╂渶澶у彿鍏滃簳锛屼繚璇佽兘绛惧埌
+                Integer maxFull = queueSequenceMapper.selectMaxVipFullOfCheckType(checkType);
+                log.error("[nextTotalPoolSeqNo] 鎬婚鐣欐睜涓嶅彲鐢紝浣跨敤鏈�澶у彿鍏滃簳. checkType={} maxFull={}", checkType, maxFull);
+                return (null == maxFull ? 0 : maxFull) + 1;
+            }
+        }
+
+        Integer updateNum = queueSequenceMapper.advanceTotalPoolVipNo(checkType);
+        if (null == updateNum || updateNum == 0) {
             throw exception(QUEUE_SEQUENCE_TIMESLOT_EXCEPTION);
         }
+        Integer after = queueSequenceMapper.selectTotalPoolVipNo(checkType);
+        if (null == after) {
+            throw exception(QUEUE_SEQUENCE_TIMESLOT_EXCEPTION);
+        }
+        log.info("[nextTotalPoolSeqNo] 宸蹭粠鎬婚鐣欐睜鍙栧彿锛堟湰娈靛凡婊℃垨娌℃湁鏃舵锛�. checkType={} timeslot={} seqNo={}",
+                checkType, timeslot, after);
+        return after;
+    }
 
-        return curVipSeqNo + 1;
+    /**
+     * 鎸夈�愭鏌ョ被鍨嬮厤缃殑瑙勫垯銆戝垎閰嶆帓闃熷簭鍙枫��
+     *
+     * <h3>涓夌瑙勫垯</h3>
+     * <ol>
+     *   <li><b>棰勭害鍙�(0)</b>锛氱洿鎺ョ敤棰勭害绯荤粺宸蹭笅鍙戠殑鍙凤紝涓嶅啀鍒嗛厤銆�
+     *       娉ㄦ剰 {@code existingSeqNo} 涓虹┖鏃�**鍥為��鍒扮鍒板簭鍙疯鍒�** 鈥斺�� 鍥犱负鑻ョ洿鎺ヨ繑鍥�
+     *       null锛岃皟鐢ㄦ柟浼氭妸 null 鍐欏叆 {@code book_seq_num}锛屾偅鑰呭皢鎷夸笉鍒板彿銆�</li>
+     *   <li><b>绛惧埌搴忓彿(1)</b>锛氬綋澶╁悓妫�鏌ョ被鍨嬪唴杩炵画閫掑锛堢 1 涓鍒� = 1 鍙凤級锛�
+     *       骞舵寜銆屾瘡 9 涓彿棰勭暀 1 涓�嶈烦杩囬鐣欏彿缁欏姞鎬ユ偅鑰呫��
+     *       **涓嶈褰撴棩涓婇檺**锛堜笌瑙勫垯 2 鐨勬椂娈靛閲忎笉鍚岋級銆�</li>
+     *   <li><b>鏃舵鍙�(2)</b>锛氳皟鐢ㄥ師鏈夌殑 {@link #distributeSeqNo}锛堟寜 {@code time_slot} 浠庡彿姹犲彇鍙凤級锛�
+     *       淇濈暀鏃舵瀹归噺鎺у埗銆傝繖涔熸槸 {@code seq_rule} 鏈厤缃椂鐨勯粯璁ゅ�笺��</li>
+     * </ol>
+     *
+     * <h3>涓轰粈涔堣鍒� 1 闇�瑕佺嫭绔嬭鏁板櫒</h3>
+     * 涓嶈兘鐢� {@code MAX(book_seq_num)+1} 璁$畻锛氶鐣欏彿锛�10銆�20鈥︼級鑻ユ棤浜轰娇鐢ㄥ氨涓嶄細鍑虹幇鍦�
+     * 鏁版嵁閲岋紝涓嬩竴涓櫘閫氬彿鎸� MAX+1 浼氬啀娆$畻鍑哄悓涓�涓鐣欏彿锛岄�犳垚鍐茬獊銆�
+     * 鍥犳鐢� {@code seq_counter} 琛�**蹇呴』琚秷鑰�**鍦伴�掑锛堣 Mapper 鐨� LAST_INSERT_ID 鍐欐硶锛夈��
+     */
+    @Override
+    @Transactional
+    public Integer distributeSeqNoByRule(Integer checkType, Integer timeslot,
+                                        Integer existingSeqNo, boolean urgent) {
+        // 鍏煎鏃ц皟鐢ㄦ柟锛氭湭浼� VIP 鏍囪鏃舵寜"闈� VIP"澶勭悊
+        return distributeSeqNoByRule(checkType, timeslot, existingSeqNo, urgent, false);
+    }
+
+    /**
+     * 銆愭湰闇�姹傘�戝甫 VIP/鍔犲彿鏍囪鐨勫彇鍙峰叆鍙c��
+     * <p>
+     * 涓庡洓鍙傜増鏈殑鍞竴宸埆锛氭寜鏃舵棰勭害锛圱IMESLOT锛変笅锛孷IP/鍔犲彿鎮h��**鐩存帴鍙栨湰娈甸鐣欏彿**
+     * 锛堣 {@link #distributeVipSeqNo}锛夛紝鑰屼笉鏄厛鍙栨櫘閫氬彿锛涚鍒板簭鍙凤紙CHECKIN锛変笅
+     * VIP/鍔犲彿鍚屾牱鍙崰鐢ㄩ鐣欏彿锛堟瘡 9 鐣� 1 鐨勯鐣欎綅锛夈��
+     */
+    @Override
+    @Transactional
+    public Integer distributeSeqNoByRule(Integer checkType, Integer timeslot,
+                                        Integer existingSeqNo, boolean urgent, boolean vip) {
+        SeqRuleEnum rule = resolveRule(checkType);
+
+        switch (rule) {
+            case APPOINT:
+                // 鈶� 棰勭害鍙凤細棰勭害绯荤粺宸茬粰鍙峰垯鐩存帴鐢紱鍚﹀垯鍥為��鍒扮鍒板簭鍙凤紙閬垮厤鎮h�呮嬁涓嶅埌鍙凤級
+                if (null != existingSeqNo && existingSeqNo > 0) {
+                    log.info("[distributeSeqNoByRule] 瑙勫垯=棰勭害鍙凤紝鐩存帴浣跨敤棰勭害绯荤粺鍙�. checkType={} seqNo={}",
+                            checkType, existingSeqNo);
+                    return existingSeqNo;
+                }
+                log.warn("[distributeSeqNoByRule] 瑙勫垯=棰勭害鍙凤紝浣嗛绾︾郴缁熸湭涓嬪彂鍙凤紝鍥為��鍒扮鍒板簭鍙�. checkType={}",
+                        checkType);
+                return nextCheckInSeqNo(checkType, urgent || vip);
+
+            case CHECKIN:
+                // 鈶� 绛惧埌搴忓彿锛氬綋澶╁悓妫�鏌ョ被鍨嬭繛缁�掑锛屾瘡 9 涓鐣� 1 涓�
+                return nextCheckInSeqNo(checkType, urgent || vip);
+
+            case TIMESLOT:
+            default:
+                // 鈶� 鏃舵鍙凤細VIP/鍔犲彿璧般�屾湰娈甸鐣欏彿 鈫� 鎬婚鐣欐睜銆嶏紱鏅�氳蛋銆屾櫘閫� 鈫� 棰勭暀 鈫� 鎬绘睜銆�
+                if (vip) {
+                    return distributeVipSeqNo(checkType, timeslot);
+                }
+                return distributeSeqNo(checkType, timeslot);
+        }
+    }
+
+    /**
+     * 瑙f瀽鏌愭鏌ョ被鍨嬮厤缃殑鎺掗槦搴忓彿瑙勫垯锛涙煡涓嶅埌閰嶇疆鎴栨湭閰嶇疆鏃惰繑鍥為粯璁わ紙鏃舵鍙凤級銆�
+     */
+    private SeqRuleEnum resolveRule(Integer checkType) {
+        if (null == checkType) {
+            return SeqRuleEnum.DEFAULT;
+        }
+        CheckTypeDO checkTypeDO = checkTypeMapper.getCheckTypeByValue(checkType);
+        if (null == checkTypeDO) {
+            log.warn("[resolveRule] 鏈壘鍒版鏌ョ被鍨嬮厤缃紝浣跨敤榛樿瑙勫垯(鏃舵鍙�). checkType={}", checkType);
+            return SeqRuleEnum.DEFAULT;
+        }
+        return SeqRuleEnum.parse(checkTypeDO.getSeqRule());
+    }
+
+    /**
+     * 璁$畻涓嬩竴涓�岀鍒板簭鍙枫�嶃��
+     *
+     * <h4>涓や釜鐙珛璁℃暟鍣紙杩欐槸韪╁潙鍚庣殑缁撹锛�</h4>
+     * {@code seq_counter} 缁存姢涓や釜浜掍笉骞叉壈鐨勮鏁板櫒锛�
+     * <ul>
+     *   <li>{@code normal_next} 鈥斺�� 鏅�氬彿锛屽簭鍒� 1..9銆�11..19銆�21..29鈥︼紙璺宠繃 10 鐨勫�嶆暟锛夛紱</li>
+     *   <li>{@code reserved_next} 鈥斺�� 棰勭暀鍙凤紙鍔犳�ョ敤锛夛紝搴忓垪 10銆�20銆�30鈥︺��</li>
+     * </ul>
+     * <b>涓轰粈涔堜笉鑳藉彧鐢ㄤ竴涓鏁板櫒</b>锛氳嫢鐢ㄥ崟涓� {@code candidate} 閫掑鍚庡啀"璺宠繃棰勭暀鍙�"锛�
+     * 蹇呯劧鎾炶溅 鈥斺�� 鏅�氭偅鑰呮嬁鍒� candidate=10 鈫� 璺宠繃寰� 11锛涗笅涓�涓櫘閫氭偅鑰呮嬁鍒� candidate=11
+     * 鈫� 涔熷緱 11銆傚疄娴� 20 娆″彇鍙蜂腑绗� 10銆�11 娆″垎閰嶅彿鐩稿悓銆�
+     * 涓や釜璁℃暟鍣ㄧ墿鐞嗛殧绂诲悗锛屼簩鑰呭彇鍊煎煙涓嶇浉浜わ紝**涓嶅彲鑳介噸鍙�**銆�
+     *
+     * <h4>骞跺彂锛堜袱澶勫潙锛屽潎宸蹭慨澶嶏級</h4>
+     * <ol>
+     *   <li><b>鎺ㄨ繘蹇呴』鍘熷瓙</b>锛氱敤鍗曟潯 {@code INSERT .. ON DUPLICATE KEY UPDATE}
+     *       鍦ㄦ暟鎹簱绔畬鎴�"璇�-鏀�-鍐�"锛岄伩鍏嶅厛鏌ュ悗鏀归�犳垚閲嶅彿銆�
+     *       娉ㄦ剰璇ヨ鍙ュ繀椤诲姞 {@code @InterceptorIgnore(dataPermission="true")}锛�
+     *       鍚﹀垯 MyBatis-Plus 鐨� DataPermissionInterceptor 浼氱敤 JSqlParser 瑙f瀽瀹冨苟
+     *       **鎶� UnsupportedOperationException**锛圝SqlParser 涓嶆敮鎸佽 MySQL 涓撴湁璇硶锛夛紝
+     *       琛ㄧ幇涓�**棰勭害纭鐩存帴 500** 鈥斺�� 璇﹁ Mapper 涓婄殑娉ㄩ噴銆�</li>
+     *   <li><b>鎺ㄨ繘涓庤鍥炲繀椤诲悓浜嬪姟</b>锛歁ySQL 榛樿 REPEATABLE READ锛�
+     *       鑻ヨ鍥炲湪鍙︿竴涓簨鍔¢噷锛屼細璇诲埌鏃у揩鐓� 鈬� 閲嶅彿銆�
+     *       鏈柟娉曟槸 private 涓旇**鍚岀被鍐呴儴璋冪敤**锛寋@code @Transactional} 鍥犱笉璧颁唬鐞嗚��**澶辨晥**锛�
+     *       鏁呮敼鐢� {@link org.springframework.transaction.support.TransactionTemplate} 鏄惧紡寮�鍚簨鍔°��</li>
+     * </ol>
+     * 瀹炴祴 8 绾跨▼ 脳 20 娆� = 160 娆″彇鍙凤細鍚屼簨鍔� 160/160 鍞竴銆佸彿鍊艰繛缁紱
+     * 鐙珛浜嬪姟鍙湁 75/160 鈥斺�� 璇﹁ Mapper 娉ㄩ噴銆�
+     */
+    private Integer nextCheckInSeqNo(Integer checkType, boolean urgent) {
+        final LocalDate today = LocalDate.now();
+
+        // 鐢ㄦ樉寮忎簨鍔℃妸銆屾帹杩涖�嶄笌銆岃鍥炪�嶅寘鍦ㄤ竴璧凤紙@Transactional 鍦ㄦ澶勪笉鐢熸晥锛岃鏂规硶娉ㄩ噴锛�
+        Integer assigned = counterTransactionTemplate.execute(status -> {
+            if (urgent) {
+                queueSequenceMapper.advanceReservedSeq(checkType, today);
+            } else {
+                queueSequenceMapper.advanceNormalSeq(checkType, today);
+            }
+            SeqCounterDO counter = queueSequenceMapper.getSeqCounter(checkType, today);
+            if (null == counter) {
+                return null;
+            }
+            return urgent ? counter.getReservedNext() : counter.getNormalNext();
+        });
+
+        if (null == assigned) {
+            // 鐞嗚涓婁笉浼氬彂鐢燂紙鍒氭帹杩涜繃锛夛紱淇濆畧鍥為��锛岄伩鍏嶆姏閿欓樆鏂鍒�
+            assigned = urgent ? SeqRuleEnum.ROUND_SIZE : 1;
+            log.error("[nextCheckInSeqNo] 璁℃暟鍣ㄨ鍙栧け璐ワ紝鍥為��涓洪粯璁ゅ彿. checkType={} 鍔犳��={} 鍥為��鍙�={}",
+                    checkType, urgent, assigned);
+        }
+
+        log.info("[nextCheckInSeqNo] 瑙勫垯=绛惧埌搴忓彿. checkType={} 鍔犳��={} 鏈鍒嗛厤={}",
+                checkType, urgent, assigned);
+        return assigned;
+    }
+
+    /**
+     * 鍚戝悗鏌ユ壘涓�涓�**浠嶆湁浣欓噺**鐨勯绾︽椂娈点��
+     * <p>
+     * 涓庢棫鐨� {@code getNextTimeSlot} 鐨勫尯鍒細
+     * <ol>
+     *   <li>鏃у疄鐜板彧鐪�"绱ч偦鐨勪笅涓�涓�"瀛楀吀椤癸紝涓�**涓嶆鏌ヨ鏃舵鍦� queue_sequence 涓槸鍚﹀瓨鍦�**
+     *       鈥斺�� 鑻ヤ笅涓�涓椂娈垫伆濂界己澶憋紙鍙锋睜娌℃湁杩欎竴琛岋級锛屼細鐩存帴杩斿洖瀹冿紝闅忓悗
+     *       {@code selectQueueNoForUpdate} 鍙栦笉鍒板�� 鈫� 鎶� TIMESLOT_FULL銆�
+     *       杩欎篃鏄�"杩愬姩璇曢獙娌℃硶绛惧埌"鐨勭洿鎺ュ師鍥犱箣涓�銆�</li>
+     *   <li>鏃у疄鐜板湪 {@code timeslot} 涓嶅湪瀛楀吀涓椂锛圚IS 浼氫笅鍙� 60 鍒嗛挓绮掑害鍊硷紝
+     *       濡� 10001100锛岃�屽瓧鍏稿彧鏈� 30 鍒嗛挓绮掑害锛夌洿鎺� {@code return null}銆�
+     *       鏈疄鐜版敼涓�**鍙栧瓧鍏镐腑绗竴涓櫄浜� {@code timeslot} 鐨勬椂娈�**缁х画鍚戝悗鎵撅紝
+     *       浠庤�屼粛鑳戒负杩欑被鎮h�呭垎閰嶅彿銆�</li>
+     *   <li>鏈�澶氬悜鍚庢壘 {@link #MAX_TIMESLOT_LOOKAHEAD} 涓椂娈碉紙绾� 3 灏忔椂锛夛紝
+     *       閬垮厤"褰撳墠鏄渶鍚庝竴涓椂娈�"鏃舵棤澶勫彲鍘伙紱鍏ㄩ兘婊℃墠杩斿洖 null銆�</li>
+     * </ol>
+     *
+     * @param checkType  妫�鏌ョ被鍨�
+     * @param timeslot   HIS 浼犲叆鐨勯绾︽椂娈碉紙鍙兘鏄瓧鍏稿鐨� 60 鍒嗛挓绮掑害鍊硷級
+     * @param includeVip true 鏃惰姹� VIP 鍙蜂篃鏈変綑閲�
+     * @return 鍙敤鐨勬椂娈靛�硷紱鏃犲彲鐢ㄦ椂娈佃繑鍥� null
+     */
+    private Integer findAvailableTimeslot(Integer checkType, Integer timeslot, boolean includeVip) {
+        List<DictDataRespDTO> dictBookTimeslotList = dictDataApi.getDictDataList(ECG_BOOK_TIMESLOT);
+        if (dictBookTimeslotList == null || dictBookTimeslotList.isEmpty()) {
+            log.error("[findAvailableTimeslot] 瀛楀吀 {} 涓虹┖锛屾棤娉曞垎閰嶆椂娈靛彿", ECG_BOOK_TIMESLOT);
+            return null;
+        }
+
+        // 璧风偣锛氫弗鏍兼櫄浜� timeslot 鐨勭涓�涓瓧鍏搁」銆�
+        // 杩欐牱鍗充娇 timeslot 涓嶅湪瀛楀吀鍐咃紙60 鍒嗛挓绮掑害鍊硷級锛屼篃鑳借惤鍒板叾鍚庣殑鏃舵缁х画鎵俱��
+        int startIdx = -1;
+        for (int i = 0; i < dictBookTimeslotList.size(); i++) {
+            Integer v = parseSlotValue(dictBookTimeslotList.get(i).getValue());
+            if (null != v && v > timeslot) {
+                startIdx = i;
+                break;
+            }
+        }
+        if (startIdx < 0) {
+            // timeslot 宸叉槸鏈�鍚庝竴涓椂娈碉紙鎴栧叏閮ㄦ椂娈甸兘鏃╀簬瀹冿級
+            log.warn("[findAvailableTimeslot] 宸叉槸鏈�鍚庝竴涓椂娈垫垨鏃犳洿鏅氭椂娈�. checkType={} timeslot={}",
+                    checkType, timeslot);
+            return null;
+        }
+
+        int end = Math.min(dictBookTimeslotList.size(), startIdx + MAX_TIMESLOT_LOOKAHEAD);
+        for (int i = startIdx; i < end; i++) {
+            Integer candidate = parseSlotValue(dictBookTimeslotList.get(i).getValue());
+            if (null == candidate) {
+                continue;
+            }
+            Integer avail = queueSequenceMapper.countAvailableTimeslot(
+                    checkType, candidate, includeVip ? 1 : 0);
+            if (null != avail && avail > 0) {
+                log.info("[findAvailableTimeslot] checkType={} 鍘熸椂娈�={} 鈫� 鏀圭敤鏃舵={}锛堢 {} 涓�欓�夛級",
+                        checkType, timeslot, candidate, i - startIdx + 1);
+                return candidate;
+            }
+        }
+
+        log.warn("[findAvailableTimeslot] 鍚戝悗 {} 涓椂娈靛唴鍧囨棤浣欓噺. checkType={} timeslot={}",
+                MAX_TIMESLOT_LOOKAHEAD, checkType, timeslot);
+        return null;
+    }
+
+    /** 瀹夊叏瑙f瀽瀛楀吀鍊硷紙褰㈠ "9301000"锛変负 Integer锛屽拷鐣ヨ剰鏁版嵁 */
+    private Integer parseSlotValue(String value) {
+        if (null == value || value.isEmpty()) {
+            return null;
+        }
+        try {
+            return Integer.valueOf(value.trim());
+        } catch (NumberFormatException e) {
+            log.warn("[parseSlotValue] 瀛楀吀鏃舵鍊奸潪娉曪紝宸插拷鐣�: {}", value);
+            return null;
+        }
+    }
+
+    /**
+     * 鏃х殑"鍙栫揣閭讳笅涓�涓椂娈�"銆備繚鐣欎緵鍙傝��/鍏煎锛屾柊閫昏緫璇风敤
+     * {@link #findAvailableTimeslot(Integer, Integer, boolean)}銆�
+     *
+     * @deprecated 鍙湅涓嬩竴涓椂娈典笖涓嶆牎楠屽彿姹犳槸鍚﹀瓨鍦紝浼氬鑷寸鍒板け璐�
+     */
+    @Deprecated
+    private Integer getNextTimeSlot(Integer timeslot, Integer checkType) {
+        List<DictDataRespDTO> dictBookTimeslotList = dictDataApi.getDictDataList(ECG_BOOK_TIMESLOT);
+        Optional<Integer> index = dictBookTimeslotList.stream()
+                .filter(e -> e.getValue().equals(String.valueOf(timeslot)))
+                .findFirst()
+                .map(dictBookTimeslotList::indexOf);
+        if (!index.isPresent())
+            return null;
+
+        if (dictBookTimeslotList.size() <= (index.get() + 1))
+            return null;
+
+        DictDataRespDTO dictDataRespDTO = dictBookTimeslotList.get(index.get() + 1);
+        return Integer.valueOf(dictDataRespDTO.getValue());
+    }
+    @Override
+    public void initNumber() {
+        queueSequenceMapper.initNumber();
     }
 
 }

--
Gitblit v1.9.3