eight
2025-03-31 8aebf6bafb60a882ebd84bf727e39ef27233ac32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
package cn.lihu.jh.module.ecg.service.queue;
 
import cn.lihu.jh.framework.common.exception.ErrorCode;
import cn.lihu.jh.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.lihu.jh.framework.common.util.object.BeanUtils;
import cn.lihu.jh.module.ecg.Utils;
import cn.lihu.jh.module.ecg.controller.admin.room.vo.MonitorInfoVO;
import cn.lihu.jh.module.ecg.dal.dataobject.checktype.CheckTypeDO;
import cn.lihu.jh.module.ecg.dal.dataobject.queue.QueueDO;
import cn.lihu.jh.module.ecg.dal.dataobject.queue.QueueStatisticDO;
import cn.lihu.jh.module.ecg.dal.dataobject.room.RoomDO;
import cn.lihu.jh.module.ecg.dal.mysql.call.CallMapper;
import cn.lihu.jh.module.ecg.dal.mysql.checktype.CheckTypeMapper;
import cn.lihu.jh.module.ecg.dal.mysql.devrent.DevRentMapper;
import cn.lihu.jh.module.ecg.dal.mysql.queue.QueueMapper;
import cn.lihu.jh.module.ecg.dal.mysql.room.RoomMapper;
import cn.lihu.jh.module.ecg.enums.BedStatusEnum;
import cn.lihu.jh.module.ecg.enums.QueueStatusEnum;
import cn.lihu.jh.module.infra.api.config.ConfigApi;
import cn.lihu.jh.module.system.api.oauth2.OAuth2TokenApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
 
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
 
import static cn.lihu.jh.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.lihu.jh.framework.common.pojo.CommonResult.error;
import static cn.lihu.jh.module.ecg.enums.ErrorCodeConstants.*;
 
/**
 * 排队 Service 数据库事务相关的方法
 *
 * @author 芋道源码
 */
@Component
@Validated
@Slf4j
public class QueueServiceTxFunctions {
 
    @Resource
    private ConfigApi configApi;
 
    @Resource
    private OAuth2TokenApi oAuth2TokenApi;
 
    @Resource
    private QueueMapper queueMapper;
 
    @Resource
    private RoomMapper roomMapper;
 
    @Resource
    private CallMapper callMapper;
 
    @Resource
    private DevRentMapper devRentMapper;
 
    @Resource
    private CheckTypeMapper checkTypeMapper;
 
 
    AtomicInteger openingFlag = new AtomicInteger(0);
    AtomicInteger curSeqNum = new AtomicInteger(0);
 
    ConcurrentHashMap<String, BedQueueBO > mapBedVsQueue = new ConcurrentHashMap<>();
 
    // 装机工位..不进入优先队列
    Map<Integer, PriorityBlockingQueue<BedQueueBO>> mapCheckTypePriorityQueue = new HashMap();
 
    // 基于类型的, 装机准备的优先队列
    // Map<Integer, PriorityBlockingQueue<BedQueueBO>> mapReadyPriorityQueue = new HashMap();
 
    Map<Integer, CheckTypeDO> mapCheckTypeVsReadyMax = null;
 
