使用Redis实现聊天记录转存

存储 存储软件 Redis
这几天在实现我开源项目的单聊功能,在实现过程中遇到了需要将聊天记录保存至数据库的问题,在收到消息时肯定不能直接存数据库,因为这样在高并发的场景下,数据库就炸了。

 [[357356]]

本文转载自微信公众号「 神奇的程序员K」,作者 神奇的程序员K 。转载本文请联系 神奇的程序员K公众号。

前言

这几天在实现我开源项目的单聊功能,在实现过程中遇到了需要将聊天记录保存至数据库的问题,在收到消息时肯定不能直接存数据库,因为这样在高并发的场景下,数据库就炸了。

于是,我就想到了redis这个东西,第一次听说它是在2年前,但是一直没时间玩他,现在终于遇到了需要使用它的场景,在用的时候学它,本文就跟大家分享下我的实现思路以及过程,欢迎各位感兴趣的开发者阅读本文。

环境搭建

我的项目是基于SpringBoot2.x搭建的,电脑已经安装了redis,用的maven作为jar包管理工具,所以只需要在maven中添加需要的依赖包即可,如果你用的是其他管理工具,请自行查阅如何添加依赖。

本文需要用到依赖:Redis 、quartz,在pom.xml文件的dependencies标签下添加下述代码。

<!-- 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-quartz</artifactId> 
    <version>2.3.7.RELEASE</version> 
</dependency> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

在application.yml文件中配置相关参数。

spring: 
# redis配置 
  redis: 
    host: 127.0.0.1 # redis地址 
    port: 6379 # 端口号 
    password:  # 密码 
    timeout: 3000 # 连接超时时间,单位毫秒 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

实现思路

在websocket的服务中,收到客户端推送的消息后,我们对数据进行解析,构造聊天记录实体类,将其保存至redis中,最后我们使用quartz设置定时任务将redis的数据定时写入mysql中。

我们将上述思路进行下整理:

  • 解析客户端数据,构造实体类
  • 将数据保存至redis
  • 使用quartz将redis中的数据定时写入mysql

实现过程

实现思路很简单,难在如何将实体类数据保存至redis,我们需要把redis这一块配置好后,才能继续实现我们的业务需求。

redis支持的数据结构类型有:

  • set 集合,string类型的无序集合,元素不允许重复
  • hash 哈希表,键值对的集合,用于存储对象
  • list 列表,链表结构
  • zset有序集合
  • string 字符串,最基本的数据类型,可以包含任何数据,比如一个序列化的对象,它的字符串大小上限是512MB

redis的客户端分为jedis 和 lettuce,在SpringBoot2.x中默认客户端是使用lettuce实现的,因此我们不用做过多配置,在使用的时候通过RedisTemplate.xxx来对redis进行操作即可。

自定义RedisTemplate

在RedisTemplate中,默认是使用Java字符串序列化,将字符串存入redis后可读性很差,因此,我们需要对他进行自定义,使用Jackson 序列化,以 JSON 方式进行存储。

我们在项目的config包下,创建一个名为LettuceRedisConfig的Java文件,我们再此文件中配置其默认序列化规则,它的代码如下:

package com.lk.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.serializer.GenericJackson2JsonRedisSerializer; 
import org.springframework.data.redis.serializer.StringRedisSerializer; 
 
 
// 自定义RedisTemplate设置序列化器, 方便转换redis中的数据与实体类互转 
@Configuration 
public class LettuceRedisConfig { 
    /** 
     * Redis 序列化配置 
     */ 
    @Bean 
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { 
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); 
        redisTemplate.setConnectionFactory(connectionFactory); 
        // 使用GenericJackson2JsonRedisSerializer替换默认序列化 
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); 
        // 设置 Key 和 Value 的序列化规则 
        redisTemplate.setKeySerializer(new StringRedisSerializer()); 
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 
        redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); 
        // 初始化 RedisTemplate 序列化完成 
        redisTemplate.afterPropertiesSet(); 
        return redisTemplate; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

封装redis工具类

做完上述操作后,通过RedisTemplate存储到redis中的数据就是json形式的了,接下来我们对其常用的操作封装成工具类,方便我们在项目中使用。

在Utils包中创建一个名为RedisOperatingUtil,其代码如下:

