使用redis实现聊天记录转存(下)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 使用redis实现聊天记录转存(下)

进行单元测试


做完上述操作后,最难弄的一关我们就已经搞定了,接下来我们来对一会需要使用的方法进行单元测试,确保其能够正常运行。


创建一个名为RedisTest的Java文件,注入需要用到的相关类。


  • redisOperatingUtil为我们的redis工具类
  • subMessageMapper为聊天记录表的dao层


@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class RedisTest {
    @Resource
    private RedisOperatingUtil redisOperatingUtil;
    @Resource
    private SubMessageMapper subMessageMapper;
}


接下来,我们看下SubMessage实体类的代码。


package com.lk.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
// 聊天记录-消息内容
public class SubMessage {
  private Integer id;
  private String msgText; // 消息内容
  private String createTime; // 创建时间
  private String userName; // 用户名
  private String userId; // 推送方用户id
  private String avatarSrc; // 推送方头像
  private String msgId; // 接收方用户id
  private Boolean status; // 消息状态
}


测试list数据的写入与获取


在单元测试类内部加入下述代码:


@Test
    public void testSerializableListRedisTemplate() {
        // 构造聊天记录实体类数据
        SubMessage subMessage = new SubMessage();
        subMessage.setAvatarSrc("https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg");
        subMessage.setUserId("1090192");
        subMessage.setUserName("神奇的程序员");
        subMessage.setMsgText("你好");
        subMessage.setMsgId("2901872");
        subMessage.setCreateTime("2020-12-12 18:54:06");
        subMessage.setStatus(false);
        // 将聊天记录对象保存到redis中
        redisOperatingUtil.listRightPush("subMessage", subMessage);
        // 获取list中的数据
        Object resultObj = redisOperatingUtil.listRange("subMessage", 0, redisOperatingUtil.listLen("subMessage"));
        // 将Object安全的转为List
        List<SubMessage> resultList = ObjectToOtherUtil.castList(resultObj, SubMessage.class);
        // 遍历获取到的结果
        if (resultList != null) {
            for (SubMessage message : resultList) {
                System.out.println(message.getUserName());
            }
        }
    }


在上述代码中,我们从redis中取出的数据是Object类型的,我们要将它转换为与之对应的实体类,一开始我是用的类型强转,但是idea会报黄色警告,于是就写了一个工具类用于将Object对象安全的转换为与之对应的类型,代码如下:


package com.lk.utils;
import java.util.ArrayList;
import java.util.List;
public class ObjectToOtherUtil {
    public static <T> List<T> castList(Object obj, Class<T> clazz) {
        List<T> result = new ArrayList<>();
        if (obj instanceof List<?>) {
            for (Object o : (List<?>) obj) {
                result.add(clazz.cast(o));
            }
            return result;
        }
        return null;
    }
}


执行后,我们看看redis是否有保存到我们写入的数据,如下所示,已经成功保存。


640.png

                       image-20201213163924700


我们再来看看,代码的执行结果,看看有没有成功获取到数据,如下图所示,也成功取到了。


640.png

                               image-20201213164038308


注意:如果你的项目对websocket进行了启动配置,可能会导致单元测试失败,报错java.lang.IllegalStateException: Failed to load ApplicationContext,解决方案就是注释掉websocket配置文件中的@Configuration即可。


测试list数据的取出


当我们把redis中存储的数据迁移到mysql后,需要删除redis中的数据,一开始我用的是它的delete方法,但是他的delete方法只能删除与之匹配的值,不能选择一个区间进行删除,于是就决定用它的pop方法进行出栈操作。


我们来测试下工具类中的listPopLeftKey方法。


@Test
    public void testListPop() {
        long item = 0;
        // 获取存储在redis中聊天记录的条数
        long messageListSize = redisOperatingUtil.listLen("subMessage");
        for (int i = 0; i < messageListSize; i++) {
            // 从头向尾取出链表中的元素
            SubMessage messageResult = (SubMessage) redisOperatingUtil.listPopLeftKey("subMessage");
            log.info(messageResult.getMsgText());
            item++;
        }
        log.info(item+"条数据已成功取出");
    }


执行结果如下所示,成功取出了redis中存储的两条数据。


640.png

                               image-20201213170726492


测试聊天记录转移至数据库


接下来我们在redis中放入三条数据用于测试


640.png

                        image-20201213171623890  


我们测试下将redis中的数据取出,然后写入数据库,代码如下:


