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/controller/admin/room/RoomController.java |  287 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 287 insertions(+), 0 deletions(-)

diff --git a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/controller/admin/room/RoomController.java b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/controller/admin/room/RoomController.java
index 6f0b3de..6e5aa4b 100644
--- a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/controller/admin/room/RoomController.java
+++ b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/controller/admin/room/RoomController.java
@@ -10,6 +10,7 @@
 import javax.validation.Valid;
 
 import cn.hutool.extra.servlet.ServletUtil;
+import cn.lihu.jh.module.ecg.utils.IpUtils;
 import cn.lihu.jh.module.ecg.controller.admin.room.vo.*;
 import cn.lihu.jh.module.ecg.dal.dataobject.queue.QueueDO;
 import cn.lihu.jh.module.ecg.dal.dataobject.room.RoomDO;
@@ -29,11 +30,16 @@
 import cn.lihu.jh.framework.common.pojo.PageParam;
 import cn.lihu.jh.framework.common.pojo.PageResult;
 import cn.lihu.jh.framework.common.pojo.CommonResult;
+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.framework.excel.core.util.ExcelUtils;
 import cn.lihu.jh.framework.apilog.core.annotation.ApiAccessLog;
+import cn.lihu.jh.framework.security.core.util.SecurityFrameworkUtils;
+import lombok.extern.slf4j.Slf4j;
 
 import static cn.lihu.jh.framework.common.pojo.CommonResult.success;
+import static cn.lihu.jh.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.lihu.jh.framework.apilog.core.enums.OperateTypeEnum.*;
 
 
@@ -41,6 +47,7 @@
 @RestController
 @RequestMapping("/clinic/room")
 @Validated
