| | |
| | | // 降级返回空列表 |
| | | return R.fail("通过运营人员id查询车队信息失败,"+cause.getMessage()); |
| | | } |
| | | |
| | | @Override |
| | | public R<Long> getUseCountByLocationId(Long locationId) { |
| | | log.error("查询打卡地被使用次数失败,原因: {}", cause.getMessage()); |
| | | return R.fail("查询打卡地被使用次数失败"); |
| | | } |
| | | }; |
| | | } |
| | | } |
| | |
| | | */ |
| | | @GetMapping("/inner/v1/fleet/getFleetByOperationsUserId") |
| | | R<XpFleet> getFleetByOperationsUserId(@RequestParam Long operationsUserId); |
| | | |
| | | /** |
| | | * 根据打卡地址id查询被车辆使用次数 |
| | | * @param locationId 打卡地址id |
| | | * @return |
| | | */ |
| | | @GetMapping("/inner/fleet/getUseCountByLocationId") |
| | | R<Long> getUseCountByLocationId(@RequestParam Long locationId); |
| | | |
| | | } |
| | |
| | | package com.xiaopeng.maintenance.controller; |
| | | |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient; |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient; |
| | | import com.xiaopeng.api.model.maintenance.XpCheckLocation; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.common.core.enums.DisabledEnum; |
| | |
| | | @RequiredArgsConstructor(onConstructor_ = @Lazy) |
| | | public class XpCheckLocationController { |
| | | private final IXpCheckLocationService xpCheckLocationService; |
| | | private final XpFleetClient xpFleetClient; |
| | | private final TokenService tokenService; |
| | | |
| | | @GetMapping("/api/v1/check-location/list") |
| | |
| | | @DeleteMapping("/api/v1/check-location/deleteById") |
| | | @ApiOperation(value = "删除打卡地", tags = {"管理后台-运维执行"}) |
| | | public R<String> deleteById(@RequestParam Long id){ |
| | | // 判断打卡地是否已被使用 |
| | | Long data = xpFleetClient.getUseCountByLocationId(id).getData(); |
| | | if (data > 0) { |
| | | return R.fail("打卡地已被使用,请先解除关联"); |
| | | } |
| | | xpCheckLocationService.removeById(id); |
| | | return R.ok(); |
| | | } |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.config; |
| | | |
| | | import com.xiaopeng.pushMessage.webSocket.handler.PushMessageWebSocketHandler; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.web.socket.config.annotation.EnableWebSocket; |
| | | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; |
| | | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; |
| | | |
| | | /** |
| | | * Spring WebSocket 配置 |
| | | * 注册 /websocket 端点,共用 Spring Boot HTTP 端口 |
| | | */ |
| | | @Configuration |
| | | @EnableWebSocket |
| | | public class WebSocketConfig implements WebSocketConfigurer { |
| | | |
| | | @Autowired |
| | | private PushMessageWebSocketHandler pushMessageWebSocketHandler; |
| | | |
| | | @Override |
| | | public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { |
| | | registry.addHandler(pushMessageWebSocketHandler, "/websocket") |
| | | // 允许所有跨域来源,生产环境可按需收紧 |
| | | .setAllowedOrigins("*"); |
| | | } |
| | | } |
| | | //package com.xiaopeng.pushMessage.webSocket.config; |
| | | // |
| | | //import com.xiaopeng.pushMessage.webSocket.handler.PushMessageWebSocketHandler; |
| | | //import org.springframework.beans.factory.annotation.Autowired; |
| | | //import org.springframework.context.annotation.Configuration; |
| | | //import org.springframework.web.socket.config.annotation.EnableWebSocket; |
| | | //import org.springframework.web.socket.config.annotation.WebSocketConfigurer; |
| | | //import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; |
| | | // |
| | | ///** |
| | | // * Spring WebSocket 配置 |
| | | // * 注册 /websocket 端点,共用 Spring Boot HTTP 端口 |
| | | // */ |
| | | //@Configuration |
| | | //@EnableWebSocket |
| | | //public class WebSocketConfig implements WebSocketConfigurer { |
| | | // |
| | | // @Autowired |
| | | // private PushMessageWebSocketHandler pushMessageWebSocketHandler; |
| | | // |
| | | // @Override |
| | | // public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { |
| | | // registry.addHandler(pushMessageWebSocketHandler, "/websocket") |
| | | // // 允许所有跨域来源,生产环境可按需收紧 |
| | | // .setAllowedOrigins("*"); |
| | | // } |
| | | //} |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.handler; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager; |
| | | import io.netty.channel.ChannelHandlerContext; |
| | | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | import org.springframework.web.socket.CloseStatus; |
| | | import org.springframework.web.socket.TextMessage; |
| | | import org.springframework.web.socket.WebSocketSession; |
| | | import org.springframework.web.socket.handler.TextWebSocketHandler; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * Spring WebSocket 消息处理器 |
| | | * 路径: /websocket,共用 Spring Boot HTTP 端口 |
| | | * 替代原 Netty 独立端口方案 |
| | | */ |
| | | @Component |
| | | @Slf4j |
| | | public class PushMessageWebSocketHandler extends TextWebSocketHandler { |
| | | |
| | | @Autowired |
| | | private WebSocketUserConnectionManager userConnectionManager; |
| | | |
| | | @Autowired |
| | | private WebSocketAuthHandler authHandler; |
| | | |
| | | // ==================== 连接生命周期 ==================== |
| | | |
| | | @Override |
| | | public void afterConnectionEstablished(WebSocketSession session) throws Exception { |
| | | log.info("WebSocket 连接建立,sessionId: {}", session.getId()); |
| | | } |
| | | |
| | | @Override |
| | | public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { |
| | | log.info("WebSocket 连接关闭,sessionId: {}, status: {}", session.getId(), status); |
| | | userConnectionManager.removeUserConnection(session); |
| | | } |
| | | |
| | | @Override |
| | | public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { |
| | | log.error("WebSocket 传输异常,sessionId: {}, error: {}", session.getId(), exception.getMessage(), exception); |
| | | if (session.isOpen()) { |
| | | session.close(CloseStatus.SERVER_ERROR); |
| | | } |
| | | } |
| | | |
| | | // ==================== 消息处理 ==================== |
| | | |
| | | @Override |
| | | protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { |
| | | String payload = message.getPayload(); |
| | | log.info("[服务器] 收到消息 -> {}", payload); |
| | | |
| | | try { |
| | | JSONObject messageObj = JSON.parseObject(payload); |
| | | String messageType = messageObj.getString("type"); |
| | | |
| | | if ("auth".equals(messageType)) { |
| | | handleUserAuth(session, messageObj); |
| | | } else if ("ping".equals(messageType)) { |
| | | handlePing(session); |
| | | } else { |
| | | handleBusinessMessage(session, messageObj); |
| | | } |
| | | } catch (Exception e) { |
| | | log.debug("消息非 JSON 格式,按普通文本处理: {}", payload); |
| | | sendText(session, "服务器时间: " + LocalDateTime.now() + "->" + payload); |
| | | } |
| | | } |
| | | |
| | | // ==================== 业务处理 ==================== |
| | | |
| | | /** |
| | | * 处理用户认证消息 |
| | | */ |
| | | private void handleUserAuth(WebSocketSession session, JSONObject messageObj) { |
| | | try { |
| | | WebSocketAuthHandler.UserAuthInfo authInfo = authHandler.extractAuthInfo(messageObj); |
| | | String appUserId = authInfo.getAppUserId(); |
| | | String token = authInfo.getToken(); |
| | | |
| | | if (!StringUtils.hasText(appUserId)) { |
| | | sendAuthFailure(session, "用户ID不能为空"); |
| | | return; |
| | | } |
| | | |
| | | WebSocketAuthHandler.AuthResult authResult = authHandler.authenticateUser(appUserId, token); |
| | | if (authResult.isSuccess()) { |
| | | userConnectionManager.addUserConnection(appUserId, session); |
| | | |
| | | // TODO |
| | | String uuid = UUID.randomUUID().toString(); |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_success"); |
| | | response.put("appUserId", appUserId); |
| | | response.put("nickName", uuid); |
| | | response.put("message", "认证成功"); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | sendText(session, response.toJSONString()); |
| | | log.info("用户 {} ({}) 认证成功,sessionId: {}", appUserId, uuid, session.getId()); |
| | | } else { |
| | | sendAuthFailure(session, authResult.getMessage()); |
| | | log.warn("用户 {} 认证失败: {}, sessionId: {}", appUserId, authResult.getMessage(), session.getId()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理用户认证时发生错误", e); |
| | | sendAuthFailure(session, "认证过程中发生错误: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private void sendAuthFailure(WebSocketSession session, String message) { |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_failed"); |
| | | response.put("message", message); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | sendText(session, response.toJSONString()); |
| | | } |
| | | |
| | | /** |
| | | * 处理心跳消息 |
| | | */ |
| | | private void handlePing(WebSocketSession session) { |
| | | String appUserId = userConnectionManager.getUserIdBySessionId(session.getId()); |
| | | |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "pong"); |
| | | response.put("appUserId", appUserId); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | log.info("心跳用户id:{}", appUserId); |
| | | |
| | | sendText(session, response.toJSONString()); |
| | | } |
| | | |
| | | /** |
| | | * 处理业务消息 |
| | | */ |
| | | private void handleBusinessMessage(WebSocketSession session, JSONObject messageObj) { |
| | | String appUserId = userConnectionManager.getUserIdBySessionId(session.getId()); |
| | | |
| | | if (appUserId != null) { |
| | | log.info("收到用户 {} 的业务消息: {}", appUserId, messageObj.toJSONString()); |
| | | |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "message_response"); |
| | | response.put("appUserId", appUserId); |
| | | response.put("originalMessage", messageObj); |
| | | response.put("serverTime", LocalDateTime.now().toString()); |
| | | |
| | | sendText(session, response.toJSONString()); |
| | | } else { |
| | | log.warn("收到未认证连接的业务消息: {}", messageObj.toJSONString()); |
| | | |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_required"); |
| | | response.put("message", "请先进行用户认证"); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | sendText(session, response.toJSONString()); |
| | | } |
| | | } |
| | | |
| | | // ==================== 工具方法 ==================== |
| | | |
| | | /** |
| | | * 发送文本消息(线程安全) |
| | | */ |
| | | private void sendText(WebSocketSession session, String text) { |
| | | try { |
| | | if (session.isOpen()) { |
| | | synchronized (session) { |
| | | session.sendMessage(new TextMessage(text)); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("发送消息失败,sessionId: {}, error: {}", session.getId(), e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | //package com.xiaopeng.pushMessage.webSocket.handler; |
| | | // |
| | | //import com.alibaba.fastjson.JSON; |
| | | //import com.alibaba.fastjson.JSONObject; |
| | | //import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager; |
| | | //import io.netty.channel.ChannelHandlerContext; |
| | | //import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
| | | //import lombok.extern.slf4j.Slf4j; |
| | | //import org.springframework.beans.factory.annotation.Autowired; |
| | | //import org.springframework.stereotype.Component; |
| | | //import org.springframework.util.StringUtils; |
| | | //import org.springframework.web.socket.CloseStatus; |
| | | //import org.springframework.web.socket.TextMessage; |
| | | //import org.springframework.web.socket.WebSocketSession; |
| | | //import org.springframework.web.socket.handler.TextWebSocketHandler; |
| | | // |
| | | //import java.time.LocalDateTime; |
| | | //import java.util.UUID; |
| | | // |
| | | ///** |
| | | // * Spring WebSocket 消息处理器 |
| | | // * 路径: /websocket,共用 Spring Boot HTTP 端口 |
| | | // * 替代原 Netty 独立端口方案 |
| | | // */ |
| | | //@Component |
| | | //@Slf4j |
| | | //public class PushMessageWebSocketHandler extends TextWebSocketHandler { |
| | | // |
| | | // @Autowired |
| | | // private WebSocketUserConnectionManager userConnectionManager; |
| | | // |
| | | // @Autowired |
| | | // private WebSocketAuthHandler authHandler; |
| | | // |
| | | // // ==================== 连接生命周期 ==================== |
| | | // |
| | | // @Override |
| | | // public void afterConnectionEstablished(WebSocketSession session) throws Exception { |
| | | // log.info("WebSocket 连接建立,sessionId: {}", session.getId()); |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { |
| | | // log.info("WebSocket 连接关闭,sessionId: {}, status: {}", session.getId(), status); |
| | | // userConnectionManager.removeUserConnection(session); |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { |
| | | // log.error("WebSocket 传输异常,sessionId: {}, error: {}", session.getId(), exception.getMessage(), exception); |
| | | // if (session.isOpen()) { |
| | | // session.close(CloseStatus.SERVER_ERROR); |
| | | // } |
| | | // } |
| | | // |
| | | // // ==================== 消息处理 ==================== |
| | | // |
| | | // @Override |
| | | // protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { |
| | | // String payload = message.getPayload(); |
| | | // log.info("[服务器] 收到消息 -> {}", payload); |
| | | // |
| | | // try { |
| | | // JSONObject messageObj = JSON.parseObject(payload); |
| | | // String messageType = messageObj.getString("type"); |
| | | // |
| | | // if ("auth".equals(messageType)) { |
| | | // handleUserAuth(session, messageObj); |
| | | // } else if ("ping".equals(messageType)) { |
| | | // handlePing(session); |
| | | // } else { |
| | | // handleBusinessMessage(session, messageObj); |
| | | // } |
| | | // } catch (Exception e) { |
| | | // log.debug("消息非 JSON 格式,按普通文本处理: {}", payload); |
| | | // sendText(session, "服务器时间: " + LocalDateTime.now() + "->" + payload); |
| | | // } |
| | | // } |
| | | // |
| | | // // ==================== 业务处理 ==================== |
| | | // |
| | | // /** |
| | | // * 处理用户认证消息 |
| | | // */ |
| | | // private void handleUserAuth(WebSocketSession session, JSONObject messageObj) { |
| | | // try { |
| | | // WebSocketAuthHandler.UserAuthInfo authInfo = authHandler.extractAuthInfo(messageObj); |
| | | // String appUserId = authInfo.getAppUserId(); |
| | | // String token = authInfo.getToken(); |
| | | // |
| | | // if (!StringUtils.hasText(appUserId)) { |
| | | // sendAuthFailure(session, "用户ID不能为空"); |
| | | // return; |
| | | // } |
| | | // |
| | | // WebSocketAuthHandler.AuthResult authResult = authHandler.authenticateUser(appUserId, token); |
| | | // if (authResult.isSuccess()) { |
| | | // userConnectionManager.addUserConnection(appUserId, session); |
| | | // |
| | | // // TODO |
| | | // String uuid = UUID.randomUUID().toString(); |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_success"); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("nickName", uuid); |
| | | // response.put("message", "认证成功"); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // sendText(session, response.toJSONString()); |
| | | // log.info("用户 {} ({}) 认证成功,sessionId: {}", appUserId, uuid, session.getId()); |
| | | // } else { |
| | | // sendAuthFailure(session, authResult.getMessage()); |
| | | // log.warn("用户 {} 认证失败: {}, sessionId: {}", appUserId, authResult.getMessage(), session.getId()); |
| | | // } |
| | | // } catch (Exception e) { |
| | | // log.error("处理用户认证时发生错误", e); |
| | | // sendAuthFailure(session, "认证过程中发生错误: " + e.getMessage()); |
| | | // } |
| | | // } |
| | | // |
| | | // private void sendAuthFailure(WebSocketSession session, String message) { |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_failed"); |
| | | // response.put("message", message); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // sendText(session, response.toJSONString()); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理心跳消息 |
| | | // */ |
| | | // private void handlePing(WebSocketSession session) { |
| | | // String appUserId = userConnectionManager.getUserIdBySessionId(session.getId()); |
| | | // |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "pong"); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // log.info("心跳用户id:{}", appUserId); |
| | | // |
| | | // sendText(session, response.toJSONString()); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理业务消息 |
| | | // */ |
| | | // private void handleBusinessMessage(WebSocketSession session, JSONObject messageObj) { |
| | | // String appUserId = userConnectionManager.getUserIdBySessionId(session.getId()); |
| | | // |
| | | // if (appUserId != null) { |
| | | // log.info("收到用户 {} 的业务消息: {}", appUserId, messageObj.toJSONString()); |
| | | // |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "message_response"); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("originalMessage", messageObj); |
| | | // response.put("serverTime", LocalDateTime.now().toString()); |
| | | // |
| | | // sendText(session, response.toJSONString()); |
| | | // } else { |
| | | // log.warn("收到未认证连接的业务消息: {}", messageObj.toJSONString()); |
| | | // |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_required"); |
| | | // response.put("message", "请先进行用户认证"); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // sendText(session, response.toJSONString()); |
| | | // } |
| | | // } |
| | | // |
| | | // // ==================== 工具方法 ==================== |
| | | // |
| | | // /** |
| | | // * 发送文本消息(线程安全) |
| | | // */ |
| | | // private void sendText(WebSocketSession session, String text) { |
| | | // try { |
| | | // if (session.isOpen()) { |
| | | // synchronized (session) { |
| | | // session.sendMessage(new TextMessage(text)); |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // log.error("发送消息失败,sessionId: {}, error: {}", session.getId(), e.getMessage()); |
| | | // } |
| | | // } |
| | | //} |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.handler; |
| | | |
| | | //import com.alibaba.fastjson.JSON; |
| | | //import com.alibaba.fastjson.JSONObject; |
| | | //import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager; |
| | | //import io.netty.channel.ChannelHandler; |
| | | //import io.netty.channel.ChannelHandlerContext; |
| | | //import io.netty.channel.SimpleChannelInboundHandler; |
| | | //import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
| | | //import lombok.extern.slf4j.Slf4j; |
| | | //import org.springframework.beans.factory.annotation.Autowired; |
| | | //import org.springframework.stereotype.Component; |
| | | //import org.springframework.util.StringUtils; |
| | | // |
| | | //import java.time.LocalDateTime; |
| | | //import java.util.Objects; |
| | | //import java.util.UUID; |
| | | // |
| | | ///** |
| | | // * TextWebSocketFrame 表示一个文本桢 |
| | | // * 使用@Sharable注解使其可以被多个连接共享 |
| | | // * 支持基于AppUser的连接管理 |
| | | // */ |
| | | //@Component |
| | | //@Slf4j |
| | | //@ChannelHandler.Sharable |
| | | //public class WebSocketTextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { |
| | | // |
| | | // |
| | | // @Autowired |
| | | // private WebSocketUserConnectionManager userConnectionManager; |
| | | // |
| | | // @Autowired |
| | | // private WebSocketAuthHandler authHandler; |
| | | // |
| | | // @Override |
| | | // protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { |
| | | // String message = msg.text(); |
| | | // log.info("[服务器] : 收到消息 -> {}", message); |
| | | // |
| | | // try { |
| | | // // 尝试解析消息,检查是否包含用户认证信息 |
| | | // JSONObject messageObj = JSON.parseObject(message); |
| | | // String messageType = messageObj.getString("type"); |
| | | // |
| | | // if ("auth".equals(messageType)) { |
| | | // // 处理用户认证消息 |
| | | // handleUserAuth(ctx, messageObj); |
| | | // } else if ("ping".equals(messageType)) { |
| | | // // 处理心跳消息 |
| | | // handlePing(ctx, messageObj); |
| | | // } else { |
| | | // // 处理普通业务消息 |
| | | // handleBusinessMessage(ctx, messageObj); |
| | | // } |
| | | // } catch (Exception e) { |
| | | // // 如果不是JSON格式,按普通文本处理 |
| | | // log.debug("消息不是JSON格式,按普通文本处理: {}", message); |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间: " + LocalDateTime.now() + "->" + message)); |
| | | // } |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理用户认证消息 |
| | | // */ |
| | | // private void handleUserAuth(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | // try { |
| | | // // 提取认证信息 |
| | | // WebSocketAuthHandler.UserAuthInfo authInfo = authHandler.extractAuthInfo(messageObj); |
| | | // String appUserId = authInfo.getAppUserId(); |
| | | // String token = authInfo.getToken(); |
| | | // |
| | | // if (!StringUtils.hasText(appUserId)) { |
| | | // sendAuthFailureResponse(ctx, "用户ID不能为空"); |
| | | // return; |
| | | // } |
| | | // |
| | | // // 进行用户认证 |
| | | // WebSocketAuthHandler.AuthResult authResult = authHandler.authenticateUser(appUserId, token); |
| | | // if (authResult.isSuccess()) { |
| | | // // 认证成功,将连接与用户关联 |
| | | // userConnectionManager.addUserConnection(appUserId, ctx.channel()); |
| | | // |
| | | // // 发送认证成功响应 |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_success"); |
| | | // response.put("appUserId", appUserId); |
| | | // // TODO |
| | | // String uuid = UUID.randomUUID().toString(); |
| | | // response.put("nickName", uuid); |
| | | //// response.put("nickName", authResult.getAppUser().getNickName()); |
| | | // response.put("message", "认证成功"); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // log.info("用户 {} ({}) 认证成功,ChannelId: {}", |
| | | // appUserId, uuid, ctx.channel().id().asLongText()); |
| | | // } else { |
| | | // // 认证失败 |
| | | // sendAuthFailureResponse(ctx, authResult.getMessage()); |
| | | // log.warn("用户 {} 认证失败: {}, ChannelId: {}", |
| | | // appUserId, authResult.getMessage(), ctx.channel().id().asLongText()); |
| | | // } |
| | | // } catch (Exception e) { |
| | | // log.error("处理用户认证时发生错误", e); |
| | | // sendAuthFailureResponse(ctx, "认证过程中发生错误: " + e.getMessage()); |
| | | // } |
| | | // } |
| | | // |
| | | // /** |
| | | // * 发送认证失败响应 |
| | | // */ |
| | | // private void sendAuthFailureResponse(ChannelHandlerContext ctx, String message) { |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_failed"); |
| | | // response.put("message", message); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理心跳消息 |
| | | // */ |
| | | // private void handlePing(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | // String appUserId = userConnectionManager.getUserIdByChannelId(ctx.channel().id().asLongText()); |
| | | // |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "pong"); |
| | | // log.info("心跳用户id:{}",appUserId); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理业务消息 |
| | | // */ |
| | | // private void handleBusinessMessage(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | // String appUserId = userConnectionManager.getUserIdByChannelId(ctx.channel().id().asLongText()); |
| | | // |
| | | // if (appUserId != null) { |
| | | // log.info("收到用户 {} 的业务消息: {}", appUserId, messageObj.toJSONString()); |
| | | // |
| | | // // 回复消息 |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "message_response"); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("originalMessage", messageObj); |
| | | // response.put("serverTime", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // } else { |
| | | // log.warn("收到未认证连接的业务消息: {}", messageObj.toJSONString()); |
| | | // |
| | | // // 要求先认证 |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_required"); |
| | | // response.put("message", "请先进行用户认证"); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // } |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
| | | // // id 表示唯一的值 LongText是唯一的 |
| | | // log.info("handlerAdded 被调用: {}", ctx.channel().id().asLongText()); |
| | | // // shortText 可能会重复 |
| | | // log.info("handlerAdded 被调用: {}", ctx.channel().id().asShortText()); |
| | | // |
| | | // // 注意:用户连接管理在认证时添加,这里不添加 |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { |
| | | // // id 表示唯一的值 LongText是唯一的 |
| | | // log.info("handlerRemoved 被调用: {}", ctx.channel().id().asLongText()); |
| | | // // shortText 可能会重复 |
| | | // log.info("handlerRemoved 被调用: {}", ctx.channel().id().asShortText()); |
| | | // |
| | | // // 从用户连接管理器中移除连接 |
| | | // if (userConnectionManager != null) { |
| | | // userConnectionManager.removeUserConnection(ctx.channel()); |
| | | // } |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
| | | // log.error("WebSocket连接异常: {}", cause.getMessage(), cause); |
| | | // ctx.channel().close(); |
| | | // } |
| | | //} |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager; |
| | | import io.netty.channel.ChannelHandler; |
| | | import io.netty.channel.ChannelHandlerContext; |
| | | import io.netty.channel.SimpleChannelInboundHandler; |
| | | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Objects; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * TextWebSocketFrame 表示一个文本桢 |
| | | * 使用@Sharable注解使其可以被多个连接共享 |
| | | * 支持基于AppUser的连接管理 |
| | | */ |
| | | @Component |
| | | @Slf4j |
| | | @ChannelHandler.Sharable |
| | | public class WebSocketTextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { |
| | | |
| | | |
| | | @Autowired |
| | | private WebSocketUserConnectionManager userConnectionManager; |
| | | |
| | | @Autowired |
| | | private WebSocketAuthHandler authHandler; |
| | | |
| | | @Override |
| | | protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { |
| | | String message = msg.text(); |
| | | log.info("[服务器] : 收到消息 -> {}", message); |
| | | |
| | | try { |
| | | // 尝试解析消息,检查是否包含用户认证信息 |
| | | JSONObject messageObj = JSON.parseObject(message); |
| | | String messageType = messageObj.getString("type"); |
| | | |
| | | if ("auth".equals(messageType)) { |
| | | // 处理用户认证消息 |
| | | handleUserAuth(ctx, messageObj); |
| | | } else if ("ping".equals(messageType)) { |
| | | // 处理心跳消息 |
| | | handlePing(ctx, messageObj); |
| | | } else { |
| | | // 处理普通业务消息 |
| | | handleBusinessMessage(ctx, messageObj); |
| | | } |
| | | } catch (Exception e) { |
| | | // 如果不是JSON格式,按普通文本处理 |
| | | log.debug("消息不是JSON格式,按普通文本处理: {}", message); |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间: " + LocalDateTime.now() + "->" + message)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 处理用户认证消息 |
| | | */ |
| | | private void handleUserAuth(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | try { |
| | | // 提取认证信息 |
| | | WebSocketAuthHandler.UserAuthInfo authInfo = authHandler.extractAuthInfo(messageObj); |
| | | String appUserId = authInfo.getAppUserId(); |
| | | String token = authInfo.getToken(); |
| | | |
| | | if (!StringUtils.hasText(appUserId)) { |
| | | sendAuthFailureResponse(ctx, "用户ID不能为空"); |
| | | return; |
| | | } |
| | | |
| | | // 进行用户认证 |
| | | WebSocketAuthHandler.AuthResult authResult = authHandler.authenticateUser(appUserId, token); |
| | | if (authResult.isSuccess()) { |
| | | // 认证成功,将连接与用户关联 |
| | | userConnectionManager.addUserConnection(appUserId, ctx.channel()); |
| | | |
| | | // 发送认证成功响应 |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_success"); |
| | | response.put("appUserId", appUserId); |
| | | // TODO |
| | | String uuid = UUID.randomUUID().toString(); |
| | | response.put("nickName", uuid); |
| | | // response.put("nickName", authResult.getAppUser().getNickName()); |
| | | response.put("message", "认证成功"); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | log.info("用户 {} ({}) 认证成功,ChannelId: {}", |
| | | appUserId, uuid, ctx.channel().id().asLongText()); |
| | | } else { |
| | | // 认证失败 |
| | | sendAuthFailureResponse(ctx, authResult.getMessage()); |
| | | log.warn("用户 {} 认证失败: {}, ChannelId: {}", |
| | | appUserId, authResult.getMessage(), ctx.channel().id().asLongText()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理用户认证时发生错误", e); |
| | | sendAuthFailureResponse(ctx, "认证过程中发生错误: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 发送认证失败响应 |
| | | */ |
| | | private void sendAuthFailureResponse(ChannelHandlerContext ctx, String message) { |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_failed"); |
| | | response.put("message", message); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | } |
| | | |
| | | /** |
| | | * 处理心跳消息 |
| | | */ |
| | | private void handlePing(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | String appUserId = userConnectionManager.getUserIdByChannelId(ctx.channel().id().asLongText()); |
| | | |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "pong"); |
| | | log.info("心跳用户id:{}",appUserId); |
| | | response.put("appUserId", appUserId); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | } |
| | | |
| | | /** |
| | | * 处理业务消息 |
| | | */ |
| | | private void handleBusinessMessage(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | String appUserId = userConnectionManager.getUserIdByChannelId(ctx.channel().id().asLongText()); |
| | | |
| | | if (appUserId != null) { |
| | | log.info("收到用户 {} 的业务消息: {}", appUserId, messageObj.toJSONString()); |
| | | |
| | | // 回复消息 |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "message_response"); |
| | | response.put("appUserId", appUserId); |
| | | response.put("originalMessage", messageObj); |
| | | response.put("serverTime", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | } else { |
| | | log.warn("收到未认证连接的业务消息: {}", messageObj.toJSONString()); |
| | | |
| | | // 要求先认证 |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_required"); |
| | | response.put("message", "请先进行用户认证"); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
| | | // id 表示唯一的值 LongText是唯一的 |
| | | log.info("handlerAdded 被调用: {}", ctx.channel().id().asLongText()); |
| | | // shortText 可能会重复 |
| | | log.info("handlerAdded 被调用: {}", ctx.channel().id().asShortText()); |
| | | |
| | | // 注意:用户连接管理在认证时添加,这里不添加 |
| | | } |
| | | |
| | | @Override |
| | | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { |
| | | // id 表示唯一的值 LongText是唯一的 |
| | | log.info("handlerRemoved 被调用: {}", ctx.channel().id().asLongText()); |
| | | // shortText 可能会重复 |
| | | log.info("handlerRemoved 被调用: {}", ctx.channel().id().asShortText()); |
| | | |
| | | // 从用户连接管理器中移除连接 |
| | | if (userConnectionManager != null) { |
| | | userConnectionManager.removeUserConnection(ctx.channel()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
| | | log.error("WebSocket连接异常: {}", cause.getMessage(), cause); |
| | | ctx.channel().close(); |
| | | } |
| | | } |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.service; |
| | | |
| | | import io.netty.channel.Channel; |
| | | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.web.socket.TextMessage; |
| | | import org.springframework.web.socket.WebSocketSession; |
| | | |
| | | import java.util.*; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * 基于 AppUser 的 WebSocket 连接管理器 |
| | | * 使用 Spring WebSocketSession(共用 Spring Boot HTTP 端口) |
| | | * 支持按用户 ID 管理连接,一个用户可以有多个设备连接 |
| | | * 基于AppUser的WebSocket连接管理器 |
| | | * 支持按用户ID管理连接,一个用户可以有多个设备连接 |
| | | */ |
| | | @Component |
| | | @Slf4j |
| | | public class WebSocketUserConnectionManager { |
| | | |
| | | /** |
| | | * 存储用户的所有连接 |
| | | * Key: 用户ID (appUserId) |
| | | * Value: 该用户的所有 WebSocketSession 集合 |
| | | * Value: 该用户的所有连接Channel列表 |
| | | */ |
| | | private static final ConcurrentHashMap<String, Set<WebSocketSession>> userConnections = new ConcurrentHashMap<>(); |
| | | private static final ConcurrentHashMap<String, Set<Channel>> userConnections = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * Key: session.getId() |
| | | * 存储连接与用户的映射关系 |
| | | * Key: ChannelId.asLongText() |
| | | * Value: 用户ID (appUserId) |
| | | */ |
| | | private static final ConcurrentHashMap<String, String> sessionToUser = new ConcurrentHashMap<>(); |
| | | private static final ConcurrentHashMap<String, String> channelToUser = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * Key: session.getId() |
| | | * Value: WebSocketSession |
| | | * 存储所有连接 |
| | | * Key: ChannelId.asLongText() |
| | | * Value: Channel |
| | | */ |
| | | private static final ConcurrentHashMap<String, WebSocketSession> allConnections = new ConcurrentHashMap<>(); |
| | | |
| | | // ==================== 连接管理 ==================== |
| | | private static final ConcurrentHashMap<String, Channel> allConnections = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * 添加用户连接 |
| | | * @param appUserId 用户ID |
| | | * @param channel WebSocket连接通道 |
| | | */ |
| | | public void addUserConnection(String appUserId, WebSocketSession session) { |
| | | String sessionId = session.getId(); |
| | | public void addUserConnection(String appUserId, Channel channel) { |
| | | String channelId = channel.id().asLongText(); |
| | | |
| | | userConnections.computeIfAbsent(appUserId, k -> ConcurrentHashMap.newKeySet()).add(session); |
| | | sessionToUser.put(sessionId, appUserId); |
| | | allConnections.put(sessionId, session); |
| | | // 添加到用户连接映射 |
| | | userConnections.computeIfAbsent(appUserId, k -> ConcurrentHashMap.newKeySet()) |
| | | .add(channel); |
| | | |
| | | log.info("用户连接已添加,用户ID: {}, sessionId: {}, 该用户连接数: {}", |
| | | appUserId, sessionId, userConnections.get(appUserId).size()); |
| | | // 添加到连接用户映射 |
| | | channelToUser.put(channelId, appUserId); |
| | | |
| | | // 添加到所有连接 |
| | | allConnections.put(channelId, channel); |
| | | |
| | | log.info("用户连接已添加,用户ID: {}, ChannelId: {}, 该用户连接数: {}", |
| | | appUserId, channelId, userConnections.get(appUserId).size()); |
| | | } |
| | | |
| | | /** |
| | | * 移除用户连接 |
| | | * @param channel WebSocket连接通道 |
| | | */ |
| | | public void removeUserConnection(WebSocketSession session) { |
| | | String sessionId = session.getId(); |
| | | String appUserId = sessionToUser.get(sessionId); |
| | | public void removeUserConnection(Channel channel) { |
| | | String channelId = channel.id().asLongText(); |
| | | String appUserId = channelToUser.get(channelId); |
| | | |
| | | if (appUserId != null) { |
| | | Set<WebSocketSession> userSessions = userConnections.get(appUserId); |
| | | if (userSessions != null) { |
| | | userSessions.remove(session); |
| | | if (userSessions.isEmpty()) { |
| | | // 从用户连接中移除 |
| | | Set<Channel> userChannels = userConnections.get(appUserId); |
| | | if (userChannels != null) { |
| | | userChannels.remove(channel); |
| | | // 如果用户没有连接了,删除用户映射 |
| | | if (userChannels.isEmpty()) { |
| | | userConnections.remove(appUserId); |
| | | } |
| | | } |
| | | sessionToUser.remove(sessionId); |
| | | log.info("用户连接已移除,用户ID: {}, sessionId: {}, 该用户剩余连接数: {}", |
| | | appUserId, sessionId, userSessions != null ? userSessions.size() : 0); |
| | | |
| | | // 从连接用户映射中移除 |
| | | channelToUser.remove(channelId); |
| | | |
| | | log.info("用户连接已移除,用户ID: {}, ChannelId: {}, 该用户剩余连接数: {}", |
| | | appUserId, channelId, userChannels != null ? userChannels.size() : 0); |
| | | } |
| | | |
| | | allConnections.remove(sessionId); |
| | | // 从所有连接中移除 |
| | | allConnections.remove(channelId); |
| | | } |
| | | |
| | | // ==================== 消息发送 ==================== |
| | | |
| | | /** |
| | | * 向指定用户的所有设备发送消息 |
| | | * @param appUserId 用户ID |
| | | * @param message 消息内容 |
| | | * @return 成功发送的设备数 |
| | | */ |
| | | public int sendMessageToUser(String appUserId, String message) { |
| | | Set<WebSocketSession> sessions = userConnections.get(appUserId); |
| | | if (sessions == null || sessions.isEmpty()) { |
| | | Set<Channel> userChannels = userConnections.get(appUserId); |
| | | if (userChannels == null || userChannels.isEmpty()) { |
| | | log.warn("用户 {} 没有活跃连接", appUserId); |
| | | return 0; |
| | | } |
| | | |
| | | int successCount = 0; |
| | | for (WebSocketSession session : sessions) { |
| | | if (session != null && session.isOpen()) { |
| | | for (Channel channel : userChannels) { |
| | | if (channel != null && channel.isActive()) { |
| | | try { |
| | | synchronized (session) { |
| | | session.sendMessage(new TextMessage(message)); |
| | | } |
| | | channel.writeAndFlush(new TextWebSocketFrame(message)); |
| | | successCount++; |
| | | } catch (Exception e) { |
| | | log.error("向用户 {} 发送消息失败,sessionId: {}, 错误: {}", |
| | | appUserId, session.getId(), e.getMessage()); |
| | | log.error("向用户 {} 发送消息失败,连接ID: {}, 错误: {}", |
| | | appUserId, channel.id().asLongText(), e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | log.info("向用户 {} 发送消息完成,成功发送到 {} 个设备", appUserId, successCount); |
| | | log.info("向用户 {} 发送消息完成,成功发送到 {} 个设备,消息内容: {}", |
| | | appUserId, successCount, message); |
| | | return successCount; |
| | | } |
| | | |
| | | /** |
| | | * 向指定 session 发送消息 |
| | | * 向指定连接发送消息 |
| | | * @param channelId 连接ID |
| | | * @param message 消息内容 |
| | | * @return 是否发送成功 |
| | | */ |
| | | public boolean sendMessageToChannel(String sessionId, String message) { |
| | | WebSocketSession session = allConnections.get(sessionId); |
| | | if (session != null && session.isOpen()) { |
| | | public boolean sendMessageToChannel(String channelId, String message) { |
| | | Channel channel = allConnections.get(channelId); |
| | | if (channel != null && channel.isActive()) { |
| | | try { |
| | | synchronized (session) { |
| | | session.sendMessage(new TextMessage(message)); |
| | | } |
| | | log.info("消息已发送到连接: {}", sessionId); |
| | | channel.writeAndFlush(new TextWebSocketFrame(message)); |
| | | log.info("消息已发送到连接: {}, 内容: {}", channelId, message); |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("发送消息失败,sessionId: {}, 错误: {}", sessionId, e.getMessage()); |
| | | log.error("发送消息失败,连接ID: {}, 错误: {}", channelId, e.getMessage()); |
| | | return false; |
| | | } |
| | | } else { |
| | | log.warn("连接不存在或已关闭,sessionId: {}", sessionId); |
| | | log.warn("连接不存在或已关闭,ChannelId: {}", channelId); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 向所有连接广播消息 |
| | | * @param message 消息内容 |
| | | * @return 成功发送的连接数 |
| | | */ |
| | | public int broadcastMessage(String message) { |
| | | int successCount = 0; |
| | | for (WebSocketSession session : allConnections.values()) { |
| | | if (session != null && session.isOpen()) { |
| | | for (Channel channel : allConnections.values()) { |
| | | if (channel != null && channel.isActive()) { |
| | | try { |
| | | synchronized (session) { |
| | | session.sendMessage(new TextMessage(message)); |
| | | } |
| | | channel.writeAndFlush(new TextWebSocketFrame(message)); |
| | | successCount++; |
| | | } catch (Exception e) { |
| | | log.error("广播消息失败,sessionId: {}, 错误: {}", session.getId(), e.getMessage()); |
| | | log.error("广播消息失败,连接ID: {}, 错误: {}", |
| | | channel.id().asLongText(), e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | log.info("广播消息完成,成功发送到 {} 个连接", successCount); |
| | | log.info("广播消息完成,成功发送到 {} 个连接,消息内容: {}", successCount, message); |
| | | return successCount; |
| | | } |
| | | |
| | | /** |
| | | * 向多个用户发送消息 |
| | | * @param appUserIds 用户ID列表 |
| | | * @param message 消息内容 |
| | | * @return 成功发送的用户数 |
| | | */ |
| | | public int sendMessageToUsers(List<String> appUserIds, String message) { |
| | | int successUserCount = 0; |
| | |
| | | return successUserCount; |
| | | } |
| | | |
| | | // ==================== 查询 ==================== |
| | | |
| | | public Set<WebSocketSession> getUserConnections(String appUserId) { |
| | | /** |
| | | * 获取用户的所有连接 |
| | | * @param appUserId 用户ID |
| | | * @return 连接列表 |
| | | */ |
| | | public Set<Channel> getUserConnections(String appUserId) { |
| | | return userConnections.getOrDefault(appUserId, Collections.emptySet()); |
| | | } |
| | | |
| | | /** |
| | | * 获取用户连接数 |
| | | * @param appUserId 用户ID |
| | | * @return 连接数 |
| | | */ |
| | | public int getUserConnectionCount(String appUserId) { |
| | | Set<WebSocketSession> sessions = userConnections.get(appUserId); |
| | | return sessions != null ? sessions.size() : 0; |
| | | Set<Channel> userChannels = userConnections.get(appUserId); |
| | | return userChannels != null ? userChannels.size() : 0; |
| | | } |
| | | |
| | | /** |
| | | * 获取总连接数 |
| | | * @return 总连接数 |
| | | */ |
| | | public int getTotalConnectionCount() { |
| | | return allConnections.size(); |
| | | } |
| | | |
| | | /** |
| | | * 获取在线用户数 |
| | | * @return 在线用户数 |
| | | */ |
| | | public int getOnlineUserCount() { |
| | | return userConnections.size(); |
| | | } |
| | | |
| | | /** |
| | | * 获取所有在线用户ID |
| | | * @return 用户ID集合 |
| | | */ |
| | | public Set<String> getOnlineUserIds() { |
| | | return userConnections.keySet(); |
| | | } |
| | | |
| | | /** |
| | | * 检查用户是否在线 |
| | | * @param appUserId 用户ID |
| | | * @return 是否在线 |
| | | */ |
| | | public boolean isUserOnline(String appUserId) { |
| | | Set<WebSocketSession> sessions = userConnections.get(appUserId); |
| | | if (sessions == null || sessions.isEmpty()) { |
| | | Set<Channel> userChannels = userConnections.get(appUserId); |
| | | if (userChannels == null || userChannels.isEmpty()) { |
| | | return false; |
| | | } |
| | | return sessions.stream().anyMatch(WebSocketSession::isOpen); |
| | | } |
| | | |
| | | public boolean isConnectionExists(String sessionId) { |
| | | WebSocketSession session = allConnections.get(sessionId); |
| | | return session != null && session.isOpen(); |
| | | // 检查是否有活跃连接 |
| | | return userChannels.stream().anyMatch(Channel::isActive); |
| | | } |
| | | |
| | | /** |
| | | * 根据 sessionId 获取用户ID |
| | | * 检查连接是否存在 |
| | | * @param channelId 连接ID |
| | | * @return 是否存在 |
| | | */ |
| | | public String getUserIdBySessionId(String sessionId) { |
| | | return sessionToUser.get(sessionId); |
| | | public boolean isConnectionExists(String channelId) { |
| | | Channel channel = allConnections.get(channelId); |
| | | return channel != null && channel.isActive(); |
| | | } |
| | | |
| | | /** |
| | | * 根据连接ID获取用户ID |
| | | * @param channelId 连接ID |
| | | * @return 用户ID |
| | | */ |
| | | public String getUserIdByChannelId(String channelId) { |
| | | return channelToUser.get(channelId); |
| | | } |
| | | |
| | | /** |
| | | * 获取连接统计信息 |
| | | * @return 统计信息 |
| | | */ |
| | | public Map<String, Object> getConnectionStats() { |
| | | Map<String, Object> stats = new HashMap<>(); |
| | | stats.put("totalConnections", getTotalConnectionCount()); |
| | | stats.put("onlineUsers", getOnlineUserCount()); |
| | | stats.put("onlineUserIds", getOnlineUserIds()); |
| | | |
| | | // 统计每个用户的连接数 |
| | | Map<String, Integer> userConnectionCounts = new HashMap<>(); |
| | | for (Map.Entry<String, Set<WebSocketSession>> entry : userConnections.entrySet()) { |
| | | for (Map.Entry<String, Set<Channel>> entry : userConnections.entrySet()) { |
| | | userConnectionCounts.put(entry.getKey(), entry.getValue().size()); |
| | | } |
| | | stats.put("userConnectionCounts", userConnectionCounts); |
| | |
| | | server: |
| | | port: 6090 |
| | | tomcat: |
| | | basedir: data/tomcat |
| | | basedir: /data/tomcat |
| | | # Spring |
| | | spring: |
| | | application: |
| | |
| | | allow-circular-references: true |
| | | profiles: |
| | | # 环境配置 |
| | | active: local |
| | | active: dev |
| | | # active: prod |
| | | --- |
| | | spring: |
| | | config: |
| | | activate: |
| | | on-profile: dev |
| | | cloud: |
| | | nacos: |
| | | discovery: |
| | | # 服务注册地址 |
| | | server-addr: 192.168.110.80:8848 |
| | | service: ${spring.application.name} |
| | | group: DEFAULT_GROUP |
| | | namespace: 5185ac4b-6369-48eb-a21b-a7cbc441c5f1 |
| | | username: nacos |
| | | password: nacos |
| | | config: |
| | | # 配置中心地址 |
| | | server-addr: 192.168.110.80:8848 |
| | | namespace: 5185ac4b-6369-48eb-a21b-a7cbc441c5f1 |
| | | group: DEFAULT_GROUP |
| | | name: ${spring.application.name} |
| | | username: nacos |
| | | password: nacos |
| | | # 配置文件格式 |
| | | file-extension: yml |
| | | # 共享配置 |
| | | shared-configs: |
| | | - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} |
| | | --- |
| | | seata: |
| | | enabled: true |
| | | application-id: ${spring.application.name} |
| | | tx-service-group: seata_tx_group #此处配置自定义的seata事务分组名称 |
| | | enable-auto-data-source-proxy: false #关闭数据库代理 |
| | | service: |
| | | vgroup-mapping: |
| | | seata_tx_group: default |
| | | data-source-proxy-mode: AT |
| | | config: |
| | | type: nacos |
| | | nacos: |
| | | # 开发环境 |
| | | server-addr: 192.168.110.80:8848 |
| | | namespace: 5185ac4b-6369-48eb-a21b-a7cbc441c5f1 |
| | | group: DEFAULT_GROUP |
| | | data-id: seata-server.properties |
| | | username: nacos |
| | | password: nacos |
| | | # 生产环境 |
| | | # server-addr: 192.168.0.137:8848,192.168.0.123:8848 |
| | | # namespace: c6cf40b5-44e8-43f9-be61-8d10fa830e2a |
| | | # group: DEFAULT_GROUP |
| | | # data-id: seata-server.properties |
| | | # username: nacos |
| | | # password: nacos |
| | | registry: |
| | | type: nacos |
| | | nacos: |
| | | application: seata-server |
| | | # 开发环境 |
| | | server-addr: 192.168.110.80:8848 |
| | | namespace: 5185ac4b-6369-48eb-a21b-a7cbc441c5f1 |
| | | group: DEFAULT_GROUP |
| | | username: nacos |
| | | password: nacos |
| | | # 生产环境 |
| | | # server-addr: 192.168.0.137:8848,192.168.0.123:8848 |
| | | # namespace: c6cf40b5-44e8-43f9-be61-8d10fa830e2a |
| | | # group: DEFAULT_GROUP |
| | | # username: nacos |
| | | # password: nacos |
| | | cloud: |
| | | nacos: |
| | | discovery: |
| | | # 开发环境 |
| | | server-addr: 192.168.110.80:8848 |
| | | namespace: 5185ac4b-6369-48eb-a21b-a7cbc441c5f1 |
| | | group: DEFAULT_GROUP |
| | | application: seata-server #Nacos 中 Seata 名称 |
| | | username: nacos |
| | | password: nacos |
| | | # 生产环境 |
| | | # server-addr: 192.168.0.137:8848,192.168.0.123:8848 |
| | | # namespace: c6cf40b5-44e8-43f9-be61-8d10fa830e2a |
| | | # group: DEFAULT_GROUP |
| | | # application: seata-server #Nacos 中 Seata 名称 |
| | | # username: nacos |
| | | # password: nacos |
| | | sentinel: |
| | | transport: |
| | | dashboard: 192.168.0.137:8080 # Sentinel控制台地址 |
| | | eager: true |
| | | --- |
| | | spring: |
| | | config: |
| | | activate: |
| | | on-profile: prod |
| | | cloud: |
| | | nacos: |
| | | discovery: |
| | | # 服务注册地址 |
| | | server-addr: 192.168.0.137:8848,192.168.0.123:8848 |
| | | service: ${spring.application.name} |
| | | group: DEFAULT_GROUP |
| | | namespace: c6cf40b5-44e8-43f9-be61-8d10fa830e2a |
| | | username: nacos |
| | | password: nacos |
| | | config: |
| | | # 配置中心地址 |
| | | server-addr: 192.168.0.137:8848,192.168.0.123:8848 |
| | | namespace: c6cf40b5-44e8-43f9-be61-8d10fa830e2a |
| | | username: nacos |
| | | password: nacos |
| | | group: DEFAULT_GROUP |
| | | name: ${spring.application.name} |
| | | # 配置文件格式 |
| | | file-extension: yml |
| | | # 共享配置 |
| | | shared-configs: |
| | | - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} |
| | | --- |
| | | spring: |
| | | config: |
| | | activate: |
| | | on-profile: test |
| | | cloud: |
| | | nacos: |
| | | discovery: |
| | | # 服务注册地址 |
| | | server-addr: 127.0.0.1:8848 |
| | | service: ${spring.application.name} |
| | | group: DEFAULT_GROUP |
| | | namespace: 96712c7a-480b-4f40-b783-39f00f3b33ce |
| | | config: |
| | | # 配置中心地址 |
| | | server-addr: 127.0.0.1:8848 |
| | | namespace: 96712c7a-480b-4f40-b783-39f00f3b33ce |
| | | group: DEFAULT_GROUP |
| | | name: ${spring.application.name} |
| | | # 配置文件格式 |
| | | file-extension: yml |
| | | # 共享配置 |
| | | shared-configs: |
| | | - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * |
| | | * 根据打卡地址id查询被车辆使用次数 |
| | | * @param locationId |
| | | * @return |
| | | */ |
| | | @GetMapping("/inner/fleet/getUseCountByLocationId") |
| | | public R<Long> getUseCountByLocationId(@RequestParam Long locationId){ |
| | | |
| | | Long count = xpFleetService.count(Wrappers.lambdaQuery(XpFleet.class) |
| | | .eq(XpFleet::getLocationId, locationId)); |
| | | |
| | | return R.ok(count); |
| | | } |
| | | |
| | | /** |
| | | * 添加车队 |
| | | * |
| | | * @param dto 添加车队DTO |
| | |
| | | import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel; |
| | | import com.xiaopeng.vehicleConvoy.model.query.*; |
| | | import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO; |
| | | import com.xiaopeng.vehicleConvoy.service.IXpFleetService; |
| | | import com.xiaopeng.vehicleConvoy.service.IXpFleetVehicleService; |
| | | import com.xiaopeng.vehicleConvoy.service.IXpVehicleAnnualRecordService; |
| | | import com.xiaopeng.vehicleConvoy.service.IXpVehicleService; |