From 0b85492cdb0c8ae5ace451465814a929017cb9d9 Mon Sep 17 00:00:00 2001 From: lzq <2495532633@qq.com> Date: Tue, 11 Nov 2025 18:50:47 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E7=82=AC=E5=A4=A7=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- njzscloud-common/njzscloud-common-ws/pom.xml | 38 ++++ .../ws/config/WebSocketAutoConfiguration.java | 65 +++++++ .../common/ws/config/WebsocketProperties.java | 41 ++++ .../common/ws/support/Constants.java | 22 +++ .../ws/support/ImpartialWebSocketHandler.java | 97 ++++++++++ .../ws/support/TokenHandshakeInterceptor.java | 76 ++++++++ .../common/ws/support/WSListener.java | 10 + .../common/ws/support/Websocket.java | 175 ++++++++++++++++++ .../njzscloud/common/ws/support/WsMsg.java | 92 +++++++++ .../njzscloud/common/ws/support/Wsdoor.java | 78 ++++++++ .../main/resources/META-INF/spring.factories | 2 + .../SupervisionStatisticsController.java | 21 ++- .../mapper/ShouJuStatisticsMapper.java | 3 + .../mapper/SupervisionStatisticsMapper.java | 4 + .../service/ShouJuStatisticsService.java | 124 +++++++++++++ .../service/SupervisionStatisticsService.java | 4 + .../mapper/ShouJuStatisticsMapper.xml | 7 + .../mapper/SupervisionStatisticsMapper.xml | 32 +++- pom.xml | 5 + 19 files changed, 885 insertions(+), 11 deletions(-) create mode 100644 njzscloud-common/njzscloud-common-ws/pom.xml create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/config/WebSocketAutoConfiguration.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/config/WebsocketProperties.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Constants.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/ImpartialWebSocketHandler.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/TokenHandshakeInterceptor.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/WSListener.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Websocket.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/WsMsg.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Wsdoor.java create mode 100644 njzscloud-common/njzscloud-common-ws/src/main/resources/META-INF/spring.factories diff --git a/njzscloud-common/njzscloud-common-ws/pom.xml b/njzscloud-common/njzscloud-common-ws/pom.xml new file mode 100644 index 0000000..ae641b0 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + + com.njzscloud + njzscloud-common + 0.0.1 + + + njzscloud-common-ws + jar + + + UTF-8 + + + + + com.njzscloud + njzscloud-common-core + provided + + + com.njzscloud + njzscloud-common-security + provided + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework + spring-websocket + + + diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/config/WebSocketAutoConfiguration.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/config/WebSocketAutoConfiguration.java new file mode 100644 index 0000000..29870d3 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/config/WebSocketAutoConfiguration.java @@ -0,0 +1,65 @@ +package com.njzscloud.common.ws.config; + +import com.njzscloud.common.ws.support.ImpartialWebSocketHandler; +import com.njzscloud.common.ws.support.TokenHandshakeInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.unit.DataSize; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; + +import java.time.Duration; + +@Configuration +@EnableWebSocket +@RequiredArgsConstructor +@EnableConfigurationProperties(WebsocketProperties.class) +public class WebSocketAutoConfiguration implements WebSocketConfigurer { + + private final WebsocketProperties websocketProperties; + + // websocket-heartbeat-js + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + String path = websocketProperties.getPath(); + registry.addHandler(new ImpartialWebSocketHandler(), path == null || path.isEmpty() ? "/fdx" : path) + .addInterceptors(new TokenHandshakeInterceptor()) + .setAllowedOrigins("*"); + } + + @Bean + public ServletServerContainerFactoryBean createWebSocketContainer() { + Duration asyncSendTimeout = websocketProperties.getAsyncSendTimeout(); + Duration maxSessionIdleTimeout = websocketProperties.getMaxSessionIdleTimeout(); + DataSize maxTextMessageBufferSize = websocketProperties.getMaxTextMessageBufferSize(); + DataSize maxBinaryMessageBufferSize = websocketProperties.getMaxBinaryMessageBufferSize(); + ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); + + if (asyncSendTimeout != null) { + long seconds = asyncSendTimeout.getSeconds(); + container.setAsyncSendTimeout(seconds * 1000); + } + + if (maxSessionIdleTimeout != null) { + long seconds = maxSessionIdleTimeout.getSeconds(); + container.setMaxSessionIdleTimeout(seconds * 1000); + } + + if (maxTextMessageBufferSize != null) { + long size = maxTextMessageBufferSize.toBytes(); + container.setMaxTextMessageBufferSize((int) size); + } + + if (maxBinaryMessageBufferSize != null) { + long size = maxBinaryMessageBufferSize.toBytes(); + container.setMaxBinaryMessageBufferSize((int) size); + } + + return container; + } +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/config/WebsocketProperties.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/config/WebsocketProperties.java new file mode 100644 index 0000000..3f2c0c1 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/config/WebsocketProperties.java @@ -0,0 +1,41 @@ +package com.njzscloud.common.ws.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.unit.DataSize; + +import java.time.Duration; + +/** + * websocket 配置 + */ +@Getter +@Setter +@ConfigurationProperties("ws") +public class WebsocketProperties { + /** + * websocket 请求地址, 默认 /fdx + */ + private String path = "/fdx"; + + /** + * 发送超时时间 + */ + private Duration asyncSendTimeout; + + /** + * 会话超时时间, 默认 15min + */ + private Duration maxSessionIdleTimeout = Duration.ofMinutes(15); + + /** + * 文本消息缓存大小, 默认 2MB + */ + private DataSize maxTextMessageBufferSize = DataSize.ofMegabytes(2); + + /** + * 二进制消息缓存大小, 默认 2MB + */ + private DataSize maxBinaryMessageBufferSize = DataSize.ofMegabytes(2); +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Constants.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Constants.java new file mode 100644 index 0000000..adb1b09 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Constants.java @@ -0,0 +1,22 @@ +package com.njzscloud.common.ws.support; + +/** + * 常量 + */ +public class Constants { + /** + * TOKEN 属性 + */ + public static final String SESSION_ATTRIBUTE_TOKEN = "token"; + + /** + * 客户端 Id 属性 + */ + public static final String SESSION_ATTRIBUTE_CID = "cid"; + + /** + * 用户 Id 属性 + */ + public static final String SESSION_ATTRIBUTE_UID = "uid"; + +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/ImpartialWebSocketHandler.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/ImpartialWebSocketHandler.java new file mode 100644 index 0000000..bc1edd9 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/ImpartialWebSocketHandler.java @@ -0,0 +1,97 @@ +package com.njzscloud.common.ws.support; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.njzscloud.common.core.ex.ExceptionMsg; +import com.njzscloud.common.core.utils.R; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; + + +/** + * websocket 处理器
+ * 不支持分片消息
+ * 支持文本、二进制信息 + */ +@Slf4j +public class ImpartialWebSocketHandler extends AbstractWebSocketHandler { + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + Wsdoor wsdoor = Wsdoor.of(session); + String cid = wsdoor.getCid(); + Long uid = wsdoor.getUid(); + wsdoor.send(new WsMsg() + .setEvent("connected") + .setData(R.success( + MapUtil.builder() + .put("uid", uid) + .put("cid", cid) + .build() + , "连接成功"))); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + Wsdoor wsdoor = Wsdoor.of(session); + WsMsg wsMsg = WsMsg.of(wsdoor, message); + + if (wsMsg == null + || StrUtil.isBlank(wsMsg.getAction()) + || StrUtil.isBlank(wsMsg.getEvent())) { + wsdoor.send(new WsMsg().setEvent("error") + .setData(R.failed(ExceptionMsg.CLI_ERR_MSG, "消息格式错误"))); + session.close(); + return; + } + String action = wsMsg.getAction(); + + if (!Websocket.ACTION_PUBLISH.equals(action) + && !Websocket.ACTION_PING.equals(action) + && !Websocket.ACTION_UNSUBSCRIBE.equals(action) + // && !Websocket.ACTION_PONG.equals(action) + && !Websocket.ACTION_SUBSCRIBE.equals(action) + ) { + wsdoor.send(new WsMsg().setEvent("error") + .setData(R.failed(ExceptionMsg.CLI_ERR_MSG, "消息格式错误"))); + session.close(); + return; + } + + String event = wsMsg.getEvent(); + if (Websocket.ACTION_PUBLISH.equals(action)) { + Websocket.publish(wsMsg); + } else if (Websocket.ACTION_SUBSCRIBE.equals(action)) { + WsMsg.User from = wsMsg.getFrom(); + log.info("用户Id:{},客户端Id:{},订阅:{}", from.getUid(), from.getCid(), event); + Websocket.subscribe(event, wsdoor); + } else if (Websocket.ACTION_UNSUBSCRIBE.equals(action)) { + WsMsg.User from = wsMsg.getFrom(); + log.info("用户Id:{},客户端Id:{},取消订阅:{}", from.getUid(), from.getCid(), event); + Websocket.unsubscribe(event, wsdoor); + } else { + wsdoor.send(new WsMsg().setAction(Websocket.ACTION_PONG)); + } + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + Wsdoor wsdoor = Wsdoor.of(session); + String cid = wsdoor.getCid(); + Long uid = wsdoor.getUid(); + log.error("连接失败: {} {}", cid, uid, exception); + Websocket.unsubscribe(wsdoor); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { + Wsdoor wsdoor = Wsdoor.of(session); + String cid = wsdoor.getCid(); + Long uid = wsdoor.getUid(); + log.info("连接关闭: {} {} {}", cid, uid, closeStatus); + Websocket.unsubscribe(wsdoor); + } +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/TokenHandshakeInterceptor.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/TokenHandshakeInterceptor.java new file mode 100644 index 0000000..4dc18b0 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/TokenHandshakeInterceptor.java @@ -0,0 +1,76 @@ +package com.njzscloud.common.ws.support; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.njzscloud.common.core.tuple.Tuple2; +import com.njzscloud.common.security.support.Token; +import com.njzscloud.common.security.support.UserDetail; +import com.njzscloud.common.security.util.SecurityUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +// TODO TOKEN 处理器 + +/** + * websocket Token 拦截器
+ * 子协议格式: Auth [token],[cid], 如: Auth aimsn,niosdawq
+ * token: 用户登录凭证; cid: 客户端号, 推荐使用 token Id + */ +@Slf4j +public class TokenHandshakeInterceptor implements HandshakeInterceptor { + private static final String SEC_WEB_SOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; + private static final String Authorization = "authorization"; + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler wsHandler, Map attributes) { + try { + HttpServletRequest req = ((ServletServerHttpRequest) request).getServletRequest(); + String authorization = req.getParameter(Authorization); + // if (StrUtil.isBlank(authorization)) return false; + + Tuple2 resolved = this.resolve(authorization); + if (resolved == null) return false; + Long uid = resolved.get_0(); + String cid = resolved.get_1(); + attributes.put(Constants.SESSION_ATTRIBUTE_TOKEN, StrUtil.isBlank(authorization) ? cid : authorization); + attributes.put(Constants.SESSION_ATTRIBUTE_CID, cid); + attributes.put(Constants.SESSION_ATTRIBUTE_UID, uid); + return true; + } catch (Exception e) { + log.error("[Websocket 拦截器] 数据解析失败", e); + return false; + } + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + try { + HttpServletRequest httpRequest = ((ServletServerHttpRequest) request).getServletRequest(); + HttpServletResponse httpResponse = ((ServletServerHttpResponse) response).getServletResponse(); + String token = httpRequest.getHeader(SEC_WEB_SOCKET_PROTOCOL); + if (StrUtil.isNotEmpty(token)) { + httpResponse.addHeader(SEC_WEB_SOCKET_PROTOCOL, token); + } + } catch (ClassCastException e) { + log.error("[Websocket 拦截器] 类型转换失败: {}、{}", request.getClass(), response.getClass(), e); + } + } + + private Tuple2 resolve(String tokenStr) { + if (StrUtil.isBlank(tokenStr)) return Tuple2.create(1L, IdUtil.fastSimpleUUID()); + UserDetail userDetail = SecurityUtil.parseToken(tokenStr); + if (userDetail == null) return null; + Token token = userDetail.getToken(); + if (token == null) return null; + return Tuple2.create(userDetail.getUserId(), token.getTid()); + } +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/WSListener.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/WSListener.java new file mode 100644 index 0000000..86b5c3b --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/WSListener.java @@ -0,0 +1,10 @@ +package com.njzscloud.common.ws.support; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface WSListener { + String value(); +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Websocket.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Websocket.java new file mode 100644 index 0000000..f1e7fc5 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Websocket.java @@ -0,0 +1,175 @@ +package com.njzscloud.common.ws.support; + +import cn.hutool.core.collection.CollUtil; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * websocket 工具 + */ +@Slf4j +public class Websocket { + + public static final String ACTION_PUBLISH = "publish"; + public static final String ACTION_PING = "ping"; + public static final String ACTION_PONG = "pong"; + public static final String ACTION_SUBSCRIBE = "subscribe"; + public static final String ACTION_UNSUBSCRIBE = "unsubscribe"; + + /** + * 客户端事件订阅列表 + */ + static final ConcurrentHashMap> EVENT_CACHE = new ConcurrentHashMap<>(); + static final ReentrantReadWriteLock EVENT_CACHE_LOCK = new ReentrantReadWriteLock(); + static final ReentrantReadWriteLock.ReadLock EVENT_CACHE_READ_LOCK = EVENT_CACHE_LOCK.readLock(); + static final ReentrantReadWriteLock.WriteLock EVENT_CACHE_WRITE_LOCK = EVENT_CACHE_LOCK.writeLock(); + + static final ConcurrentHashMap>> LISTENER_CACHE = new ConcurrentHashMap<>(); + static final ReentrantReadWriteLock LISTENER_CACHE_LOCK = new ReentrantReadWriteLock(); + static final ReentrantReadWriteLock.ReadLock LISTENER_CACHE_READ_LOCK = LISTENER_CACHE_LOCK.readLock(); + static final ReentrantReadWriteLock.WriteLock LISTENER_CACHE_WRITE_LOCK = LISTENER_CACHE_LOCK.writeLock(); + + public static boolean publish(WsMsg wsMsg) { + wsMsg.setAction(ACTION_PUBLISH); + // 总数,成功数 + Integer[] success = {0, 0}; + String event = wsMsg.getEvent(); + List to = wsMsg.getTo(); + Set uids; + Set cids; + if (CollUtil.isNotEmpty(to)) { + uids = to.stream().map(WsMsg.User::getUid).collect(Collectors.toSet()); + cids = to.stream().map(WsMsg.User::getCid).collect(Collectors.toSet()); + } else { + uids = Collections.emptySet(); + cids = Collections.emptySet(); + } + EVENT_CACHE_READ_LOCK.lock(); + try { + EVENT_CACHE.computeIfPresent(event, (k, v) -> { + if (CollUtil.isEmpty(v)) return v; + List wsdoors = v; + if (!uids.isEmpty() || !cids.isEmpty()) { + wsdoors = v.stream().filter(it -> { + Long uid = it.getUid(); + String cid = it.getCid(); + return cids.contains(cid) || uids.contains(uid); + }).collect(Collectors.toList()); + } + success[1] = wsdoors.size(); + for (Wsdoor wsdoor : wsdoors) { + boolean send = wsdoor.send(wsMsg); + if (send) success[1] = success[1] + 1; + } + return v; + }); + } finally { + EVENT_CACHE_READ_LOCK.unlock(); + } + + LISTENER_CACHE_READ_LOCK.lock(); + try { + LISTENER_CACHE.computeIfPresent(event, (k, v) -> { + if (CollUtil.isEmpty(v)) return v; + success[1] = success[1] + v.size(); + for (Consumer listener : v) { + try { + listener.accept(wsMsg); + success[1] = success[1] + 1; + } catch (Exception e) { + log.error("事件 {} 监听方法执行异常", event, e); + } + } + return v; + }); + } finally { + LISTENER_CACHE_READ_LOCK.unlock(); + } + + return success[1] > 0; + } + + public static boolean publish(String event, Object data) { + return publish(new WsMsg() + .setAction(ACTION_PUBLISH) + .setEvent(event) + .setData(data)); + } + + public static void subscribe(String event, Wsdoor wsdoor) { + EVENT_CACHE_READ_LOCK.lock(); + try { + EVENT_CACHE.computeIfAbsent(event, k -> new LinkedList<>()); + + EVENT_CACHE.computeIfPresent(event, (k, v) -> { + v.add(wsdoor); + return v; + }); + } finally { + EVENT_CACHE_READ_LOCK.unlock(); + } + } + + public static void unsubscribe(Wsdoor wsdoor) { + EVENT_CACHE_WRITE_LOCK.lock(); + try { + EVENT_CACHE.forEach((k, v) -> { + v.removeIf(it -> it.equals(wsdoor)); + }); + EVENT_CACHE.entrySet().removeIf(it -> CollUtil.isEmpty(it.getValue())); + } finally { + EVENT_CACHE_WRITE_LOCK.unlock(); + } + } + + public static void unsubscribe(String event, Wsdoor wsdoor) { + EVENT_CACHE_WRITE_LOCK.lock(); + try { + EVENT_CACHE.computeIfPresent(event, (k, v) -> { + v.removeIf(it -> it.equals(wsdoor)); + return v; + }); + EVENT_CACHE.entrySet().removeIf(it -> CollUtil.isEmpty(it.getValue())); + } finally { + EVENT_CACHE_WRITE_LOCK.unlock(); + } + } + + /** + * 系统内部专用订阅, 用于系统事件通知
+ * + * @param event 事件名称 + */ + public static void subscribe(String event, Consumer listener) { + LISTENER_CACHE_READ_LOCK.lock(); + try { + LISTENER_CACHE.computeIfAbsent(event, k -> new LinkedList<>()); + LISTENER_CACHE.computeIfPresent(event, (k, v) -> { + v.add(listener); + return v; + }); + } finally { + LISTENER_CACHE_READ_LOCK.unlock(); + } + } + + public static void unsubscribe(String event, Consumer listener) { + LISTENER_CACHE_READ_LOCK.lock(); + try { + LISTENER_CACHE.computeIfPresent(event, (k, v) -> { + v.removeIf(it -> it == listener); + return v; + }); + } finally { + LISTENER_CACHE_READ_LOCK.unlock(); + } + } +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/WsMsg.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/WsMsg.java new file mode 100644 index 0000000..14a64c9 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/WsMsg.java @@ -0,0 +1,92 @@ +package com.njzscloud.common.ws.support; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.njzscloud.common.core.jackson.Jackson; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.TextMessage; + +import java.util.List; + +/** + * websocket 消息内容 + */ +@Slf4j +@Getter +@Setter +@Accessors(chain = true) +public class WsMsg { + /** + * 发送方 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private User from; + + /** + * 接收方 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private List to; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String action; + + /** + * 事件 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String event; + + /** + * 数据内容 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Object data; + + public static WsMsg of(Wsdoor wsdoor, TextMessage message) { + String payload = message.getPayload(); + try { + WsMsg wsMsg = Jackson.toBean(payload, WsMsg.class); + if (wsMsg != null) { + wsMsg.setFrom(new User() + .setUid(wsdoor.getUid()) + .setCid(wsdoor.getCid()) + ); + } + return wsMsg; + } catch (Exception e) { + log.error("WebSocket 消息反序列化失败, 数据: {}", payload, e); + return null; + } + } + + @Override + public String toString() { + return Jackson.toJsonStr(this); + } + + public T extractData(Class clazz) { + return Jackson.toBean(Jackson.toJsonStr(data), clazz); + } + + @Getter + @Setter + @ToString + @Accessors(chain = true) + public static class User { + /** + * 用户 Id + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long uid; + /** + * 客户端 Id + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String cid; + } +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Wsdoor.java b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Wsdoor.java new file mode 100644 index 0000000..386f163 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/java/com/njzscloud/common/ws/support/Wsdoor.java @@ -0,0 +1,78 @@ +package com.njzscloud.common.ws.support; + +import cn.hutool.core.map.MapUtil; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.util.Map; +import java.util.Objects; + +@Slf4j +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class Wsdoor { + + /** + * 用户 Id + */ + private final Long uid; + /** + * 客户端 Id + */ + private final String cid; + /** + * TOKEN + */ + private final String token; + /** + * 连接会话 + */ + private final WebSocketSession session; + + private Wsdoor(WebSocketSession session) { + Map attributes = session.getAttributes(); + this.uid = MapUtil.getLong(attributes, "uid"); + this.cid = MapUtil.getStr(attributes, "cid"); + this.token = MapUtil.getStr(attributes, "token"); + this.session = session; + } + + public static Wsdoor of(WebSocketSession session) { + return new Wsdoor(session); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Wsdoor wsdoor = (Wsdoor) o; + return Objects.equals(uid, wsdoor.uid) && Objects.equals(cid, wsdoor.cid); + } + + @Override + public int hashCode() { + return Objects.hash(uid, cid); + } + + /** + * 发送文本数据 + * + * @param wsMsg 消息 + */ + public boolean send(WsMsg wsMsg) { + try { + if (!session.isOpen()) return false; + session.sendMessage(new TextMessage(wsMsg.toString())); + return true; + } catch (Exception e) { + log.error("消息发送失败", e); + } + return false; + } +} diff --git a/njzscloud-common/njzscloud-common-ws/src/main/resources/META-INF/spring.factories b/njzscloud-common/njzscloud-common-ws/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..9e894b6 --- /dev/null +++ b/njzscloud-common/njzscloud-common-ws/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration = \ + com.njzscloud.common.ws.config.WebSocketAutoConfiguration diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/controller/SupervisionStatisticsController.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/controller/SupervisionStatisticsController.java index abc858c..6150115 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/controller/SupervisionStatisticsController.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/controller/SupervisionStatisticsController.java @@ -1,19 +1,30 @@ package com.njzscloud.supervisory.statistics.controller; -/* @Slf4j +import com.njzscloud.common.core.utils.R; +import com.njzscloud.supervisory.statistics.service.SupervisionStatisticsService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@Slf4j @CrossOrigin @RestController @RequiredArgsConstructor -@RequestMapping("/statistics/supervision") */ +@RequestMapping("/statistics/supervision") public class SupervisionStatisticsController { - // private final SupervisionStatisticsService supervisionStatisticsService; + private final SupervisionStatisticsService supervisionStatisticsService; - /* @GetMapping("/obtain_data") + @GetMapping("/obtain_data") public R obtainData() { long l = System.currentTimeMillis(); Map data = supervisionStatisticsService.obtainData(); System.out.println("耗时: " + (System.currentTimeMillis() - l)); return R.success(data); - } */ + } } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/mapper/ShouJuStatisticsMapper.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/mapper/ShouJuStatisticsMapper.java index 6aea025..3f812fd 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/mapper/ShouJuStatisticsMapper.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/mapper/ShouJuStatisticsMapper.java @@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; +import java.util.Map; @Mapper public interface ShouJuStatisticsMapper { @@ -28,4 +29,6 @@ public interface ShouJuStatisticsMapper { Double disposeWeight(@Param("startTime") Long startTime, @Param("endTime") Long endTime); List getOrderAmountSummary(@Param("startTime") Long startTime, @Param("endTime") Long endTime); + + List> getChuzhi(); } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/mapper/SupervisionStatisticsMapper.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/mapper/SupervisionStatisticsMapper.java index 9b7363f..51a8beb 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/mapper/SupervisionStatisticsMapper.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/mapper/SupervisionStatisticsMapper.java @@ -4,6 +4,7 @@ import com.njzscloud.supervisory.statistics.pojo.*; import org.apache.ibatis.annotations.Mapper; import java.util.List; +import java.util.Map; @Mapper public interface SupervisionStatisticsMapper { @@ -27,4 +28,7 @@ public interface SupervisionStatisticsMapper { List getProjects(); + Integer getTotalCarCount(); + + List> getKehuList(); } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/service/ShouJuStatisticsService.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/service/ShouJuStatisticsService.java index 68a4878..e410147 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/service/ShouJuStatisticsService.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/service/ShouJuStatisticsService.java @@ -4,6 +4,10 @@ import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.Week; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.njzscloud.common.core.tuple.Tuple2; import com.njzscloud.common.core.tuple.Tuple3; @@ -28,6 +32,104 @@ public class ShouJuStatisticsService implements StatisticsService { private final String[] weeks = {"周一", "周二", "周三", "周四", "周五", "周六", "周日"}; private final String[] weeks_ = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"}; private final String[] months = {"1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"}; + private final String group = "[\n" + + " {\n" + + " \"C1\": \"256\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"257\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"263\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"275\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"289\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"292\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"310\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"316\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"317\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"319\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"323\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"324\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"325\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"327\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"330\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"331\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"334\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"335\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"337\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"338\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"339\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"340\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"341\",\n" + + " \"C2\": \"装修垃圾\"\n" + + " },\n" + + " {\n" + + " \"C1\": \"346\",\n" + + " \"C2\": \"拆除垃圾\"\n" + + " }\n" + + "]"; @Override public Company getCompany() { @@ -298,6 +400,27 @@ public class ShouJuStatisticsService implements StatisticsService { return new TodayOrderSummary(); }); + List> chuzhi = statisticsMapper.getChuzhi(); + JSONArray objects = JSON.parseArray(group); + + Map map1 = GroupUtil.k_o(objects, it -> { + JSONObject object = (JSONObject) it; + return object.getInteger("C1"); + }, it -> { + JSONObject object = (JSONObject) it; + return object.getString("C2"); + }); + + HashMap map2 = new HashMap<>(); + + for (Map m : chuzhi) { + Integer id = MapUtil.getInt(m, "id"); + Integer weight = MapUtil.getInt(m, "weight"); + String s1 = map1.get(id); + if (StrUtil.isBlank(s1)) continue; + map2.compute(s1, (k, v) -> v == null ? 0 : v + weight); + } + Tuple2, List> weekAmount_ = weekAmount.get(); Tuple3, List, List> monthAmount_ = monthAmount.get(); Tuple2, List> yearAmount_ = yearAmount.get(); @@ -320,6 +443,7 @@ public class ShouJuStatisticsService implements StatisticsService { .put("days", monthAmount_.get_2()) .put("yearAmountIn", yearAmount_.get_0()) .put("yearAmountOut", yearAmount_.get_1()) + .put("chuzhi", chuzhi) .build(); } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/service/SupervisionStatisticsService.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/service/SupervisionStatisticsService.java index df6359c..b6c320b 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/service/SupervisionStatisticsService.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/statistics/service/SupervisionStatisticsService.java @@ -27,11 +27,13 @@ public class SupervisionStatisticsService { CarSummary carSummary = supervisionStatisticsMapper.getCarSummary(); Integer todayCarCount = supervisionStatisticsMapper.getTodayCarCount(); + Integer totalCarCount = supervisionStatisticsMapper.getTotalCarCount(); List regionSummary = supervisionStatisticsMapper.getRegionSummary(); List companySummary = supervisionStatisticsMapper.getCompanySummary(); List stationInfo = supervisionStatisticsMapper.getStationInfo(); List orders = supervisionStatisticsMapper.getOrders(); List projects = supervisionStatisticsMapper.getProjects(); + List> kehu = supervisionStatisticsMapper.getKehuList(); return MapUtil.builder() .put("dispatchSummary", dispatchSummary == null ? new DispatchSummary() : dispatchSummary) @@ -44,6 +46,8 @@ public class SupervisionStatisticsService { .put("stationInfo", stationInfo == null ? new ArrayList<>() : stationInfo) .put("orders", orders == null ? new ArrayList<>() : orders) .put("projects", projects == null ? new ArrayList<>() : projects) + .put("totalCarCount", totalCarCount == null ? 0 : totalCarCount) + .put("kehu", kehu == null ? new ArrayList<>() : kehu) .build(); } diff --git a/njzscloud-svr/src/main/resources/mapper/ShouJuStatisticsMapper.xml b/njzscloud-svr/src/main/resources/mapper/ShouJuStatisticsMapper.xml index ccce709..e1a42e3 100644 --- a/njzscloud-svr/src/main/resources/mapper/ShouJuStatisticsMapper.xml +++ b/njzscloud-svr/src/main/resources/mapper/ShouJuStatisticsMapper.xml @@ -189,4 +189,11 @@ LIMIT #{count} + diff --git a/njzscloud-svr/src/main/resources/mapper/SupervisionStatisticsMapper.xml b/njzscloud-svr/src/main/resources/mapper/SupervisionStatisticsMapper.xml index 884f31a..c64ecf8 100644 --- a/njzscloud-svr/src/main/resources/mapper/SupervisionStatisticsMapper.xml +++ b/njzscloud-svr/src/main/resources/mapper/SupervisionStatisticsMapper.xml @@ -38,14 +38,14 @@ FROM (SELECT COUNT(*) c, 1 t FROM ba_order a INNER JOIN ba_order_dispatch b ON b.order_no = a.order_no - WHERE a.create_time >= UNIX_TIMESTAMP(CURDATE()) - AND a.create_time UNIX_TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL -24 HOUR)) + WHERE UNIX_TIMESTAMP(a.create_time) >= UNIX_TIMESTAMP(CURDATE()) + AND UNIX_TIMESTAMP(a.create_time) UNIX_TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL -24 HOUR)) UNION ALL SELECT COUNT(*) c, 2 t FROM ba_order a INNER JOIN ba_order_dispatch b ON b.order_no = a.order_no - WHERE a.create_time >= UNIX_TIMESTAMP(CURDATE()) - AND a.create_time UNIX_TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL 24 HOUR))) t) tt + WHERE UNIX_TIMESTAMP(a.create_time) >= UNIX_TIMESTAMP(CURDATE()) + AND UNIX_TIMESTAMP(a.create_time) UNIX_TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL 24 HOUR))) t) tt @@ -155,4 +155,24 @@ ORDER BY g.create_time DESC LIMIT 10 + + + diff --git a/pom.xml b/pom.xml index 9aea99f..9ddf3e1 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,11 @@ njzscloud-common-sichen 0.0.1 + + com.njzscloud + njzscloud-common-ws + 0.0.1 + com.njzscloud njzscloud-common-sn