// 测试聊天记录转移数据库
    @Test
    public void testRedisToMysqlTask() {
        // 获取存储在redis中聊天记录的条数
        long messageListSize = redisOperatingUtil.listLen("subMessage");
        // 写入数据库的数据总条数
        long resultCount = 0;
        for (int i = 0; i < messageListSize; i++) {
            // 从头到尾取出链表中的元素
            SubMessage subMessage= (SubMessage) redisOperatingUtil.listPopLeftKey("subMessage");
            // 向数据库写入数据
            int result = subMessageMapper.addMessageTextInfo(subMessage);
            if (result > 0) {
                // 写入成功
                resultCount++;
            }
        }
        log.info(resultCount+ "条聊天记录,已写入数据库");
    }


执行结果如下,数据已成功写入数据库且redis中的数据也被删除。

640.png

                           image-20201213171834299

640.png

                                image-20201213171956311


640.png

                             image-20201213172031222  


解析客户端数据保存至redis


完成上述操作后,我们redis那一块的东西就搞定了,接下来就可以实现将客户端的数据存到redis里了。


这里有个坑,因为websocket服务类中用到了@Component,会导致redis的工具类注入失败,出现null的情况,解决这个问题需要将当前类名声明为静态变量,然后在init中获取赋值redis工具类,代码如下:


// 解决redis操作工具类注入为null的问题
    public static WebSocketServer webSocketServer;
    @PostConstruct
    public void init() {
        webSocketServer = this;
        webSocketServer.redisOperatingUtil = this.redisOperatingUtil;
    }


在websocket服务的@OnMessage注解中,收到客户端发送的消息,我们将其保存到redis中,代码如下:


/**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     *                // @param session 客户端会话
     */
    @OnMessage
    public void onMessage(String message) {
        // 客户端发送的消息
        JSONObject jsReply = new JSONObject(message);
        // 添加在线人数
        jsReply.put("onlineUsers", getOnlineCount());
        if (jsReply.has("buddyId")) {
            // 获取推送方id
            String userId = jsReply.getString("userID");
            // 获取被推送方id
            String buddyId = jsReply.getString("buddyId");
            // 非测试数据则推送消息
            if (!buddyId.equals("121710f399b84322bdecc238199d6888")) {
                // 发送消息至推送方
                this.sendInfo(jsReply.toString(), userId);
            }
            // 构造聊天记录实体类数据
            SubMessage subMessage = new SubMessage();
            subMessage.setAvatarSrc(jsReply.getString("avatarSrc"));
            subMessage.setUserId(jsReply.getString("userID"));
            subMessage.setUserName(jsReply.getString("username"));
            subMessage.setMsgText(jsReply.getString("msg"));
            subMessage.setMsgId(jsReply.getString("msgId"));
            subMessage.setCreateTime(DateUtil.getThisTime());
            subMessage.setStatus(false);
            // 将聊天记录对象保存到redis中
            webSocketServer.redisOperatingUtil.listRightPush("subMessage", subMessage);
            // 发送消息至被推送方
            this.sendInfo(jsReply.toString(), buddyId);
        }
    }


做完上述操作后,收到客户端发送的消息就会自动写入redis。


定时将redis的数据写入mysql


接下来,我们使用quartz定时向mysql中写入数据,他执行定时任务的步骤分为2步:


  1. 创建任务类编写任务内容


  1. 在QuartzConfig文件中设置定时,执行第一步创建的任务。


  • 首先,创建quartzServer包,在其下创建RedisToMysqlTask.java文件,在此文件内实现redis写入mysql的代码
package com.lk.quartzServer;
import com.lk.dao.SubMessageMapper;
import com.lk.entity.SubMessage;
import com.lk.utils.RedisOperatingUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import javax.annotation.Resource;
// 将redis数据放进mysql中
@Slf4j
public class RedisToMysqlTask extends QuartzJobBean {
    @Resource
    private RedisOperatingUtil redisOperatingUtil;
    @Resource
    private SubMessageMapper subMessageMapper;
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 获取存储在redis中聊天记录的条数
        long messageListSize = redisOperatingUtil.listLen("subMessage");
        // 写入数据库的数据总条数
        long resultCount = 0;
        for (int i = 0; i < messageListSize; i++) {
            // 从头到尾取出链表中的元素
            SubMessage subMessage= (SubMessage) redisOperatingUtil.listPopLeftKey("subMessage");
            // 向数据库写入数据
            int result = subMessageMapper.addMessageTextInfo(subMessage);
            if (result > 0) {
                // 写入成功
                resultCount++;
            }
        }
        log.info(resultCount+ "条聊天记录,已写入数据库");
    }
}


  • 在config包下创建QuartzConfig.java文件,创建定时任务
