feat: 实现WebSocket通信框架及任务管理功能
新增WebSocket客户端和服务端通信框架,包括会话管理、心跳检测和自动重连机制 添加任务管理器用于处理WebSocket任务创建和执行 实现消息回调处理和错误处理机制 重构销售类型服务并添加缓存支持 移除旧的销售类型服务实现
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
import com.ecep.contract.constant.WebSocketConstant;
|
||||
import com.ecep.contract.msg.SimpleMessage;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import javafx.application.Platform;
|
||||
@@ -24,6 +26,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* WebSocket消息服务
|
||||
@@ -49,6 +52,9 @@ public class WebSocketService {
|
||||
private SimpleBooleanProperty online = new SimpleBooleanProperty(false);
|
||||
private SimpleStringProperty message = new SimpleStringProperty("");
|
||||
|
||||
// 存储所有活跃的WebSocket会话
|
||||
private final Map<String, WebSocketClientSession> sessions = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
// 存储所有活跃的WebSocket会话
|
||||
private final Map<String, CompletableFuture<JsonNode>> callbacks = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
@@ -75,29 +81,23 @@ public class WebSocketService {
|
||||
|
||||
try {
|
||||
JsonNode node = objectMapper.readTree(text);
|
||||
if (node.has("messageId")) {
|
||||
String messageId = node.get("messageId").asText();
|
||||
if (node.has(WebSocketConstant.MESSAGE_ID_FIELD_NAME)) {
|
||||
String messageId = node.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME).asText();
|
||||
CompletableFuture<JsonNode> future = callbacks.remove(messageId);
|
||||
if (future != null) {
|
||||
if (node.has("success")) {
|
||||
if (!node.get("success").asBoolean()) {
|
||||
future.completeExceptionally(
|
||||
new RuntimeException("请求失败:来自服务器的消息=" + node.get("message").asText()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 使用具体类型后,这里不会再出现类型不匹配的错误
|
||||
if (node.has("data")) {
|
||||
future.complete(node.get("data"));
|
||||
} else {
|
||||
future.complete(node);
|
||||
}
|
||||
onCallbackMessage(future, node);
|
||||
} else {
|
||||
logger.error("未找到对应的回调future: {}", messageId);
|
||||
}
|
||||
} else if (node.has("errorCode")) {
|
||||
int errorCode = node.get("errorCode").asInt();
|
||||
String errorMsg = node.get("message").asText();
|
||||
} else if (node.has(WebSocketConstant.SESSION_ID_FIELD_NAME)) {
|
||||
String sessionId = node.get(WebSocketConstant.SESSION_ID_FIELD_NAME).asText();
|
||||
WebSocketClientSession session = sessions.get(sessionId);
|
||||
if (session != null) {
|
||||
session.onMessage(node);
|
||||
}
|
||||
} else if (node.has(WebSocketConstant.ERROR_CODE_FIELD_NAME)) {
|
||||
int errorCode = node.get(WebSocketConstant.ERROR_CODE_FIELD_NAME).asInt();
|
||||
String errorMsg = node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText();
|
||||
// TODO 需要重新登录
|
||||
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
|
||||
}
|
||||
@@ -139,6 +139,22 @@ public class WebSocketService {
|
||||
}
|
||||
};
|
||||
|
||||
private void onCallbackMessage(CompletableFuture<JsonNode> future, JsonNode node) {
|
||||
if (node.has(WebSocketConstant.SUCCESS_FIELD_VALUE)) {
|
||||
if (!node.get(WebSocketConstant.SUCCESS_FIELD_VALUE).asBoolean()) {
|
||||
future.completeExceptionally(
|
||||
new RuntimeException("请求失败:来自服务器的消息=" + node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 使用具体类型后,这里不会再出现类型不匹配的错误
|
||||
if (node.has("data")) {
|
||||
future.complete(node.get("data"));
|
||||
} else {
|
||||
future.complete(node);
|
||||
}
|
||||
}
|
||||
|
||||
public void send(String string) {
|
||||
if (webSocket != null && webSocket.send(string)) {
|
||||
logger.debug("send message success:{}", string);
|
||||
@@ -147,6 +163,10 @@ public class WebSocketService {
|
||||
}
|
||||
}
|
||||
|
||||
public void send(Object message) throws JsonProcessingException {
|
||||
send(objectMapper.writeValueAsString(message));
|
||||
}
|
||||
|
||||
public CompletableFuture<JsonNode> send(SimpleMessage msg) {
|
||||
CompletableFuture<JsonNode> future = new CompletableFuture<>();
|
||||
try {
|
||||
@@ -168,6 +188,14 @@ public class WebSocketService {
|
||||
return future;
|
||||
}
|
||||
|
||||
public CompletableFuture<JsonNode> invoke(String service, String method, Object... params) {
|
||||
SimpleMessage msg = new SimpleMessage();
|
||||
msg.setService(service);
|
||||
msg.setMethod(method);
|
||||
msg.setArguments(params);
|
||||
return send(msg).orTimeout(getReadTimeout(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void initWebSocket() {
|
||||
isActive = true;
|
||||
OkHttpClient httpClient = Desktop.instance.getHttpClient();
|
||||
@@ -278,4 +306,25 @@ public class WebSocketService {
|
||||
return online;
|
||||
}
|
||||
|
||||
public void withSession(Consumer<WebSocketClientSession> sessionConsumer) {
|
||||
WebSocketClientSession session = createSession();
|
||||
try {
|
||||
sessionConsumer.accept(session);
|
||||
} finally {
|
||||
// closeSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSession(WebSocketClientSession session) {
|
||||
if (session != null) {
|
||||
sessions.remove(session.getSessionId());
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
private WebSocketClientSession createSession() {
|
||||
WebSocketClientSession session = new WebSocketClientSession(this);
|
||||
sessions.put(session.getSessionId(), session);
|
||||
return session;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
import com.ecep.contract.constant.WebSocketConstant;
|
||||
import com.ecep.contract.task.Tasker;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WebSocketSession {
|
||||
@Getter
|
||||
private String sessionId = UUID.randomUUID().toString();
|
||||
|
||||
private WebSocketClientTasker tasker;
|
||||
|
||||
private final WebSocketService webSocketService;
|
||||
|
||||
public WebSocketSession(WebSocketService webSocketService) {
|
||||
this.webSocketService = webSocketService;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
public void submitTask(WebSocketClientTasker tasker, Object... args) throws JsonProcessingException {
|
||||
Map<String, Object> argments = Map.of(
|
||||
WebSocketConstant.SESSION_ID_FIELD_NAME, getSessionId(),
|
||||
"type", "createTask",
|
||||
"taskName", tasker.getTaskName(),
|
||||
"args", args);
|
||||
webSocketService.send(argments);
|
||||
}
|
||||
|
||||
public void onMessage(JsonNode node) {
|
||||
if (node.has("type")) {
|
||||
String type = node.get("type").asText();
|
||||
if (type.equals("message")) {
|
||||
JsonNode args = node.get("args");
|
||||
String message = args.get(1).asText();
|
||||
String level = args.get(0).asText();
|
||||
if (tasker instanceof Tasker<?> t) {
|
||||
t.updateMessage(java.util.logging.Level.parse(level), message);
|
||||
}
|
||||
} else if (type.equals("title")) {
|
||||
JsonNode args = node.get("args");
|
||||
String message = args.get(0).asText();
|
||||
if (tasker instanceof Tasker<?> t) {
|
||||
t.updateTitle(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
public interface WebSocketClientTasker {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
public class CompanyCustomerFileTypeService {
|
||||
}
|
||||
@@ -4,10 +4,14 @@ import java.util.List;
|
||||
|
||||
import com.ecep.contract.model.Company;
|
||||
import com.ecep.contract.model.CompanyInvoiceInfo;
|
||||
import com.ecep.contract.service.QueryService;
|
||||
import com.ecep.contract.service.ViewModelService;
|
||||
import com.ecep.contract.vm.CompanyInvoiceInfoViewModel;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
public class CompanyInvoiceInfoService implements ViewModelService<CompanyInvoiceInfo, CompanyInvoiceInfoViewModel> {
|
||||
@Service
|
||||
|
||||
public class CompanyInvoiceInfoService extends QueryService<CompanyInvoiceInfo, CompanyInvoiceInfoViewModel> {
|
||||
|
||||
public List<CompanyInvoiceInfo> searchByCompany(Company company, String searchText) {
|
||||
throw new UnsupportedOperationException("未实现");
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
public class CompanyVendorFileTypeService {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
public class HolidayService {
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ecep.contract.model.ProjectSaleType;
|
||||
import com.ecep.contract.vm.ProjectSaleTypeViewModel;
|
||||
|
||||
@Service
|
||||
public class SaleTypeService extends QueryService<ProjectSaleType, ProjectSaleTypeViewModel> {
|
||||
|
||||
public ProjectSaleType findByName(String name) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'findByName'");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.ecep.contract.vm;
|
||||
|
||||
public class CompanyCustomerFileTypeLocalViewModel {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.ecep.contract.vm;
|
||||
|
||||
public class CompanyVendorFileTypeLocalViewModel {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.ecep.contract.vm;
|
||||
|
||||
public class EnumViewModel {
|
||||
}
|
||||
Reference in New Issue
Block a user