diff --git a/src/main/java/com/goeing/printserver/main/domain/dto/WebSocketMessageDTO.java b/src/main/java/com/goeing/printserver/main/domain/dto/WebSocketMessageDTO.java new file mode 100644 index 0000000..81673ef --- /dev/null +++ b/src/main/java/com/goeing/printserver/main/domain/dto/WebSocketMessageDTO.java @@ -0,0 +1,22 @@ +package com.goeing.printserver.main.domain.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class WebSocketMessageDTO { + + // 消息内容 + private String payload; + + // 消息时间戳 + private LocalDateTime timestamp; + + // 消息类型(可选) + private String type; + + //请求id + private String requestId; + +} diff --git a/src/main/java/com/goeing/printserver/main/sse/PrinterClient.java b/src/main/java/com/goeing/printserver/main/sse/PrinterClient.java new file mode 100644 index 0000000..f2af61d --- /dev/null +++ b/src/main/java/com/goeing/printserver/main/sse/PrinterClient.java @@ -0,0 +1,213 @@ +package com.goeing.printserver.main.sse; + +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import com.goeing.printserver.main.PrintController; +import com.goeing.printserver.main.domain.dto.WebSocketMessageDTO; +import com.goeing.printserver.main.domain.request.PrintRequest; +import jakarta.websocket.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import jakarta.annotation.PreDestroy; + +import javax.print.PrintService; +import java.awt.print.PrinterJob; +import java.io.IOException; +import java.net.URI; +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@ClientEndpoint +@Component +@Slf4j +public class PrinterClient implements ApplicationRunner { + private Session session; + @Value("${print.printer.id}") + private String printerId; + @Value("${print.websocket.url}") + private String serverUri; + @Value("${print.websocket.apiKey}") + private String apiKey; + + private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(); + private final ScheduledExecutorService heartbeatExecutor = Executors.newSingleThreadScheduledExecutor(); + private boolean isConnecting = false; + + // 无参构造函数,由Spring管理 + public PrinterClient() { + // 构造函数不做连接操作,在run方法中进行连接 + } + + @OnOpen + public void onOpen(Session session) { + this.session = session; + log.info("WebSocket连接已建立"); + isConnecting = false; + } + + @OnMessage + public void onMessage(String message, Session session) { + log.info("收到消息: {}", message); + try { + // 解析消息 + WebSocketMessageDTO webSocketMessageDTO = JSON.parseObject(message, WebSocketMessageDTO.class); + String type = webSocketMessageDTO.getType(); + + if ("print".equals(type)) { + String payload = webSocketMessageDTO.getPayload(); + PrintRequest printRequest = JSONUtil.toBean(payload, PrintRequest.class); + PrintController bean = SpringUtil.getBean(PrintController.class); + + + // 处理打印任务 + log.info("收到打印任务: {}, ", printRequest); + + try { + bean.print(printRequest); + log.info("打印任务完成: {}", printRequest.getFileUrl()); + + Map map = new HashMap<>(); + map.put("status", "success"); + map.put("msg", ""); + + webSocketMessageDTO.setType("RESPONSE"); + webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(map)); + session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO)); + } catch (Exception e) { + log.error("打印失败: {}", printRequest.getFileUrl(), e); + // 发送打印失败消息 + String errorMsg = e.getMessage(); + if (errorMsg == null) { + errorMsg = "未知错误"; + } else { + // 转义JSON特殊字符 + errorMsg = errorMsg.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n"); + } + + Map map = new HashMap<>(); + map.put("status", "fail"); + map.put("msg", errorMsg); + webSocketMessageDTO.setType("RESPONSE"); + webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(map)); + + session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO)); + } + } else if ("heartbeat_ack".equals(type)) { + // 心跳响应,可以记录最后一次心跳时间 + log.debug("收到心跳响应"); + }else if ("printerList".equals(type)) { + + PrintService[] printServices = PrinterJob.lookupPrintServices(); + Set collect = Arrays.stream(printServices).map(PrintService::getName).collect(Collectors.toSet()); + + List collect1 = collect.stream().sorted().collect(Collectors.toList()); + + webSocketMessageDTO.setType("RESPONSE"); + webSocketMessageDTO.setPayload(JSONUtil.toJsonStr(collect1)); + session.getBasicRemote().sendText(JSONUtil.toJsonStr(webSocketMessageDTO)); + + } + } catch (Exception e) { + log.error("处理消息失败", e); + } + } + + @OnClose + public void onClose(Session session, CloseReason closeReason) { + log.warn("WebSocket连接关闭: {}", closeReason.getReasonPhrase()); + this.session = null; + // 安排重连任务 + scheduleReconnect(); + } + + @OnError + public void onError(Session session, Throwable throwable) { + log.error("WebSocket连接发生错误", throwable); + this.session = null; + + // 安排重连任务 + scheduleReconnect(); + } + + /** + * 连接到WebSocket服务器 + */ + private void connect() { + if (isConnecting || (session != null && session.isOpen())) { + return; // 已经连接或正在连接中 + } + String tempUrl = serverUri+"?printerId="+printerId+"&apiKey="+apiKey; + + isConnecting = true; + try { + log.info("正在连接到WebSocket服务器: {}", tempUrl); + WebSocketContainer container = ContainerProvider.getWebSocketContainer(); + // 设置连接超时时间 + container.setDefaultMaxSessionIdleTimeout(60000); + container.connectToServer(this, new URI(tempUrl)); + } catch (Exception e) { + log.error("连接到WebSocket服务器失败", e); + isConnecting = false; + // 连接失败,安排重连 + scheduleReconnect(); + } + } + + /** + * 启动心跳机制 + */ + private void startHeartbeat() { + heartbeatExecutor.scheduleAtFixedRate(() -> { + if (session != null && session.isOpen()) { + try { + session.getBasicRemote().sendText("{\"type\":\"heartbeat\"}"); + log.debug("发送心跳"); + } catch (IOException e) { + log.error("发送心跳失败", e); + } + } + }, 30, 30, TimeUnit.SECONDS); + log.info("心跳机制已启动"); + } + + /** + * 安排重连任务 + */ + private void scheduleReconnect() { + reconnectExecutor.schedule(() -> { + log.info("尝试重新连接到WebSocket服务器..."); + connect(); + }, 5, TimeUnit.SECONDS); + } + + /** + * 关闭连接和资源 + */ + @PreDestroy + public void shutdown() { + log.info("正在关闭WebSocket客户端..."); + if (session != null) { + try { + session.close(); + } catch (Exception e) { + log.error("关闭WebSocket连接失败", e); + } + } + reconnectExecutor.shutdownNow(); + heartbeatExecutor.shutdownNow(); + log.info("WebSocket客户端已关闭"); + } + + @Override + public void run(ApplicationArguments args) { + // 应用启动后连接到WebSocket服务器 + connect(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3996dad..8c822d8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,6 @@ spring.application.name=goeingPrintServer server.port=9090 +print.websocket.url=ws://127.0.0.1:8080/print-websocket +print.printer.id=123456 +print.websocket.apiKey=519883ab-3677-ce4b-59ba-7263870d0a26