java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java消息推送功能

java中消息推送功能的关键实现代码

作者:hqxstudying

在日常开发中,消息推送是非常典型的业务需求,下面这篇文章主要介绍了java中消息推送功能的关键实现代码,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

在 Java 项目中实现消息推送服务,后端核心需要解决实时性可靠性扩展性(集群支持)和连接管理四大问题。以下从技术选型、核心架构、关键实现和注意事项四个方面展开说明:

一、技术选型:根据场景选协议

消息推送的核心是服务器主动向客户端发送数据,需根据实时性、客户端类型(Web/APP/ 物联网设备)选择合适的通信协议:

协议 / 方案适用场景优势劣势Java 技术栈支持
WebSocketWeb 端实时通信(如聊天、通知)全双工、低延迟、长连接部分老旧浏览器不支持Spring WebSocket、Netty、Tomcat 原生
MQTT物联网设备(低带宽、不稳定网络)轻量、支持 QoS(消息质量等级)需额外部署 MQTT broker(如 EMQX)Eclipse Paho、Spring Integration
长轮询(Long Polling)兼容性要求高的场景(如老浏览器)实现简单、兼容性好延迟较高、服务器资源消耗大Servlet + 异步处理
Server-Sent Events (SSE)服务器单向推送(如实时日志)轻量、仅服务器向客户端推送不支持客户端向服务器发送数据Spring WebFlux、原生 Servlet

二、核心架构:后端模块设计

无论选择哪种协议,消息推送服务的后端架构通常包含以下核心模块:

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   业务系统API   │───>│   消息路由模块   │───>│   连接管理模块   │───> 客户端
└─────────────────┘    └─────────────────┘    └─────────────────┘
        │                       │                       │
        ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  离线消息存储    │<───│  集群通信模块    │<───│  会话管理模块    │
└─────────────────┘    └─────────────────┘    └─────────────────┘

三、关键实现:以 WebSocket + Spring Boot 为例

以最常用的 Web 端实时推送为例,基于 Spring Boot + WebSocket + Redis(集群支持)实现核心流程:

1. 依赖配置(Maven)

<!-- WebSocket核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Redis(集群通信与会话存储) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. WebSocket 配置(连接建立与处理器)

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Autowired
    private MessageHandler messageHandler; // 自定义消息处理器

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 配置WebSocket端点:客户端通过ws://ip:port/ws?token=xxx连接
        registry.addHandler(messageHandler, "/ws")
                .setAllowedOrigins("*") // 允许跨域(生产环境需限制域名)
                .addInterceptors(new HandshakeInterceptor() {
                    // 握手前验证用户(如Token解析用户ID)
                    @Override
                    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, 
                                                  WebSocketHandler wsHandler, Map<String, Object> attributes) {
                        String token = ((ServletServerHttpRequest) request).getServletRequest().getParameter("token");
                        String userId = parseToken(token); // 解析Token获取用户ID
                        if (userId == null) {
                            response.setStatusCode(HttpStatus.UNAUTHORIZED);
                            return false; // 验证失败,拒绝连接
                        }
                        attributes.put("userId", userId); // 存储用户ID到属性中
                        return true;
                    }

                    @Override
                    public void afterHandshake(...) {}
                });
    }
}

3. 消息处理器(连接管理与会话绑定)

@Component
public class MessageHandler extends TextWebSocketHandler {
    // 本地会话映射:用户ID -> WebSocket会话(单机用)
    private final Map<String, WebSocketSession> localSessions = new ConcurrentHashMap<>();
    @Autowired
    private StringRedisTemplate redisTemplate; // Redis操作模板
    @Autowired
    private OfflineMessageService offlineMessageService; // 离线消息服务

    // 连接建立时:绑定用户ID与Session
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        String userId = (String) session.getAttributes().get("userId");
        localSessions.put(userId, session);
        
        // 1. 拉取离线消息并推送
        List<Message> offlineMessages = offlineMessageService.getByUserId(userId);
        for (Message msg : offlineMessages) {
            sendMessage(session, msg.getContent());
        }
        offlineMessageService.deleteByUserId(userId); // 清除已推送的离线消息
        
        // 2. 集群:将用户ID与当前节点IP存入Redis(便于其他节点定位)
        redisTemplate.opsForValue().set("user:session:" + userId, getLocalIp());
    }

    // 连接关闭时:移除会话映射
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        String userId = (String) session.getAttributes().get("userId");
        localSessions.remove(userId);
        redisTemplate.delete("user:session:" + userId); // 集群:删除Redis映射
    }

    // 发送消息到指定用户(核心方法)
    public void pushToUser(String userId, String content) {
        // 1. 先检查本地是否有该用户的连接
        WebSocketSession session = localSessions.get(userId);
        if (session != null && session.isOpen()) {
            sendMessage(session, content);
            return;
        }

        // 2. 集群:检查用户是否连接在其他节点
        String targetNodeIp = redisTemplate.opsForValue().get("user:session:" + userId);
        if (targetNodeIp != null) {
            // 发布消息到Redis频道,目标节点订阅后发送
            redisTemplate.convertAndSend("push:channel", 
                JSON.toJSONString(new PushMessage(userId, content)));
            return;
        }

        // 3. 用户不在线:存入离线消息
        offlineMessageService.save(new Message(userId, content, LocalDateTime.now()));
    }

    // 实际发送消息(封装异常处理)
    private void sendMessage(WebSocketSession session, String content) {
        try {
            session.sendMessage(new TextMessage(content));
        } catch (IOException e) {
            log.error("消息发送失败", e);
        }
    }
}

4. 集群支持:Redis Pub/Sub 订阅

@Component
public class RedisMessageListener {
    @Autowired
    private MessageHandler messageHandler;

    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 订阅推送频道,接收其他节点的消息
        container.addMessageListener((message, pattern) -> {
            String json = new String(message.getBody());
            PushMessage pushMsg = JSON.parseObject(json, PushMessage.class);
            // 调用本地消息处理器发送(此时用户应在当前节点)
            messageHandler.pushToUser(pushMsg.getUserId(), pushMsg.getContent());
        }, new ChannelTopic("push:channel"));
        return container;
    }
}

5. 业务调用接口

@RestController
@RequestMapping("/api/message")
public class MessageController {
    @Autowired
    private MessageHandler messageHandler;

    // 业务系统调用此接口触发推送
    @PostMapping("/push")
    public Result push(@RequestBody PushRequest request) {
        messageHandler.pushToUser(request.getUserId(), request.getContent());
        return Result.success();
    }
}

四、注意事项

总结

后端消息推送服务的核心是 **“连接管理”+“消息路由”**,小规模场景可用 Spring WebSocket + 本地会话;中大规模集群需结合 Redis 实现分布式会话与跨节点通信;物联网场景优先选 MQTT 协议。根据实时性和规模需求,可逐步迭代优化(从单机到集群,从基础功能到可靠性保障)。

到此这篇关于java中消息推送功能的文章就介绍到这了,更多相关java消息推送功能内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文