+@Slf4j
 public class RoomController {
 
     @Resource
@@ -92,6 +99,258 @@
         String reqIp = ServletUtil.getClientIP(request);
         RoomProfile roomProfile = roomService.getRoomByIP(reqIp, roomId);
         return success(BeanUtils.toBean(roomProfile, RoomProfileRespVO.class));
+    }
+
+    /* ==================== 閫夋嫨宸ヤ綅妯″紡锛堥渶姹傦細璇婂宸ヤ綅閫夋嫨妯″紡 0/1/2/3锛� ==================== */
+
+    /**
+     * 鎸夋湰鏈篒P鍖归厤宸ヤ綅锛堟ā寮� 1/3 鐢級銆�
+     *
+     * <p>鍙鎺ュ彛锛�
+     * <ul>
+     *   <li>鎶婂悗绔疄闄呰瘑鍒埌鐨勬潵璁縄P涓�璧疯繑鍥烇紝渚夸簬鎺掓煡"鍓嶇疆浠g悊鏈�忎紶 X-Forwarded-For"锛�</li>
+     *   <li>鍊欓�夊伐浣嶅凡鎸夈�屾湰浜哄凡鍗犵敤 鈫� 绌洪棽 鈫� 琚粬浜哄崰鐢ㄣ�嶆帓濂藉簭锛屽墠绔彇绗竴涓彲鐢ㄧ殑鑷姩鍏ュ骇锛�</li>
+     *   <li>鏈帴鍙d笉鏀逛换浣曠姸鎬侊紝鍏ュ骇浠嶈蛋鍘熸湁鐨� bedDoctorOn 娴佺▼銆�</li>
+     * </ul>
+     * 闇�瑕佺櫥褰曪紙鏈櫥褰曟椂杩斿洖绌虹粨鏋滐級锛屼絾涓嶅仛鑿滃崟鏉冮檺鏍¢獙锛氳兘鐧诲綍灏辫兘鏌ヨ嚜宸辩殑宸ヤ綅銆�</p>
+     */
+    @GetMapping("/get-ip-bed")
+    @Operation(summary = "鎸夋湰鏈篒P鍖归厤宸ヤ綅锛堥�夋嫨宸ヤ綅妯″紡 1/3锛�")
+    public CommonResult<RoomSelectBedRespVO> getIpBed(HttpServletRequest request) {
+        // 瑙勮寖鍖栵細鍘绘嫭鍙�/鍖哄煙鍙枫�佺粺涓�灏忓啓銆両Pv4-mapped IPv6 杩樺師鎴� IPv4锛堟敮鎸� IPv4 涓� IPv6锛�
+        String clientIp = IpUtils.normalize(ServletUtil.getClientIP(request));
+        Long docId = SecurityFrameworkUtils.getLoginUserId();
+        RoomSelectBedRespVO resp = new RoomSelectBedRespVO();
+        resp.setClientIp(clientIp);
+        resp.setIpValid(IpUtils.isBindableIp(clientIp));
+        resp.setIpLoopback(IpUtils.isLoopback(clientIp));
+        resp.setIpv6(IpUtils.isIpv6Address(clientIp));
+        List<RoomSelectBedRespVO.RoomSelectBedItem> beds = roomService.matchBedsByIp(clientIp, docId);
+        resp.setBeds(beds);
+        resp.setIpBound(!beds.isEmpty());
+        // 鏈�"鏈汉宸插崰鐢�"鎴�"绌洪棽"鐨勫伐浣嶏紝灏辫兘鑷姩鍏ュ骇
+        resp.setMatched(beds.stream()
+                .anyMatch(b -> Boolean.TRUE.equals(b.getMine()) || !Boolean.TRUE.equals(b.getOccupied())));
+        log.info("[getIpBed][鎸塈P鍖归厤宸ヤ綅] clientIp={} docId={} ipValid={} loopback={} 鍊欓�夋暟={} 鍙嚜鍔ㄥ叆搴�={}",
+                clientIp, docId, resp.getIpValid(), resp.getIpLoopback(), beds.size(), resp.getMatched());
+        return success(resp);
+    }
+
+    /**
+     * 鎶婃湰鏈篒P鍐欏叆鎸囧畾宸ヤ綅锛堟ā寮� 2/3 鐢級銆�
+     *
+     * <p>鍙洿鏂� {@code clinic_room.ip} 涓�涓瓧娈碉紝涓嶈蛋 {@code clinic:room:update}锛堝惁鍒欎负浜嗛�夊伐浣嶈繕寰楃粰鍖荤敓寮�璇婂绠$悊鏉冮檺锛夛紝
+     * 涔熶笉浼氱 status/doc_id/check_types 绛夊叾瀹冨瓧娈点��
+     * 鑻ヨIP宸茬粦鍦ㄥ埆鐨勫伐浣嶄笂锛岄粯璁よ繑鍥炲啿绐佸伐浣嶈鍓嶇纭锛涚‘璁ゅ悗甯� {@code force=true} 鍐嶈皟涓�娆″嵆瑕嗙洊銆�</p>
+     */
+    @PostMapping("/bind-ip")
+    @Operation(summary = "鎶婃湰鏈篒P缁戝畾鍒板伐浣嶏紙閫夋嫨宸ヤ綅妯″紡 2/3锛�")
+    public CommonResult<Map<String, Object>> bindIp(HttpServletRequest request,
+                                                    @RequestParam("id") Integer id,
+                                                    @RequestParam(value = "force", required = false, defaultValue = "false") Boolean force) {
+        Map<String, Object> data = new HashMap<>();
+        String rawIp = ServletUtil.getClientIP(request);
+        String clientIp = IpUtils.normalize(rawIp);
+        data.put("clientIp", clientIp);
+        if (!IpUtils.isBindableIp(clientIp)) {
+            data.put("code", 400);
+            if (IpUtils.isLoopback(clientIp)) {
+                // 娴忚鍣ㄥ拰鏈嶅姟鍦ㄥ悓涓�鍙版満鍣ㄤ笂璁块棶鏃跺繀鐒跺嚭鐜帮細IPv4 鏄� 127.0.0.1锛孖Pv6 鏄� ::1
+                data.put("msg", "鏃犳硶缁戝畾宸ヤ綅锛氭娴嬪埌褰撳墠璁块棶鏉ヨ嚜鏈満鍥炵幆鍦板潃锛�" + clientIp
+                        + "锛屽嵆鏈満璁块棶锛夛紝璇峰湪鍖荤敓瀹為檯浣跨敤鐨勭數鑴戜笂璁块棶鍚庡啀缁戝畾");
+            } else if (IpUtils.isUnspecified(clientIp)) {
+                data.put("msg", "鏃犳硶缁戝畾宸ヤ綅锛氬綋鍓嶈瘑鍒埌鐨勫湴鍧�鏄湭鎸囧畾鍦板潃锛�" + clientIp + "锛�");
+            } else if (IpUtils.isLocalAddress(clientIp)) {
+                // 鏈鏂板锛氱幇鍦烘渶甯歌鐨�"IP 涓嶅"灏辨槸杩欑 鈥斺�� 鍦ㄦ湇鍔″櫒涓婏紙鎴栬繙绋嬫闈㈤噷锛夎闂郴缁燂紝
+                // 瀹㈡埛绔湰鏉ュ氨鏄湇鍔″櫒鑷繁锛涜嫢鎶婂畠鍐欒繘宸ヤ綅锛岃宸ヤ綅浠ュ悗姘歌繙鍖归厤涓嶅埌鍖荤敓鐢佃剳銆�
+                data.put("msg", "鏃犳硶缁戝畾宸ヤ綅锛氳瘑鍒埌鐨勬槸**鏈嶅姟鍣ㄨ嚜韬湴鍧�**锛�" + clientIp
+                        + "锛夆�斺�� 璇存槑褰撳墠鏄湪鏈嶅姟鍣ㄤ笂璁块棶锛屾垨缁忚繙绋嬫闈�/鏈満浠g悊璁块棶锛岃繖涓嶆槸鍖荤敓鐢佃剳鐨� IP銆�"
+                        + "璇峰湪鍖荤敓瀹為檯浣跨敤鐨勭數鑴戜笂銆佺敤鏈嶅姟鍣ㄧ殑 IPv4 鍦板潃璁块棶鍚庡啀缁戝畾"
+                        + "锛堝彲鐢ㄣ�孖P 璇婃柇銆嶆煡鐪嬪悗绔埌搴曟妸璋佸綋鎴愪簡瀹㈡埛绔級");
+                log.warn("[bindIp][璇嗗埆鍒版湇鍔″櫒鑷韩鍦板潃锛屾嫆缁濈粦瀹歖 clientIp={} bedId={} 鏈嶅姟鍣ㄧ綉鍗Pv4={}",
+                        clientIp, id, IpUtils.localIpv4Addresses());
+            } else if (IpUtils.isIpv6Address(clientIp)) {
+                // 鏈郴缁熷伐浣嶇粦瀹氱粺涓�閲囩敤 IPv4锛涘鎴风璧� IPv6 鏃舵棤娉曚粠浠g爜"閫�鍖栨垚"IPv4
+                data.put("msg", "鏃犳硶缁戝畾宸ヤ綅锛氬綋鍓嶈闂娇鐢ㄧ殑鏄� IPv6 鍦板潃锛�" + clientIp
+                        + "锛夛紝鏈郴缁熷伐浣嶇粦瀹氫粎閲囩敤 IPv4锛岃鏀圭敤鏈嶅姟鍣ㄧ殑 IPv4 鍦板潃璁块棶"
+                        + "锛堟垨璁╂湇鍔″彧鐩戝惉 IPv4锛欽VM 鍙傛暟 -Djava.net.preferIPv4Stack=true锛�");
+            } else {
+                data.put("msg", "鏃犳硶缁戝畾宸ヤ綅锛氭湭鑳借瘑鍒埌鍚堟硶鐨� IPv4 鏉ヨ鍦板潃锛�" + clientIp
+                        + "锛夛紝璇锋鏌ユ槸鍚﹂�氳繃浠g悊璁块棶銆佷互鍙婁唬鐞嗘槸鍚﹂�忎紶瀹㈡埛绔湴鍧�锛圶-Forwarded-For锛�");
+            }
+            log.warn("[bindIp][IP涓嶅彲鐢紝鎷掔粷缁戝畾] raw={} normalized={} ipv6={} bedId={}",
+                    rawIp, clientIp, IpUtils.isIpv6Address(clientIp), id);
+            return success(data);
+        }
+        // 鍐茬獊妫�娴嬶細璇P宸茬粦鍦ㄥ埆鐨勫伐浣� 鈫� 鍏堣鍓嶇纭
+        List<RoomDO> conflicts = roomService.listIpBindings(clientIp, id);
+        if (!conflicts.isEmpty() && !Boolean.TRUE.equals(force)) {
+            RoomDO c = conflicts.get(0);
+            data.put("code", 409);
+            data.put("conflict", true);
+            data.put("conflictBedId", c.getId());
+            data.put("conflictRoomName", c.getRoomName());
+            data.put("conflictBedNo", c.getBedNo());
+            data.put("msg", "璇P宸茬粦瀹氥��" + c.getRoomName() + " / " + c.getBedNo() + "銆�");
+            return success(data);
+        }
+        Integer rows = roomService.bindIpToBed(id, clientIp);
+        data.put("code", rows != null && rows > 0 ? 200 : 500);
+        data.put("msg", rows != null && rows > 0 ? "宸茬粦瀹氭湰鏈篒P鍒拌宸ヤ綅" : "缁戝畾澶辫触锛堝伐浣嶄笉瀛樺湪锛燂級");
+        data.put("rows", rows);
+        log.info("[bindIp][缁戝畾缁撴灉] raw={} clientIp={} bedId={} force={} rows={}", rawIp, clientIp, id, force, rows);
+        return success(data);
+    }
+
+    /* ==================== 鑷姩鍏ュ骇 / 鎴戠殑宸ヤ綅锛堟湰娆¢渶姹傦級 ==================== */
+
+    /**
+     * 涓�閿嚜鍔ㄥ叆搴э紙閫夋嫨宸ヤ綅妯″紡 1/3 鐢級銆�
+     *
+     * <p>涓轰粈涔堝悎骞舵垚涓�涓帴鍙o細鍓嶇鍘熸潵鎶娿�屽尮閰� 鈫� 缁� IP 鈫� 鍏ュ骇銆嶆媶鎴愪笁涓姹傝嚜宸辨嫾锛�
+     * 浠讳竴姝ュけ璐ラ兘鍙兘鍥炶惤"鎵嬮�夊伐浣�"锛岀幇鍦鸿〃鐜板氨鏄�"鐧诲綍鍚庢病鏈夎嚜鍔ㄥ叆搴�"涓旂湅涓嶅嚭鍘熷洜銆�
+     * 鐜板湪涓�娆¤皟鐢ㄥ畬鎴愶紝骞惰繑鍥�**璇嗗埆鍒扮殑 IP** 涓�**鏈叆搴у師鍥�**
+     * 锛坽@code ip_invalid / nomatch / multi / occupied / error}锛夛紝鎺掓煡涓�鐪煎彲杈ㄣ��
+     *
+     * <p>闇�瑕佺櫥褰曪紝浣嗕笉鍋氳彍鍗曟潈闄愭牎楠岋細鑳界櫥褰曞氨鑳藉潗鑷繁鐨勫伐浣嶃��
+     */
+    @PostMapping("/auto-seat")
+    @Operation(summary = "鎸夋湰鏈篒P鑷姩鍖归厤宸ヤ綅骞惰嚜鍔ㄥ叆搴э紙閫夋嫨宸ヤ綅妯″紡 1/3锛�")
+    public CommonResult<RoomSeatRespVO> autoSeat(HttpServletRequest request) {
+        String clientIp = IpUtils.normalize(ServletUtil.getClientIP(request));
+        Long docId = SecurityFrameworkUtils.getLoginUserId();
+        String docName = SecurityFrameworkUtils.getLoginUserNickname();
+        RoomSeatRespVO resp = roomService.autoSeatByIp(clientIp, docId, docName);
+        log.info("[autoSeat][鑷姩鍏ュ骇] clientIp={} docId={} seated={} reason={} 宸ヤ綅={}/{}",
+                clientIp, docId, resp.getSeated(), resp.getReason(), resp.getRoomName(), resp.getBedNo());
+        return success(resp);
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐧诲綍浜�**宸插叆搴�**鐨勫伐浣嶏紙绯荤粺椤舵爮"褰撳墠宸ヤ綅"鏍囩鐢級銆�
+     *
+     * <p>鏈叆搴ф椂杩斿洖 {@code seated=false}锛屽墠绔嵁姝ゆ樉绀虹伆鑹层�屾湭鍏ュ骇銆嶅苟鍙偣鍑诲幓閫夊伐浣嶃��
+     */
+    @GetMapping("/my-seat")
+    @Operation(summary = "鏌ヨ褰撳墠鐧诲綍浜哄凡鍏ュ骇鐨勫伐浣嶏紙绯荤粺椤舵爮灞曠ず锛�")
+    public CommonResult<RoomSeatRespVO> mySeat() {
+        Long docId = SecurityFrameworkUtils.getLoginUserId();
+        RoomSeatRespVO resp = roomService.getMySeat(docId);
+        log.info("[mySeat][鏌ヨ鎴戠殑宸ヤ綅] docId={} seated={} 宸ヤ綅={}/{}",
+                docId, resp.getSeated(), resp.getRoomName(), resp.getBedNo());
+        return success(resp);
+    }
+
+    /**
+     * 銆愭湰闇�姹偮蜂竴浜哄宸ヤ綅銆戞煡璇㈠綋鍓嶇櫥褰曚汉鍗犳嵁鐨�**鍏ㄩ儴**宸ヤ綅锛堣妭鍋囨棩鍊肩彮澶氬矖浣嶏級銆�
+     *
+     * <p>杩斿洖銆屽尰鐢熷湪搴� / 鍖荤敓鏆傚仠銆嶇殑宸ヤ綅鍒楄〃锛屾寜 roomId + bedNo 鍗囧簭锛涙湭鍗犲伐浣嶈繑鍥炵┖鏁扮粍銆�
+     * 鍓嶇鐢ㄥ畠娓叉煋椤舵爮銆屾垜鐨勫伐浣嶏紙N 涓級銆嶅苟鍦ㄥ伐浣嶄箣闂村垏鎹€��
+     */
+    @GetMapping("/my-seats")
+    @Operation(summary = "鏌ヨ褰撳墠鐧诲綍浜哄凡鍏ュ骇鐨勫叏閮ㄥ伐浣嶏紙涓�浜哄宀椾綅锛�")
+    public CommonResult<List<RoomSeatRespVO>> mySeats() {
+        Long docId = SecurityFrameworkUtils.getLoginUserId();
+        List<RoomSeatRespVO> seats = roomService.getMySeats(docId);
+        log.info("[mySeats][鏌ヨ鎴戠殑鍏ㄩ儴宸ヤ綅] docId={} 鏁伴噺={}", docId, null == seats ? 0 : seats.size());
+        return success(seats);
+    }
+
+    /**
+     * 銆愭湰闇�姹偮锋帴鐝嚜鍔╂帴绠°�戞妸宸ヤ綅浠�"鍒汉鍗犵敤"鏀逛负"褰撳墠鐧诲綍浜哄崰鐢�"銆�
+     *
+     * <p>鍦烘櫙锛氫笂涓�鐝尰鐢熶笅鐝湭鐐广�岀搴с�嶏紝涓嬩竴鐝尰鐢熷湪鍚屼竴鍙扮數鑴�/鍚屼竴宸ヤ綅鎺ョ彮銆�
+     * 鍘熷崰鐢ㄤ汉浼氳寮哄埗绂诲骇锛堝叾椤甸潰 5s 杞浼氭彁绀恒�岃閲嶆柊灏卞潗銆嶅苟杞彧璇伙級銆�
+     * 鏉冮檺娌跨敤鍖荤敓宸ヤ綔鍙板凡鏈夌殑 {@code ecg:doctor:task}锛屾棤闇�鏂板鏉冮檺鐐广��
+     */
+    @PostMapping("/take-over-bed")
+    @Operation(summary = "鎺ョ宸ヤ綅锛堟帴鐝細鎶婁粬浜哄崰鐢ㄧ殑宸ヤ綅杞粰鎴戯級")
+    @PreAuthorize("@ss.hasPermission('ecg:doctor:task')")
+    public CommonResult<Boolean> takeOverBed(@RequestParam("roomId") Long roomId,
+                                             @RequestParam("bedNo") String bedNo) {
+        roomService.takeOverBed(roomId, bedNo);
+        return success(true);
+    }
+
+    /**
+     * 瀹㈡埛绔� IP 璇嗗埆璇婃柇锛堢幇鍦烘帓鏌�"宸ヤ綅缁戝畾鐨� IP 涓嶅"鐢級銆�
+     *
+     * <p>涓�娆℃妸鎵�鏈夌浉鍏崇嚎绱㈡憡寮�锛氬悇涓唬鐞嗗ご銆乀CP 鐩磋繛鍦板潃銆佽鑼冨寲缁撴灉銆佹槸鍚﹀洖鐜�/IPv6銆�
+     * **鏄惁鏄湇鍔″櫒鑷韩鍦板潃**銆佷互鍙婃湇鍔″櫒鎵�鏈� IPv4 缃戝崱鍦板潃銆傜晫闈笂鐨勩�孖P 璇婃柇銆嶆寜閽氨璋冨畠銆�
+     *
+     * <p>鍏稿瀷缁撹锛�
+     * <ul>
+     *   <li>鍙栧埌鐨勬槸鏈嶅姟鍣ㄨ嚜韬湴鍧� 鈫� 褰撳墠鏄湪鏈嶅姟鍣ㄤ笂/杩滅▼妗岄潰閲岃闂紝鎴栦唬鐞嗘妸瀹㈡埛绔湴鍧�瑕嗙洊鎴愪簡鏈嶅姟鍣ㄥ湴鍧�锛�</li>
+     *   <li>鍙栧埌 127.x / ::1 鈫� 娴忚鍣ㄤ笌鏈嶅姟鍦ㄥ悓涓�鍙版満鍣紱</li>
+     *   <li>鍙栧埌 IPv6 鈫� 瀹㈡埛绔蛋 IPv6锛堟湰绯荤粺宸ヤ綅缁戝畾鍙敤 IPv4锛夛紱</li>
+     *   <li>{@code X-Forwarded-For} 涓虹┖涓旂洿杩炲湴鍧�=缃戝叧 鈫� 鍓嶉潰鐨勪唬鐞嗘病閫忎紶瀹㈡埛绔湴鍧�銆�</li>
+     * </ul>
+     */
+    @GetMapping("/ip-debug")
+    @Operation(summary = "瀹㈡埛绔疘P璇嗗埆璇婃柇锛堟帓鏌ュ伐浣嶇粦瀹欼P涓嶅锛�")
+    public CommonResult<Map<String, Object>> ipDebug(HttpServletRequest request) {
+        Map<String, Object> data = new LinkedHashMap<>();
+        String xff = request.getHeader("X-Forwarded-For");
+        String realIp = request.getHeader("X-Real-IP");
+        String remoteAddr = request.getRemoteAddr();
+        String raw = ServletUtil.getClientIP(request);
+        String clientIp = IpUtils.normalize(raw);
+
+        data.put("璁块棶Host", request.getHeader("Host"));
+        data.put("X-Forwarded-For", xff);
+        data.put("X-Real-IP", realIp);
+        data.put("Proxy-Client-IP", request.getHeader("Proxy-Client-IP"));
+        data.put("WL-Proxy-Client-IP", request.getHeader("WL-Proxy-Client-IP"));
+        data.put("HTTP_CLIENT_IP", request.getHeader("HTTP_CLIENT_IP"));
+        data.put("鐩磋繛鍦板潃(remoteAddr)", remoteAddr);
+        data.put("鍙栧埌鐨勫師濮嬪��", raw);
+        data.put("瑙勮寖鍖栧悗(宸ヤ綅鎸夋鍖归厤)", clientIp);
+        data.put("鍙敤浜庡伐浣嶇粦瀹�", IpUtils.isBindableIp(clientIp));
+        data.put("鏄洖鐜�(娴忚鍣ㄤ笌鏈嶅姟鍚屾満)", IpUtils.isLoopback(clientIp));
+        data.put("鏄疘Pv6", IpUtils.isIpv6Address(clientIp));
+        data.put("鏄湇鍔″櫒鑷韩鍦板潃", IpUtils.isLocalAddress(clientIp));
+        data.put("鏈嶅姟鍣ㄧ綉鍗Pv4", IpUtils.localIpv4Addresses());
+
+        String verdict;
+        if (IpUtils.isLoopback(clientIp)) {
+            verdict = "瀹㈡埛绔氨鏄湇鍔″櫒鏈満锛堝洖鐜� " + clientIp + "锛夛細灞炪�愭祻瑙堝櫒涓庢湇鍔″湪鍚屼竴鍙版満鍣ㄣ�戠殑姝e父鐜拌薄锛�"
+                    + "涓嶈兘浣滀负宸ヤ綅鏍囪瘑锛岃鍦ㄥ尰鐢熺數鑴戜笂璁块棶銆�";
+        } else if (IpUtils.isLocalAddress(clientIp)) {
+            verdict = "鈿� 璇嗗埆鍒扮殑鏄�愭湇鍔″櫒鑷韩鍦板潃銆戯紙" + clientIp + "锛夛細褰撳墠鏄湪鏈嶅姟鍣ㄤ笂/杩滅▼妗岄潰閲岃闂紝"
+                    + "鎴栧墠缃唬鐞嗘妸瀹㈡埛绔湴鍧�瑕嗙洊鎴愪簡鏈嶅姟鍣ㄥ湴鍧� 鈫� 涓嶈兘浣滀负宸ヤ綅鏍囪瘑銆�"
+                    + "璇峰湪鍖荤敓鐢佃剳涓娿�佺敤鏈嶅姟鍣ㄧ殑 IPv4 鍦板潃璁块棶锛涜嫢纭鏄湪鍖荤敓鐢佃剳涓婅闂紝璇锋鏌ヤ唬鐞嗘槸鍚﹂�忎紶 "
+                    + "X-Forwarded-For锛坣ginx: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;锛夈��";
+        } else if (IpUtils.isIpv6Address(clientIp)) {
+            verdict = "瀹㈡埛绔蛋鐨勬槸 IPv6锛�" + clientIp + "锛夛細鏈郴缁熷伐浣嶇粦瀹氬彧閲囩敤 IPv4锛�"
+                    + "璇疯鏈嶅姟鍙洃鍚� IPv4锛圝VM: -Djava.net.preferIPv4Stack=true锛夋垨鏀圭敤 IPv4 鍦板潃璁块棶銆�";
+        } else if (IpUtils.isBindableIp(clientIp)) {
+            verdict = "璇嗗埆姝e父锛屽彲鐢ㄤ簬宸ヤ綅缁戝畾锛�" + clientIp;
+        } else {
+            verdict = "鏈兘璇嗗埆鍒板彲鐢ㄧ殑 IPv4 瀹㈡埛绔湴鍧�锛堝師濮嬪�硷細" + raw + "锛岀洿杩炲湴鍧�锛�" + remoteAddr
+                    + "锛夛細璇锋鏌ヤ唬鐞嗘槸鍚﹂�忎紶瀹㈡埛绔湴鍧�锛圶-Forwarded-For锛夈��";
+        }
+        data.put("璇婃柇缁撹", verdict);
+
+        log.info("[ipDebug][瀹㈡埛绔疘P璇婃柇] Host={} XFF={} X-Real-IP={} remoteAddr={} 鈫� clientIp={} 鏈嶅姟鍣ㄥ湴鍧�={}",
+                request.getHeader("Host"), xff, realIp, remoteAddr, clientIp, IpUtils.isLocalAddress(clientIp));
+        return success(data);
+    }
+
+    /**
+     * 宸ヤ綅鐘舵�佽瘖鏂紙鎺掓煡"鍏ュ骇鎶ョ姸鎬佸紓甯� / 鏄庢槑绌洪棽鍗村潗涓嶄笂"锛夈��
+     *
+     * <p>鎶婂簱閲�**鍘熷琛�**鎽婂紑锛歚id / room_id / room_name / bed_no(鍚暱搴︿笌 HEX锛岀敤浜庡彂鐜扮粨灏剧┖鏍笺�佸叏瑙�) /
+     * ip / status / doc_id / doc_name / op_type / deleted / update_time`锛屽苟缁欏嚭涓�鍙ョ粨璁恒��
+     *
+     * <p>鐢ㄦ硶锛歚GET /clinic/room/bed-state?id=3` 鎴� `?roomId=118&bedNo=C1`銆�
+     */
+    @GetMapping("/bed-state")
+    @Operation(summary = "宸ヤ綅鐘舵�佽瘖鏂紙鎺掓煡鍏ュ骇鎶ョ姸鎬佸紓甯革級")
+    public CommonResult<Map<String, Object>> bedState(
+            @RequestParam(value = "id", required = false) Integer id,
+            @RequestParam(value = "roomId", required = false) Long roomId,
+            @RequestParam(value = "bedNo", required = false) String bedNo) {
+        return success(roomService.diagnoseBed(id, roomId, bedNo));
     }
 
     @GetMapping("/page")
@@ -166,6 +425,10 @@
         List<RoomDO> list = roomService.simpleRoomList();
         List<RoomRespVO> list2 = BeanUtils.toBean(list, RoomRespVO.class);
 
+        // 鏄惧紡鏍囨敞銆屾槸鍚﹀凡琚尰鐢熷崰鐢ㄣ�嶏細閫夊伐浣嶉〉鎹鏄剧ず銆屽凡鍏ュ骇锛氬紶涓夈�嶅苟**绂佺敤鍏ュ骇鎸夐挳**锛�
+        // 涓嶄緷璧� BeanUtils 鏄惁鎶� docId 澶嶅埗杩囨潵锛堝弻淇濋櫓锛涙湰娆$幇鍦哄氨鏈�"鐪嬩笉鍑烘潵璋佸崰浜嗗伐浣�"鐨勫弽棣堬級銆�
+        list2.forEach(roomRespVO -> roomRespVO.setOccupied(null != roomRespVO.getDocId()));
+
         Map<Long, List<RoomRespVO>> map = list2.stream().collect(Collectors.groupingBy(RoomRespVO::getRoomId));
         return success(map);
     }
