(二十)springboot实战——springboot使用redis的订阅发布机制结合SSE实现站内信的功能

前言

在前面的章节内容中,我们介绍了如何使用springboot项目实现基于redis订阅发布机制实现消息的收发,同时也介绍了基于SSE机制的单通道消息推送案例,本节内容结合redis和sse实现一个常用的实战案例——站内信。实现系统消息的实时推送。

正文

①引入项目的pom依赖,并在application.yml中配置redis连接

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>

 ②创建一个SSE服务器,用于连接用户和收发消息

package com.yundi.atp.server;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class SseServer {
    /**
     * 存储用户的连接
     */
    public static Map<String, SseEmitterUTF8> sseMap = new HashMap<>();

    /**
     * 建立连接
     *
     * @param username
     * @throws IOException
     */
    public static SseEmitterUTF8 connect(String username) throws IOException {
        if (!sseMap.containsKey(username)) {
            //设置超时时间(和token有效期一致,超时后不再推送消息),0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException
            SseEmitterUTF8 sseEmitter = new SseEmitterUTF8(0L);
            sseEmitter.send(String.format("%s号用户,连接成功!", username));
            sseEmitter.onCompletion(() -> sseMap.remove(username));
            sseEmitter.onTimeout(() -> sseMap.remove(username));
            sseEmitter.onError(throwable -> sseMap.remove(username));
            sseMap.put(username, sseEmitter);
            return sseEmitter;
        } else {
            SseEmitterUTF8 sseEmitterUTF8 = sseMap.get(username);
            sseEmitterUTF8.send(String.format("%s,用户连接成功!", username));
            return sseEmitterUTF8;
        }
    }

    /**
     * 发送消息
     *
     * @param message
     */
    public static synchronized void sendMessage(String message) {
        List<String> removeList = new ArrayList<>();
        for (Map.Entry<String, SseEmitterUTF8> entry : sseMap.entrySet()) {
            String username = entry.getKey();
            try {
                SseEmitterUTF8 sseEmitterUTF8 = entry.getValue();
                sseEmitterUTF8.onCompletion(() -> sseMap.remove(username));
                sseEmitterUTF8.onTimeout(() -> sseMap.remove(username));
                sseEmitterUTF8.onError(throwable -> sseMap.remove(username));
                sseEmitterUTF8.send(message);
            } catch (IOException e) {
                //发送不成功,将该用户加入移除列表
                removeList.add(username);
            }
        }
        //移除连接异常的用户
        removeList.forEach(item -> sseMap.remove(item));
    }
}

 ③创建一个redis消息的监听器,将监听到的消息通过sse服务推送给连接的用户

package com.yundi.atp.listen;

import com.yundi.atp.constant.ChannelConstant;
import com.yundi.atp.server.SseServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;


@Slf4j
@Component
public class RedisMessageSubscriber implements MessageListener {
    @Autowired
    private RedisMessageListenerContainer redisMessageListenerContainer;

    /**
     * 订阅消息:将订阅者添加到指定的频道
     */
    @PostConstruct
    public void subscribeToChannel() {
        //广播消息
        redisMessageListenerContainer.addMessageListener(this, new ChannelTopic(ChannelConstant.CHANNEL_GLOBAL_NAME));
    }

    @Override
    public void onMessage(Message message, byte[] bytes) {
        String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
        String messageBody = new String(message.getBody(), StandardCharsets.UTF_8);
        log.info("Received message: " + messageBody + " from channel: " + channel);
        SseServer.sendMessage(messageBody);
    }
}

 ④创建SseEmitterUTF8并继承SseEmitter,重写extendResponse方法,解决中文消息发送乱码问题

package com.yundi.atp.server;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.nio.charset.StandardCharsets;


public class SseEmitterUTF8 extends SseEmitter {

    public SseEmitterUTF8(Long timeout) {
        super(timeout);
    }

    @Override
    protected void extendResponse(ServerHttpResponse outputMessage) {
        super.extendResponse(outputMessage);
        HttpHeaders headers = outputMessage.getHeaders();
        headers.setContentType(new MediaType(MediaType.TEXT_EVENT_STREAM, StandardCharsets.UTF_8));
    }
}

⑤ 创建redis的配置类,用于初始化redis的容器监听器和工具类

package com.yundi.atp.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
public class RedisConfig {
    /**
     * 初始化一个Redis消息监听容器
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 添加其他配置,如线程池大小等
        return container;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

⑦ 创建用于站内信发送的频道Channel

package com.yundi.atp.constant;


public class ChannelConstant {
    /**
     * 广播通道
     */
    public static final String CHANNEL_GLOBAL_NAME = "channel-global";

    /**
     * 单播通道
     */
    public static final String CHANNEL_SINGLE_NAME = "channel-single";
}

 ⑧创建一个消息发布接口和一个sse用户消息推送连接接口

package com.yundi.atp.controller;

import com.yundi.atp.constant.ChannelConstant;
import com.yundi.atp.server.SseEmitterUTF8;
import com.yundi.atp.server.SseServer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.IOException;


@RequestMapping(value = "base")
@RestController
public class BaseController {
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 发布广播消息
     *
     * @param msg
     */
    @GetMapping(value = "/publish/{msg}")
    public void sendMsg(@PathVariable(value = "msg") String msg) {
        redisTemplate.convertAndSend(ChannelConstant.CHANNEL_GLOBAL_NAME, msg);
    }


    /**
     * 接收消息
     *
     * @return
     * @throws IOException
     */
    @GetMapping(path = "/connect/{username}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitterUTF8 connect(@PathVariable(value = "username") String username) throws IOException {
        SseEmitterUTF8 connect = SseServer.connect(username);
        return connect;
    }
}

 ⑨启动服务,验证站内信功能是否可以正常使用

结语

关于springboot使用redis的订阅发布机制结合SSE实现站内信的功能到这里就结束了,我们下期见。。。。。。

  • 14
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot中集成Redis实现多个发布订阅是相对简单的。下面是一个简单的步骤指南: 1. 首先,在pom.xml文件中添加RedisSpring Data Redis的依赖: ```xml <dependencies> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> ``` 2. 在application.properties文件中配置Redis连接信息: ``` spring.redis.host=your_redis_host spring.redis.port=your_redis_port ``` 3. 创建一个Redis消息监听器,用于处理接收到的消息。可以实现MessageListener接口或使用注解方式,这里使用注解方式。 ```java @Component public class RedisMessageListener { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private RedisMessagePublisher redisMessagePublisher; @EventListener public void handleMessage(Message message) { System.out.println("Received message: " + message.toString()); } @Scheduled(fixedDelay = 1000) public void publishMessage() { redisMessagePublisher.publish("Hello from Redis!"); } } ``` 4. 创建一个Redis消息发布器,用于发布消息。 ```java @Component public class RedisMessagePublisher { @Autowired private RedisTemplate<String, String> redisTemplate; public void publish(String message) { redisTemplate.convertAndSend("channel", message); } } ``` 5. 启动应用程序,Redis将会自动连接并监听消息。在上面的示例中,每秒钟会向名为"channel"的频道发布一条消息,并由监听器接收并处理。 请根据你的具体需求对代码进行适当调整。这只是一个简单的示例,你可以根据实际情况进行扩展和优化。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

厉害哥哥吖

您的支持是我创作下去的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值