package com.lk.utils; 
 
import org.springframework.data.redis.connection.DataType; 
import org.springframework.data.redis.core.RedisTemplate; 
import org.springframework.stereotype.Component; 
 
import javax.annotation.Resource; 
import java.util.Arrays; 
import java.util.Collections; 
import java.util.List; 
import java.util.Map; 
import java.util.concurrent.TimeUnit; 
 
@Component 
// Redis操作工具类 
public class RedisOperatingUtil { 
    @Resource 
    private RedisTemplate<Object, Object> redisTemplate; 
 
    /** 
     * 指定 key 的过期时间 
     * 
     * @param key  键 
     * @param time 时间(秒) 
     */ 
    public void setKeyTime(String key, long time) { 
        redisTemplate.expire(keytime, TimeUnit.SECONDS); 
    } 
 
    /** 
     * 根据 key 获取过期时间(-1 即为永不过期) 
     * 
     * @param key 键 
     * @return 过期时间 
     */ 
    public Long getKeyTime(String key) { 
        return redisTemplate.getExpire(key, TimeUnit.SECONDS); 
    } 
 
    /** 
     * 判断 key 是否存在 
     * 
     * @param key 键 
     * @return 如果存在 key 则返回 true,否则返回 false 
     */ 
    public Boolean hasKey(String key) { 
        return redisTemplate.hasKey(key); 
    } 
 
    /** 
     * 删除 key 
     * 
     * @param key 键 
     */ 
    public Long delKey(String... key) { 
        if (key == null || key.length < 1) { 
            return 0L; 
        } 
        return redisTemplate.delete(Arrays.asList(key)); 
    } 
 
    /** 
     * 获取 Key 的类型 
     * 
     * @param key 键 
     */ 
    public String keyType(String key) { 
        DataType dataType = redisTemplate.type(key); 
        assert dataType != null
        return dataType.code(); 
    } 
 
    /** 
     * 批量设置值 
     * 
     * @param map 要插入的 key value 集合 
     */ 
    public void barchSet(Map<String, Object> map) { 
        redisTemplate.opsForValue().multiSet(map); 
    } 
 
    /** 
     * 批量获取值 
     * 
     * @param list 查询的 Key 列表 
     * @return value 列表 
     */ 
    public List<Object> batchGet(List<String> list) { 
        return redisTemplate.opsForValue().multiGet(Collections.singleton(list)); 
    } 
 
 
    /** 
     * 获取指定对象类型key的值 
     * 
     * @param key 键 
     * @return 值 
     */ 
    public Object objectGetKey(String key) { 
        return redisTemplate.opsForValue().get(key); 
    } 
 
    /** 
     * 设置对象类型的数据 
     * 
     * @param key   键 
     * @param value 值 
     */ 
    public void objectSetValue(String key, Object value) { 
        redisTemplate.opsForValue().set(key, value); 
    } 
 
    /** 
     * 向list的头部插入一条数据 
     * 
     * @param key   键 
     * @param value 值 
     */ 
    public Long listLeftPush(String key, Object value) { 
        return redisTemplate.opsForList().leftPush(key, value); 
    } 
 
    /** 
     * 向list的末尾插入一条数据 
     * 
     * @param key   键 
     * @param value 值 
     */ 
    public Long listRightPush(String key, Object value) { 
        return redisTemplate.opsForList().rightPush(key, value); 
    } 
 
    /** 
     * 向list头部添加list数据 
     * 
     * @param key   键 
     * @param value 值 
     */ 
    public Long listLeftPushAll(String key, List<Object> value) { 
        return redisTemplate.opsForList().leftPushAll(key, value); 
    } 
 
    /** 
     * 向list末尾添加list数据 
     * 
     * @param key   键 
     * @param value 值 
     */ 
    public Long listRightPushAll(String key, List<Object> value) { 
        return redisTemplate.opsForList().rightPushAll(key, value); 
    } 
 
    /** 
     * 通过索引设置list元素的值 
     * 
     * @param key   键 
     * @param index 索引 
     * @param value 值 
     */ 
    public void listIndexSet(String key, long index, Object value) { 
        redisTemplate.opsForList().set(keyindex, value); 
    } 
 
