feat: 实现WebSocket通信框架及任务管理功能

新增WebSocket客户端和服务端通信框架,包括会话管理、心跳检测和自动重连机制
添加任务管理器用于处理WebSocket任务创建和执行
实现消息回调处理和错误处理机制
重构销售类型服务并添加缓存支持
移除旧的销售类型服务实现
This commit is contained in:
2025-09-17 11:44:39 +08:00
parent ada539bebf
commit 30deb0a280
19 changed files with 495 additions and 160 deletions

View File

@@ -0,0 +1,130 @@
package com.ecep.contract.service;
import com.ecep.contract.Message;
import com.ecep.contract.constant.WebSocketConstant;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
@Service
public class WebSocketTaskManager implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(WebSocketTaskManager.class);
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ScheduledExecutorService scheduledExecutorService;
private Map<String, String> taskClzMap = Map.of();
@Override
public void afterPropertiesSet() throws Exception {
taskClzMap = Map.of(
"ContractSyncTask", "com.ecep.contract.cloud.u8.ContractSyncTask",
"ContractRepairTask", "com.ecep.contract.ds.contract.tasker.ContractRepairTask"
);
}
public void onMessage(WebSocketSession session, JsonNode jsonNode) {
// 处理 sessionId 的消息
String sessionId = jsonNode.get(WebSocketConstant.SESSION_ID_FIELD_NAME).asText();
try {
handleAsSessionCallback(session, sessionId, jsonNode);
} catch (Exception e) {
sendError(session, sessionId, e.getMessage());
logger.warn("处理会话回调失败 (会话ID: {}): {}", sessionId, e.getMessage(), e);
}
}
private void handleAsSessionCallback(WebSocketSession session, String sessionId, JsonNode jsonNode) {
if (!jsonNode.has("type")) {
throw new IllegalArgumentException("缺失 type 参数");
}
String type = jsonNode.get("type").asText();
if (type.equals("createTask")) {
createTask(session, sessionId, jsonNode);
}
}
private void createTask(WebSocketSession session, String sessionId, JsonNode jsonNode) {
if (!jsonNode.has("taskName")) {
throw new IllegalArgumentException("缺失 taskName 参数");
}
String taskName = jsonNode.get("taskName").asText();
String clzName = taskClzMap.get(taskName);
if (clzName == null) {
throw new IllegalArgumentException("未知的任务类型: " + taskName);
}
Object tasker = null;
try {
Class<?> clz = Class.forName(clzName);
tasker = clz.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("未知的任务类型: " + taskName + ", class: " + clzName);
} catch (Exception e) {
throw new IllegalArgumentException("任务类型: " + taskName + ", class: " + clzName + " 实例化失败");
}
if (tasker instanceof WebSocketServerTasker t) {
t.setTitleHandler(title -> sendToSession(session, sessionId, "title", title));
t.setMessageHandler(msg -> sendMessageToSession(session, sessionId, msg));
t.init(jsonNode);
scheduledExecutorService.submit(t);
}
}
private boolean sendMessageToSession(WebSocketSession session, String sessionId, Message msg) {
return sendToSession(session, sessionId, "message", msg.getLevel().getName(), msg.getMessage());
}
private boolean sendToSession(WebSocketSession session, String sessionId, String type, Object... args) {
try {
String text = objectMapper.writeValueAsString(Map.of(
WebSocketConstant.SESSION_ID_FIELD_NAME, sessionId,
"type", type,
"args", args
));
session.sendMessage(new TextMessage(text));
} catch (IOException e) {
// 捕获所有可能的异常,防止影响主流程
logger.error("发送错误消息失败 (会话ID: {})", session.getId(), e);
}
return true;
}
private void sendError(WebSocketSession session, String sessionId, String message) {
if (session == null || !session.isOpen()) {
logger.warn("尝试向已关闭的WebSocket会话发送错误消息: {}", message);
return;
}
try {
String errorMessage = objectMapper.writeValueAsString(Map.of(
WebSocketConstant.SESSION_ID_FIELD_NAME, sessionId,
WebSocketConstant.SUCCESS_FIELD_VALUE, false,
WebSocketConstant.MESSAGE_FIELD_NAME, message
));
// 检查会话状态并尝试发送错误消息
if (session.isOpen()) {
session.sendMessage(new TextMessage(errorMessage));
} else {
logger.warn("会话已关闭,无法发送错误消息: {}", message);
}
} catch (Exception e) {
// 捕获所有可能的异常,防止影响主流程
logger.error("发送错误消息失败 (会话ID: {})", session.getId(), e);
}
}
}

View File

@@ -0,0 +1,15 @@
package com.ecep.contract.service;
import com.ecep.contract.Message;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
public interface WebSocketTasker extends Callable<Object> {
void setMessageHandler(Predicate<Message> messageHandler);
void setTitleHandler(Predicate<String> titleHandler);
void init(JsonNode jsonNode);
}