    /**
     * 已关闭 或者 关闭中,可以开通工位
     * @param roomId
     * @param bedNo
     * @return
     */
    public ErrorCode bedOpen(Long roomId, String roomName, String bedNo) {
        BedQueueBO bedQueueBO2 = mapBedVsQueue.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null != bedQueueBO2) {
            log.error("bedOpen mapBedVsQueue has existed. " + roomId + " " + bedNo);
            return QUEUE_BED_EXIST;
        }
 
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.CLOSED);
        Integer updateNum = roomMapper.setBedOpeningOpening(roomId, bedNo, BedStatusEnum.OPENING, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedOpen DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
 
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        List<QueueDO> queueDOList = queueMapper.getBedQueueByStatus(roomId, bedNo, queueStatusList);
 
        RoomDO roomDO = roomMapper.getRoom(roomId, bedNo);
 
        // 新增..工位队列
        BedQueueBO bedQueueBO = new BedQueueBO();
        bedQueueBO.setRoomId(roomId);
        bedQueueBO.setRoomName(roomName);
        bedQueueBO.setBedNo(bedNo);
        bedQueueBO.setMaxQueueNum(getBedReadyMax(roomId, bedNo));
        bedQueueBO.setQueueNum(new AtomicInteger(queueDOList.size()));
        bedQueueBO.setStatus(BedStatusEnum.OPENING.getStatus());
        bedQueueBO.setCheckTypes( roomDO.getCheckTypes() );
        bedQueueBO.setOpType( roomDO.getOpType() );
        mapBedVsQueue.put( Utils.formatRoomBed(roomId, bedNo), bedQueueBO);
 
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    public ErrorCode bedClose(Long roomId, String bedNo) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedQueueBO) {
            log.error("bedClose mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
 
        // 除了判断 准备候诊中 的人数,还需要 判断 过号的人数
        //if (bedQueueBO.getQueueNum().get() >0)
        //    return QUEUE_HAVE_PATIENT;
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        queueStatusList.add(QueueStatusEnum.PASSED.getStatus());
        queueStatusList.add(QueueStatusEnum.RECALLED.getStatus());
        List<QueueDO>  queueDOList = queueMapper.getBedQueueByStatus(roomId, bedNo, queueStatusList);
        if (queueDOList.size() > 0)
            return QUEUE_HAVE_PATIENT;
 
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.OPENING);
        statusList.add(BedStatusEnum.DOCTOR_ON);
        statusList.add(BedStatusEnum.PAUSE);
        Integer updateNum = roomMapper.setBedOpeningClosed(roomId, bedNo,
                BedStatusEnum.CLOSED, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedClose DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
 
        mapBedVsQueue.remove( Utils.formatRoomBed(roomId, bedNo) );
        removePriorityQueue(bedQueueBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    public ErrorCode bedDoctorPause(Long roomId, String bedNo, Long docId, String docName) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedQueueBO) {
            log.error("bedDoctorPause mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
 
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.DOCTOR_ON);
        Integer updateNum = roomMapper.setBedDoctorStatus(roomId, bedNo, docId, BedStatusEnum.PAUSE, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedDoctorPause DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
 
        bedQueueBO.setStatus(BedStatusEnum.PAUSE.getStatus());
        removePriorityQueue(bedQueueBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    public ErrorCode bedDoctorResume(Long roomId, String bedNo, Long docId, String docName) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedQueueBO || !bedQueueBO.getStatus().equals(BedStatusEnum.PAUSE.getStatus())) {
            log.error("bedDoctorResume mapBedVsQueue DONOT existed OR NOT Paused. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
 
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.PAUSE);
        Integer updateNum = roomMapper.setBedDoctorStatus(roomId, bedNo, docId,
                BedStatusEnum.DOCTOR_ON, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedDoctorResume DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
 
        bedQueueBO.setStatus(BedStatusEnum.DOCTOR_ON.getStatus());
        addPriorityQueue(bedQueueBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    public ErrorCode bedDoctorOn(Long roomId, String bedNo, Long docId, String docName) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get( Utils.formatRoomBed(roomId, bedNo) );
        if (null == bedQueueBO) {
            log.error("bedDoctorOn mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
 
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.OPENING);
        Integer updateNum = roomMapper.setBedDoctorOn(roomId, bedNo, docId, docName, BedStatusEnum.DOCTOR_ON, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedDoctorOn DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
 
        bedQueueBO.setStatus(BedStatusEnum.DOCTOR_ON.getStatus());
        addPriorityQueue(bedQueueBO);
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    public ErrorCode bedDoctorOff(Long roomId, String bedNo, Long docId, String docName) {
        BedQueueBO bedQueueBO = mapBedVsQueue.get( Utils.formatRoomBed( roomId, bedNo ) );
        if (null == bedQueueBO) {
            log.error("bedDoctorOff mapBedVsQueue DONOT existed. " + roomId + " " + bedNo);
            return QUEUE_BED_NOT_EXIST;
        }
 
        // DB update
        List statusList = new ArrayList<BedStatusEnum>();
        statusList.add(BedStatusEnum.DOCTOR_ON);
        statusList.add(BedStatusEnum.PAUSE);
        Integer updateNum = roomMapper.setBedDoctorOff(roomId, bedNo, docId, BedStatusEnum.OPENING, statusList);
        if ( null==updateNum || 0 == updateNum ) {
            log.error("bedDoctorOff DB invalid status. " + roomId + " " + bedNo);
            return ROOM_INVALID_STATUS;
        }
 
        removePriorityQueue(bedQueueBO);
 
        bedQueueBO.setStatus(BedStatusEnum.OPENING.getStatus());
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    /* 2024.11.11
    // 常规 或者 领用 时 医生 取下一位患者
    public ErrorCode nextPatient(Long roomId, String bedNo) {
        // 从 DB 把该工位.序号最小的 [就诊准备中] 的人 设置为 [就诊中] (或领用中)
        Integer updateNum = queueMapper.updateBedQueueStatus(roomId, bedNo,
                QueueStatusEnum.READY.getStatus(), QueueStatusEnum.ONSTAGE.getStatus());
        // 该工位 没有 [就诊准备中] 人员
        if (null == updateNum || 0 == updateNum) {
            return QUEUE_NOT_READY_PATIENT;
        }
 
        // 优先队列中 该工位 就诊准备中人的数量 减一
        BedQueueBO bo = mapBedVsQueue.get( Utils.formatRoomBed( roomId, bedNo ) );
        bo.queueNum.getAndDecrement(); // 可能已经【并发的】在 hurry-up 中改变了值
 
        refreshPriorityQueue(bo);
        return GlobalErrorCodeConstants.SUCCESS;
    }
    */
 
    // 常规 或者 领用 时 医生 取下一位患者
    public ErrorCode nextPatient(Long roomId, String bedNo) {
        QueueDO firstReadyQueueItem = queueMapper.getFirstItemByBedAndStatus(roomId, bedNo, QueueStatusEnum.READY.getStatus());
        // 该工位 没有 [就诊准备中] 人员
        if (null == firstReadyQueueItem) {
            return QUEUE_NOT_READY_PATIENT;
        }
 
        // QueueStatusEnum.READY  =>  QueueStatusEnum.ONSTAGE
        firstReadyQueueItem.setStatus(QueueStatusEnum.ONSTAGE.getStatus());
        queueMapper.updateById( firstReadyQueueItem );
 
        // 优先队列中 该工位 就诊准备中人的数量 减一
        BedQueueBO bo = mapBedVsQueue.get( Utils.formatRoomBed( roomId, bedNo ) );
        bo.queueNum.getAndDecrement(); // 可能已经【并发的】在 hurry-up 中改变了值
 
        refreshPriorityQueue(bo);
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    // 装机时 医生 取下一位患者
    public ErrorCode nextInstallPatient(Long roomId, String bedNo) {
        // 从 DB 把 序号最小的 [已领用] [已召回-安装] 的人 设置为 [安装中]
        QueueDO firstItem = queueMapper.getFirstItemByRoomAndStatus(roomId, List.of(QueueStatusEnum.RECEIVED.getStatus(), QueueStatusEnum.RECALLED_INSTALL.getStatus()));
        // 该工位 没有 [已领用] | [已召回-安装] 人员
        if (null == firstItem) {
            return QUEUE_NOT_READY_PATIENT;
        }
 
        firstItem.setStatus( QueueStatusEnum.INSTALLING.getStatus() );
        firstItem.setBedNo( bedNo );
        queueMapper.updateById( firstItem );
 
        // 检查项目.亲和性 处理逻辑
        CheckTypeDO checkTypeDO = getCheckTypeItem( firstItem.getBookCheckType() );
        if (checkTypeDO.getAffinityCheckTypes().length > 0) {
            procAffinityWhenSomeOneEntryInstalling(firstItem.getPatId(), roomId, bedNo, checkTypeDO.getAffinityCheckTypes() );
        }
 
        // 装机工位 无优先队列
        // 优先队列中 该工位 [已领用]人的数量 减一
        //BedQueueBO bo = mapBedVsQueue.get( Utils.formatRoomBed( roomId, bedNo ) );
        //bo.queueNum.getAndDecrement(); // 可能已经【并发的】在 hurry-up 中改变了值
        //refreshPriorityQueue(bo);
 
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    /**
     * 1. 每天开诊前 从DB同步工位的患者队列数据到 工位优先队列
     * 2. 服务运维重启时
     */
    public ErrorCode bedReload() {
        mapCheckTypePriorityQueue.clear();
        mapBedVsQueue.clear();
 
        // 清除非当天的排队人员
        queueMapper.clearQueue();
        // 清除非当天的语音叫号记录
        callMapper.clearCall();
 
        // 从DB 获取 工位列表
        List<BedStatusEnum> bedStatusEnumList = new ArrayList<BedStatusEnum>();
        bedStatusEnumList.add(BedStatusEnum.OPENING);
        bedStatusEnumList.add(BedStatusEnum.DOCTOR_ON);
        bedStatusEnumList.add(BedStatusEnum.PAUSE);
        List<RoomDO> roomDOList = roomMapper.simpleRoomList(bedStatusEnumList);
        List<BedQueueBO> bedQueueBOList = roomDOList.stream().map(item -> BeanUtils.toBean(item, BedQueueBO.class)).toList();
 
        // 从DB 获取 队列中 就诊准备中人员统计 列表
        List<Integer> queueStatusList = new ArrayList<>();
        queueStatusList.add(QueueStatusEnum.READY.getStatus());
        List<QueueStatisticDO> queueStatisticDOList = queueMapper.queueStatistic(queueStatusList);
 
        bedQueueBOList.forEach(item -> {
            item.maxQueueNum = getBedReadyMax(item.roomId, item.bedNo);
            Optional<QueueStatisticDO> queueStatisticDOOptional = queueStatisticDOList.stream().filter(it->it.getRoomId()==item.roomId && it.getBedNo().equals(item.getBedNo())).findFirst();
            int queueNum = queueStatisticDOOptional.isPresent() ? queueStatisticDOOptional.get().getTotalInStatus() : 0;
            if ( item.maxQueueNum < queueNum )
                throw new RuntimeException("init: exceed max queue number!");
 
            item.queueNum.set( queueNum );
            mapBedVsQueue.put( Utils.formatRoomBed(item.roomId, item.bedNo), item);
            if (BedStatusEnum.DOCTOR_ON.getStatus() == item.getStatus()) {
                addPriorityQueue(item);
            }
        });
 
        Integer num = queueMapper.getMaxSeqNum();
        curSeqNum = new AtomicInteger(null == num ? 0 : num);
 
        return GlobalErrorCodeConstants.SUCCESS;
    }
 
    public void resetRoom(Boolean needCloseBed) {
        if (needCloseBed) {
            // 踢出在座的医生
            List<BedStatusEnum> bedStatusEnumList = new ArrayList<BedStatusEnum>();
            bedStatusEnumList.add(BedStatusEnum.DOCTOR_ON);
            bedStatusEnumList.add(BedStatusEnum.PAUSE);
            List<RoomDO> roomDOList = roomMapper.simpleRoomList(bedStatusEnumList);
            List<Long> userIdList = roomDOList.stream().map(roomDO -> roomDO.getDocId()).toList();
            oAuth2TokenApi.tick(userIdList);
 
            // 所有工位状态 => 关闭
            Integer ret = roomMapper.resetRoom(BedStatusEnum.CLOSED.getStatus());
        }
    }
 
    public void hurryupAllBed() {
        if (0 == openingFlag.get())
            return;
 
        mapBedVsQueue.values().forEach(bedQueueBO -> hurryupOneBed(bedQueueBO.roomId, bedQueueBO.bedNo));
    }
 
    /**
     * 把医生候诊的队列塞满
     */
    public void hurryupOneBed(Long roomId, String bedNo) {
        if (0 == openingFlag.get())
            return;
 
        BedQueueBO bedQueueBO = getBedQueueBO(roomId, bedNo);
 
        // 处理 过号-回来 的人
        while (bedQueueBO.queueNum.get() < bedQueueBO.maxQueueNum) {
            // 查看 当前工位 是否有过号-回来的患者
            Integer updateNum = queueMapper.queueRecalledPatient(
                    bedQueueBO.getRoomId(),
                    bedQueueBO.getRoomName(),
                    bedQueueBO.getBedNo(),
                    curSeqNum.get() + 1,  // 过号回来,排到当前的下一位
                    QueueStatusEnum.RECALLED.getStatus(), // 不需要处理 [安装召回], 原因: 安装工位 没有优先队列
                    QueueStatusEnum.READY.getStatus());
            if (null == updateNum || 0 == updateNum)
                break;
 
            // 召回 [过号] 患者后,处理 检查项目.亲和性 逻辑
            QueueDO recalledQueueItem = queueMapper.getQueueItemBySeqNum(curSeqNum.get() + 1);
 
            CheckTypeDO checkTypeDO = getCheckTypeItem( recalledQueueItem.getBookCheckType() );
            if ( checkTypeDO.getAffinityCheckTypes().length > 0) {
                procAffinityWhenRecalled(recalledQueueItem, checkTypeDO.getAffinityCheckTypes());
            }
 
            curSeqNum.getAndIncrement();
 
            // 可能已经【并发的】在 nextPatient 中改变了值
            bedQueueBO.queueNum.incrementAndGet();
 
            // 可能已经【并发的】在 nextPatient 中改变了优先队列顺序
            refreshPriorityQueue(bedQueueBO);
        }
 
        // 处理 排队中 患者
        hurryupOneBedCheckTypeList( bedQueueBO );
    }
 
    /**
     * Deprecated  代码保留,实际已经不再使用
     * 原因: 同一工位可以同时处理多个检查项目,所以需要保证多个检查项目的就诊时间先后顺序
     * 按照一个检查项目来处理,就不能保证同一个工位的多个检查项目的就诊时间的先后顺序
     * @param checkType
     */
    public void hurryupOneCheckType(Integer checkType) {
 
        PriorityBlockingQueue<BedQueueBO> priorityQueue = mapCheckTypePriorityQueue.get(checkType);
        if (null == priorityQueue)
            return;
 
        while (true) {
            BedQueueBO bedQueueBO = priorityQueue.peek();
            if (null == bedQueueBO)
                return;
 
            int curQueueNum = bedQueueBO.queueNum.get();
            if (curQueueNum > bedQueueBO.maxQueueNum)
                throw new RuntimeException("hurryupOneCheckType: exceed max queue number!");
 
            if (curQueueNum == bedQueueBO.maxQueueNum)
                return;
 
            // 查看 是否有排队中的患者
            Integer updateNum = queueMapper.preemptWaitingPatient(
                    bedQueueBO.getRoomId(),
                    bedQueueBO.getRoomName(),
                    bedQueueBO.getBedNo(),
                    curSeqNum.get() + 1,
                    QueueStatusEnum.WAITING.getStatus(),
                    QueueStatusEnum.READY.getStatus(),
                    checkType);
 
            // 没有抢到排队患者
            if (null == updateNum || 0 == updateNum) {
                return;
            }
 
            QueueDO preemptQueueItem = queueMapper.getQueueItemBySeqNum(curSeqNum.get() + 1);
            CheckTypeDO checkTypeDO = getCheckTypeItem( preemptQueueItem.getBookCheckType() );
 
            // 抢到排队患者后,处理 检查项目.亲和性 逻辑
            if ( checkTypeDO.getAffinityCheckTypes().length > 0) {
                procAffinityWhenPreempt(preemptQueueItem, checkTypeDO.getAffinityCheckTypes());
            }
 
            curSeqNum.getAndIncrement();
 
            // 可能已经【并发的】在 nextPatient 中改变了值
            bedQueueBO.queueNum.incrementAndGet();
 
            // 可能已经【并发的】在 nextPatient 中改变了优先队列顺序
            refreshPriorityQueue(bedQueueBO);
        }
    }
 
    public Integer getOpeningFlag() {
        return openingFlag.get();
    }
 
    public void setOpeningFlag(Integer flag) {
        openingFlag.set(flag);
    }
 
    public void initCheckType() {
        List<CheckTypeDO> checkTypeDOList = checkTypeMapper.simpleCheckTypeList();
        mapCheckTypeVsReadyMax = checkTypeDOList.stream().collect(Collectors.toMap(CheckTypeDO::getValue, checkType -> checkType));
    }
 
    public CheckTypeDO getCheckTypeItem(Integer checkType) {
        return mapCheckTypeVsReadyMax.get( checkType );
    }
 
    public BedQueueBO getBedQueueBO(Long roomId, String bedNo) {
        return mapBedVsQueue.get( Utils.formatRoomBed(roomId, bedNo) );
    }
 
    public MonitorInfoVO getMonitorInfo() {
        MonitorInfoVO monitorInfoVO = new MonitorInfoVO();
        monitorInfoVO.setOpeningFlag( openingFlag.get() );
        roomBedStatistic(monitorInfoVO);
 
        return monitorInfoVO;
    }
 
    public void monitorInfo() {
        MonitorInfoVO monitorInfoVO = new MonitorInfoVO();
        roomBedStatistic(monitorInfoVO);
        log.info(" opening " + openingFlag.get() + " " + monitorInfoVO.getQueueNum() + " " + monitorInfoVO.getActiveQueueNum() + " " + monitorInfoVO.getCheckTypeBedInfo().toString() );
    }
 
    private void hurryupOneBedCheckTypeList(BedQueueBO bedQueueBO) {
        while (bedQueueBO.queueNum.get() < bedQueueBO.maxQueueNum) {
            // 抢占 是否有排队中的患者
            Integer updateNum = queueMapper.preemptWaitingPatientWithBedCheckTypes(
                    bedQueueBO.getRoomId(),
                    bedQueueBO.getRoomName(),
                    bedQueueBO.getBedNo(),
                    curSeqNum.get() + 1,
                    QueueStatusEnum.WAITING.getStatus(),
                    QueueStatusEnum.READY.getStatus(),
                    bedQueueBO.checkTypes);
 
            // 没有抢到排队患者
            if (null == updateNum || 0 == updateNum) {
                return;
            }
 
            QueueDO preemptQueueItem = queueMapper.getQueueItemBySeqNum(curSeqNum.get() + 1);
            CheckTypeDO checkTypeDO = getCheckTypeItem( preemptQueueItem.getBookCheckType() );
 
            // 抢到排队患者后,处理 检查项目.亲和性 逻辑
            if ( checkTypeDO.getAffinityCheckTypes().length > 0) {
                procAffinityWhenPreempt(preemptQueueItem, checkTypeDO.getAffinityCheckTypes());
            }
 
            curSeqNum.getAndIncrement();
 
            bedQueueBO.queueNum.incrementAndGet();
        }
 
        // 改变了优先队列顺序
        refreshPriorityQueue(bedQueueBO);
    }
 
    private void procAffinityWhenPreempt(QueueDO preemptQueueItem, Integer[] affinityCheckTypes) {
        List<QueueDO> affinityItems = queueMapper.getCurPatGivenCheckTypesAndStatus(
                preemptQueueItem.getPatId(), affinityCheckTypes, QueueStatusEnum.WAITING.getStatus());
        for (int i = 0; i < affinityItems.size(); i++) {
            QueueDO queueItem = affinityItems.get(i);
            queueItem.setStatus(QueueStatusEnum.AFFINITY_WAITING.getStatus()); //改变 排队状态
            queueItem.setAffinityItem( 1 );
            queueItem.setRoomId(preemptQueueItem.getRoomId());
            queueItem.setRoomName(preemptQueueItem.getRoomName());
            queueItem.setBedNo(preemptQueueItem.getBedNo());
            queueItem.setSeqNum( preemptQueueItem.getSeqNum());  // 使用同一个 内部序号
            queueMapper.updateById(queueItem);
        }
    }
 
    private void procAffinityWhenRecalled(QueueDO recalledQueueItem, Integer[] affinityCheckTypes) {
        List<QueueDO> affinityItems = queueMapper.getCurPatGivenCheckTypesAndStatus(
                recalledQueueItem.getPatId(), affinityCheckTypes, QueueStatusEnum.AFFINITY_WAITING.getStatus());
        for (int i = 0; i < affinityItems.size(); i++) {
            QueueDO queueItem = affinityItems.get(i);
            queueItem.setSeqNum( recalledQueueItem.getSeqNum() );  // 召回后,原内部序号更新
            queueMapper.updateById(queueItem);
        }
    }
 
    private Integer getBedReadyMax(Long roomId, String bedNo) {
        RoomDO roomDO = roomMapper.getRoom(roomId, bedNo);
        Integer[] checkTypes = roomDO.getCheckTypes();
        Integer ret = 0;
        for (int i=0; i<checkTypes.length; i++) {
            CheckTypeDO checkTypeDO = mapCheckTypeVsReadyMax.get(checkTypes[i]);
            Integer tmp = checkTypeDO.getReadyNum();
            ret = ret < tmp ? tmp : ret;  //取大值
        }
        return ret;
    }
 
    private void addPriorityQueue(BedQueueBO bedQueueBO) {
        Integer[] checkTypes = bedQueueBO.getCheckTypes();
        Arrays.stream(checkTypes).forEach(checkType -> {
            CheckTypeDO checkTypeDO = mapCheckTypeVsReadyMax.get(checkType);
            // 装机工位 不进 优先队列,不从排队中接收人
            if (checkTypeDO.getNeedDevReady() == 1 && bedQueueBO.getOpType() == 2) {
                return;
            }
            
            PriorityBlockingQueue<BedQueueBO> priorityQueue = mapCheckTypePriorityQueue.get(checkType);
            if (null == priorityQueue) {
                priorityQueue = new PriorityBlockingQueue<BedQueueBO>();
                mapCheckTypePriorityQueue.put( checkType, priorityQueue );
            }
 
            if (!priorityQueue.contains(bedQueueBO)) {
                priorityQueue.offer(bedQueueBO);
            } else {
                log.error("bedDoctorResume priorityQueue still exist!. " + bedQueueBO.getRoomId() + " " + bedQueueBO.getBedNo());
            }
        });
    }
 
    private void removePriorityQueue(BedQueueBO bedQueueBO) {
        Integer[] checkTypes = bedQueueBO.getCheckTypes();
        Arrays.stream(checkTypes).forEach(checkType -> {
            PriorityBlockingQueue<BedQueueBO> priorityBlockingQueue = mapCheckTypePriorityQueue.get(checkType);
            if (null != priorityBlockingQueue)
                priorityBlockingQueue.remove(bedQueueBO);
        });
    }
 
    private void refreshPriorityQueue(BedQueueBO bedQueueBO) {
        Integer[] checkTypes = bedQueueBO.getCheckTypes();
        Arrays.stream(checkTypes).forEach(checkType -> {
            PriorityBlockingQueue<BedQueueBO> priorityQueue = mapCheckTypePriorityQueue.get(checkType);
            priorityQueue.remove(bedQueueBO);
            priorityQueue.offer(bedQueueBO);
        });
    }
 
    private void procAffinityWhenSomeOneEntryInstalling(String patId, Long roomId_operator, String bedNo_operator, Integer[] affinityCheckTypes) {
        // 对于 刚进入到 [安装中]的项,  如果存在 [已领用] 的亲和项,则需要其跟随
        List<QueueDO> affinityItems = queueMapper.getCurPatGivenCheckTypesAndStatus(
                                patId, affinityCheckTypes, QueueStatusEnum.RECEIVED.getStatus());
        for (int i = 0; i < affinityItems.size(); i++) {
            QueueDO queueItem = affinityItems.get(i);
            queueItem.setStatus(QueueStatusEnum.AFFINITY_RECEIVED.getStatus()); //改变 排队状态
            queueItem.setRoomId(roomId_operator);
            queueItem.setBedNo(bedNo_operator);  // 跟随到 装机 工位
            queueMapper.updateById(queueItem);
        }
    }
 
    private void roomBedStatistic(MonitorInfoVO monitorInfoVO) {
        Map<Integer, Integer>  mapOpeningCheckBedStatInfo = new HashMap<>();
        Map<Integer, Integer>  mapOpeningInstallBedStatInfo = new HashMap<>();
        Map<Integer, Integer>  mapOperatingCheckBedStatInfo = new HashMap<>();
        Map<Integer, Integer>  mapOperatingInstallBedStatInfo = new HashMap<>();
        mapCheckTypeVsReadyMax.keySet().forEach( checkType -> {
            mapOpeningCheckBedStatInfo.put(checkType, 0);
            mapOpeningInstallBedStatInfo.put(checkType, 0);
            mapOperatingCheckBedStatInfo.put(checkType, 0);
            mapOperatingInstallBedStatInfo.put(checkType, 0);
        });
 
        //开通的 和 运营中 的工位统计
        mapBedVsQueue.values().forEach( bedQueueBO -> {
            Integer[] checkTypes = bedQueueBO.getCheckTypes();
            Arrays.stream(checkTypes).forEach(checkType -> {
                if (bedQueueBO.opType == 0 || bedQueueBO.opType == 1)
                    mapOpeningCheckBedStatInfo.put(checkType, mapOpeningCheckBedStatInfo.get(checkType) + 1 );
                else
                    mapOpeningInstallBedStatInfo.put(checkType, mapOpeningInstallBedStatInfo.get(checkType) + 1 );
 
                if (bedQueueBO.getStatus() == BedStatusEnum.DOCTOR_ON.getStatus()) {
                    if (bedQueueBO.opType == 0 || bedQueueBO.opType == 1)
                        mapOperatingCheckBedStatInfo.put(checkType, mapOperatingCheckBedStatInfo.get(checkType) + 1);
                    else
                        mapOperatingInstallBedStatInfo.put(checkType, mapOperatingInstallBedStatInfo.get(checkType) + 1);
                }
            });
        });
 
        // 接收..排队中患者..的优先队列
        HashSet<BedQueueBO> hashSetOperatingBedQueue = new HashSet<>();
        mapCheckTypePriorityQueue.values().forEach(priorityQueue->{
            priorityQueue.stream().forEach(bedQueueBO -> hashSetOperatingBedQueue.add(bedQueueBO));
        });
 
 
        Map<Integer, String> mapCheckTypeBedInfo = new HashMap<>();
        mapCheckTypeVsReadyMax.keySet().forEach( checkType -> {
           String str = mapOpeningCheckBedStatInfo.get(checkType) + " " +
                        mapOpeningInstallBedStatInfo.get(checkType) + " " +
                        mapOperatingCheckBedStatInfo.get(checkType) + " " +
                        mapOperatingInstallBedStatInfo.get(checkType) + " ";
            mapCheckTypeBedInfo.put( checkType, str);
        });
 
        monitorInfoVO.setCheckTypeBedInfo(mapCheckTypeBedInfo);
        monitorInfoVO.setQueueNum(mapBedVsQueue.size());
        monitorInfoVO.setActiveQueueNum((int) mapBedVsQueue.values().stream().filter(item -> item.getStatus() == BedStatusEnum.DOCTOR_ON.getStatus()).count());
        monitorInfoVO.setPriorityQueueNum( hashSetOperatingBedQueue.size() );
        monitorInfoVO.setMapBedVsQueue( mapBedVsQueue );
    }
 
}