@@ -200,5 +463,29 @@
         return success(0);
     }
 
+    /**
+     * 銆愭湰娆¢渶姹傘�戠鐞嗗憳寮哄埗娓呯┖鏌愪釜宸ヤ綅锛堜笉瑕佹眰鏄崰鐢ㄨ�呮湰浜猴級銆�
+     * <p>
+     * 浣跨敤鍦烘櫙锛氬尰鐢熶笅鐝繕璁般�岀搴с�嶏紝鎴栨崲浜轰娇鐢ㄥ悓涓�宸ヤ綅銆�
+     * <p>
+     * 涓庡尰鐢熻嚜宸辩殑銆岀搴с�嶆帴鍙o紙{@code /ecg/doctor/bed-doctor-off}锛夌殑鍖哄埆锛�
+     * 路 璇ユ帴鍙e彧鑳芥竻**鑷繁**鍗犵敤鐨勫伐浣嶏紙SQL 甯� {@code doc_id = 褰撳墠鐢ㄦ埛}锛夛紱
+     * 路 鏈帴鍙i潰鍚戠鐞嗙锛屽彲浠ユ竻**浠讳綍浜�**鍗犵敤鐨勫伐浣嶏紙浠呰姹傝宸ヤ綅褰撳墠鏄�屽尰鐢熷湪宀� / 鏆傚仠銆嶏級銆�
+     * <p>
+     * 宸ヤ綅绌洪棽鏃朵細杩斿洖銆屽綋鍓嶅伐浣嶇姸鎬佷笉鍏佽璇ユ搷浣溿�嶁�斺�旇繖鏄鏈熻涓恒��
+     */
+    @PostMapping("/force-clear-bed")
+    @Operation(summary = "绠$悊鍛樺己鍒舵竻绌哄伐浣�", description = "鍖荤敓涓嬬彮蹇樼搴� / 鎹汉浣跨敤鍚屼竴宸ヤ綅鏃朵娇鐢�")
+    @PreAuthorize("@ss.hasPermission('clinic:room:update')")
+    public CommonResult<Boolean> forceClearBed(@RequestParam("roomId") Long roomId,
+                                               @RequestParam("bedNo") String bedNo) {
+        ErrorCode ec = queueService.startBedDoctorOffByAdmin(roomId, bedNo);
+        if (!GlobalErrorCodeConstants.SUCCESS.getCode().equals(ec.getCode())) {
+            log.warn("[forceClearBed] 娓呯┖宸ヤ綅澶辫触 roomId={} bedNo={} code={} msg={}",
+                    roomId, bedNo, ec.getCode(), ec.getMsg());
+            throw exception(ec);
+        }
+        return success(true);
+    }
 
 }

--
Gitblit v1.9.3