package com.lk.config;
import com.lk.quartzServer.RedisToMysqlTask;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * Quartz定时任务配置
 */
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail RedisToMysqlQuartz() {
        // 执行定时任务
        return JobBuilder.newJob(RedisToMysqlTask.class).withIdentity("CallPayQuartzTask").storeDurably().build();
    }
    @Bean
    public Trigger CallPayQuartzTaskTrigger() {
        //cron方式,从每月1号开始,每隔三天就执行一次
        return TriggerBuilder.newTrigger().forJob(RedisToMysqlQuartz())
                .withIdentity("CallPayQuartzTask")
                .withSchedule(CronScheduleBuilder.cronSchedule("* * 4 1/3 * ?"))
                .build();
    }
}


这里我设置的定时任务是从每月1号开始,每隔三天就执行一次,Quartz定时任务采用的是cron表达式,自己算这个比较麻烦,这里推荐一个在线网站,可以很容易的生成表达式:Cron表达式生成器


实现效果


最后,配合Vue实现的浏览器端,跟大家展示下实现效果:


效果视频:使用Vue实现单聊


项目浏览器端代码地址:github/chat-system


项目在线体验地址:chat-system


写在最后


  • 公众号无法外链,如果文中有链接,可点击下方阅读原文查看😊
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
缓存 移动开发 NoSQL
php结合redis实现高并发下的抢购、秒杀功能的实例
php结合redis实现高并发下的抢购、秒杀功能的实例
257 0
|
NoSQL Redis
Redis学习4:List数据类型、拓展操作、实现日志等
注意点:对存储空间的顺序进行分析!
Redis学习4:List数据类型、拓展操作、实现日志等
|
存储 NoSQL Redis
Redis学习3:hash类型操作、拓展操作、实现购物等
首先可以理解成一个redis里面有一个小的redis。同时要注意引入了一个field的名字。
Redis学习3:hash类型操作、拓展操作、实现购物等
|
缓存 NoSQL 安全
2021年你还不会Shiro?----10.使用redis实现Shiro的缓存
上一篇文章已经总结了使用ehCache来实现Shiro的缓存管理,步骤也很简单,引入依赖后,直接开启Realm的缓存管理器即可。如果使用Redis来实现缓存管理其实也是一样的,我们也是需要引入redis的依赖,然后开启缓存传入自定义的redis的缓存管理器就行。区别是我们需要为自定义的redis缓存管理器提供自定义的缓存管理类。这个缓存管理类中需要使用到redisTemplate模板,这个模板我们也是需要自己定义。
270 0
2021年你还不会Shiro?----10.使用redis实现Shiro的缓存
|
NoSQL Java 关系型数据库
浅谈Redis实现分布式锁
浅谈Redis实现分布式锁
|
存储 NoSQL 关系型数据库
「Redis」事务实现机制
Redis事务实现机制
586 0
|
消息中间件 设计模式 NoSQL
异步结果通知实现——基于Redis实现,我这操作很可以
前段时间,我在内存中实现了一个简单异步通知框架。但由于没有持久化功能,应用重启就会导致数据丢失,且不支持分布式和集群。今天这篇笔记,引入了 Redis 来解决这些问题,以下是几点理由: 数据结构丰富,支持 List、Sorted Set 等 具有持久化功能,消息的可靠性能得到保证 高可用性,支持单机、主从、集群部署 项目中已使用,接入成本更低 基于 Redis 实现延时队列也有几种方法,展开详细讲讲。
|
NoSQL 前端开发 PHP
thinkphp+redis实现秒杀功能
thinkphp+redis实现秒杀功能
262 0
thinkphp+redis实现秒杀功能
|
存储 NoSQL 安全
分布式锁中-基于 Redis 的实现如何防重入
分布式锁中-基于 Redis 的实现如何防重入
258 0
分布式锁中-基于 Redis 的实现如何防重入
|
存储 消息中间件 缓存
分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇
分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇
362 0
分布式锁中-基于 Redis 的实现需避坑 - Jedis 篇
下一篇
无影云桌面