    /** 
     * 获取列表指定范围内的list元素,正数则表示正向查找,负数则倒叙查找 
     * 
     * @param key   键 
     * @param start 开始 
     * @param end   结束 
     * @return boolean 
     */ 
    public Object listRange(String key, long start, long end) { 
        return redisTemplate.opsForList().range(key, start, end); 
    } 
 
    /** 
     * 从列表前端开始取出数据 
     * 
     * @param key 键 
     * @return 结果数组对象 
     */ 
    public Object listPopLeftKey(String key) { 
        return redisTemplate.opsForList().leftPop(key); 
    } 
 
    /** 
     * 从列表末尾开始遍历取出数据 
     * 
     * @param key 键 
     * @return 结果数组 
     */ 
    public Object listPopRightKey(String key) { 
        return redisTemplate.opsForList().rightPop(key); 
    } 
 
    /** 
     * 获取list长度 
     * 
     * @param key 键 
     * @return 列表长度 
     */ 
    public Long listLen(String key) { 
        return redisTemplate.opsForList().size(key); 
    } 
 
    /** 
     * 通过索引获取list中的元素 
     * 
     * @param key   键 
     * @param index 索引(index>=0时,0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推) 
     * @return 列表中的元素 
     */ 
    public Object listIndex(String key, long index) { 
        return redisTemplate.opsForList().index(keyindex); 
    } 
 
    /** 
     * 移除list元素 
     * 
     * @param key   键 
     * @param count 移除数量("负数"则从列表倒叙查找删除 count 个对应的值; "整数"则从列表正序查找删除 count 个对应的值;) 
     * @param value 值 
     * @return 成功移除的个数 
     */ 
    public Long listRem(String key, long count, Object value) { 
        return redisTemplate.opsForList().remove(keycount, value); 
    } 
 
    /** 
     * 截取指定范围内的数据, 移除不是范围内的数据 
     * @param key 操作的key 
     * @param start 截取开始位置 
     * @param end 截取激素位置 
     */ 
    public void listTrim(String key, long start, long end) { 
        redisTemplate.opsForList().trim(key, start, end); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.

进行单元测试

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

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

  • redisOperatingUtil为我们的redis工具类
  • subMessageMapper为聊天记录表的dao层
@RunWith(SpringRunner.class) 
@SpringBootTest 
@Slf4j 
public class RedisTest { 
    @Resource 
    private RedisOperatingUtil redisOperatingUtil; 
    @Resource 
    private SubMessageMapper subMessageMapper; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

接下来,我们看下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; // 消息状态 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

测试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()); 
            } 
        } 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

在上述代码中,我们从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
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

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

image-20201213163924700

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

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+"条数据已成功取出"); 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

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

image-20201213170726492

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

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

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+ "条聊天记录,已写入数据库"); 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

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

image-20201213171834299

 

image-20201213171956311

 

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; 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

在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); 
        } 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

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

定时将redis的数据写入mysql

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

创建任务类编写任务内容

在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+ "条聊天记录,已写入数据库"); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

在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.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

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

实现效果

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

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

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

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

 

 

责任编辑:武晓燕 来源: 神奇的程序员k
相关推荐

2022-07-15 15:11:27

SQLite微信数据库

2021-09-08 14:54:51

微信功能备份

2023-11-09 14:40:56

大数据自动化工具

2023-05-11 15:12:12

2020-12-22 06:02:48

JS聚合聊天

2021-03-29 09:23:08

微信聊天记录移动应用

2012-05-18 14:53:25

2023-05-09 08:54:01

ChatGPT必应聊天

2019-11-05 10:00:06

手机QQQQ空间QQ

2021-09-08 14:50:38

微信聊天记录移动应用

2016-09-01 20:45:44

2021-08-12 07:24:42

WhatsAppAndroidiOS

2015-08-06 16:19:52

微信聊天恢复数据

2021-04-06 11:03:28

WhatsAppiPhoneAndroid

2011-12-28 14:41:02

金山快盘同步MSN聊天

2022-03-01 15:34:48

勒索组织黑客网络安全

2019-11-04 10:40:06

手机QQ聊天记录

2025-02-25 13:55:06

2023-05-10 14:25:25

必应聊天

2020-10-30 20:54:29

微信新功能移动应用
点赞
收藏

51CTO技术栈公众号