Pārlūkot izejas kodu

feat(common): 添加业务异常类 BusinessException- 新增 BusinessException 类用于统一业务异常处理
- 支持携带错误码和数据的异常构造方式
- 提供多种构造函数满足不同异常场景需求
- 集成 Lombok 简化 getter/setter 代码feat(admin): 实现客服聊天测试页面

- 创建 chat.html 页面用于测试客服聊天功能
- 实现 WebSocket 连接与消息收发功能
- 添加输入状态提示(typing indicator)
- 支持用户与客服之间的实时聊天通信
- 集成 token 获取与 WebSocket 认证连接- 实现防抖机制优化输入状态发送频率

nahida 7 mēneši atpakaļ
vecāks
revīzija
f54232cad9

+ 130 - 0
zksy-admin/src/main/resources/static/chat.html

@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+    <meta charset="UTF-8"/>
+    <title>客服聊天测试</title>
+    <style>
+        body {
+            font-family: sans-serif;
+        }
+
+        #messages {
+            border: 1px solid #ccc;
+            height: 300px;
+            overflow-y: auto;
+            padding: 8px;
+        }
+
+        #typingIndicator {
+            color: gray;
+            font-style: italic;
+            margin-top: 5px;
+        }
+    </style>
+</head>
+<body>
+<h2>客服聊天测试</h2>
+<div id="messages"></div>
+<div id="typingIndicator"></div>
+<input type="text" id="msgInput" placeholder="输入消息"/>
+<button onclick="sendMessage()">发送</button>
+<input type="text" id="adminId" placeholder="输入客服ID"/>
+
+<script>
+    const userId = prompt("请输入您的用户ID");
+    fetch('/chat/getToken', {
+        method: "GET"
+    }).then(response => response.json())
+        .then(token => {
+            const customInfo = { token: token.data };
+
+            const queryParams = new URLSearchParams();
+            Object.entries(customInfo).forEach(([key, value]) => {
+                queryParams.append(key, value);
+            });
+
+            const wsUrl = `ws://localhost:8081/ws?${queryParams.toString()}`;
+            const ws = new WebSocket(wsUrl);
+
+            const messagesDiv = document.getElementById("messages");
+            const typingIndicator = document.getElementById("typingIndicator");
+
+            let typingTimeout = null; // 停止输入的定时器
+            let lastTypingSent = 0;   // 防抖用
+            const TYPING_INTERVAL = 500; // 最小间隔
+            const STOP_DELAY = 2000; // 超过2秒没输入则发送 stop
+
+            ws.onopen = () => {
+                console.log("已连接");
+                ws.send(JSON.stringify({from: userId, type: "login", role: "user"}));
+            };
+
+            ws.onmessage = (e) => {
+                const msg = JSON.parse(e.data);
+
+                if (msg.type === "typing") {
+                    typingIndicator.textContent = msg.from + " 正在输入...";
+                } else if (msg.type === "typingStop") {
+                    typingIndicator.textContent = "";
+                } else if (msg.type === "chat") {
+                    const p = document.createElement("p");
+                    p.textContent = msg.from + ": " + msg.content;
+                    messagesDiv.appendChild(p);
+                    typingIndicator.textContent = ""; // 收到消息时清掉 typing
+                }
+            };
+
+            window.sendMessage = function () {
+                const input = document.getElementById("msgInput");
+                const content = input.value;
+                if (content.trim()) {
+                    const adminIdInput = document.getElementById("adminId");
+                    const toAdminId = adminIdInput.value.trim();
+                    ws.send(JSON.stringify({
+                        from: userId,
+                        to: toAdminId,
+                        type: "chat",
+                        content: content,
+                        role: "user"
+                    }));
+                    input.value = "";
+                }
+            };
+
+            // 🔥 监听输入框 typing
+            const input = document.getElementById("msgInput");
+            input.addEventListener("input", () => {
+                const now = Date.now();
+                if (now - lastTypingSent > TYPING_INTERVAL) {
+                    const adminIdInput = document.getElementById("adminId");
+                    const toAdminId = adminIdInput.value.trim();
+                    ws.send(JSON.stringify({
+                        from: userId,
+                        to: toAdminId,
+                        type: "typing",
+                        role: "user"
+                    }));
+                    lastTypingSent = now;
+                }
+
+                // 重置停止输入的定时器
+                if (typingTimeout) clearTimeout(typingTimeout);
+                typingTimeout = setTimeout(() => {
+                    const adminIdInput = document.getElementById("adminId");
+                    const toAdminId = adminIdInput.value.trim();
+                    ws.send(JSON.stringify({
+                        from: userId,
+                        to: toAdminId,
+                        type: "typingStop",
+                        role: "user"
+                    }));
+                }, STOP_DELAY);
+            });
+        })
+        .catch(error => {
+            console.error('获取token失败:', error);
+            alert('获取token失败,无法建立连接');
+        });
+</script>
+</body>
+</html>

+ 38 - 0
zksy-common/src/main/java/com/zksy/common/exception/BusinessException.java

@@ -0,0 +1,38 @@
+package com.zksy.common.exception;
+
+import lombok.Data;
+
+@Data
+public class BusinessException extends RuntimeException{
+    private Object data;
+    private Integer code;
+
+    public BusinessException() {
+        super();
+    }
+
+    public BusinessException(String message) {
+        super(message);
+        this.code = 500;
+        this.data = null;
+    }
+
+    public BusinessException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+//    public BusinessException(Throwable cause) {
+//        super(cause);
+//    }
+
+    public BusinessException(Integer code, String message, Object data) {
+        super(message);
+        this.data = data;
+        this.code = code;
+    }
+
+    public BusinessException(String message, Object data, Throwable cause) {
+        super(message, cause);
+        this.data = data;
+    }
+}