进行单元测试
做完上述操作后,最难弄的一关我们就已经搞定了,接下来我们来对一会需要使用的方法进行单元测试,确保其能够正常运行。
创建一个名为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是否有保存到我们写入的数据,如下所示,已经成功保存。
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+"条数据已成功取出"); }
执行结果如下所示,成功取出了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+ "条聊天记录,已写入数据库"); }
执行结果如下,数据已成功写入数据库且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; }
在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步:
- 创建任务类编写任务内容
- 在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
写在最后
- 公众号无法外链,如果文中有链接,可点击下方阅读原文查看😊