引言
消息已读功能在现代应用程序中非常常见,它允许用户知道哪些消息已经被他们阅读过了。这对于保持沟通和信息同步至关重要。本文将介绍如何使用Java来实现消息已读功能,让我们开始吧!
技术栈
在实现消息已读功能之前,我们需要熟悉一些基本的技术栈:
- Java编程语言
- 数据库(MySQL)
- Web框架(例如Spring Boot)
步骤
1. 设计数据库表,除了一些常规字段外,需要一个保存已读用户id的字段,可以使用mysql中的json类型,用户点击读操作后,将用户id存到对应消息中的已读用户字段中,查询未读信息时,如果该字段中没有当前用户id,则表明是未读信息,效果如下:
建表语句如下,“消息开启时间”该字段是进行到点发送消息的关键字,未到时间用户不会收到该消息。
CREATE TABLE `notification` (
`notification_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '消息id',
`notification_type` tinyint(1) DEFAULT 1 COMMENT '消息类型(0-弹窗消息,1-普通消息)',
`notification_title` varchar(255) DEFAULT NULL COMMENT '消息标题',
`notification_content` longtext DEFAULT NULL COMMENT '消息内容',
`notification_start` varchar(255) DEFAULT NULL COMMENT '消息开启时间',
`is_active` tinyint(1) DEFAULT 0 COMMENT '消息状态 0开启,1关闭,默认开启',
`created_date` datetime DEFAULT NULL COMMENT '创建时间',
`updated_date` datetime DEFAULT NULL COMMENT '更新时间',
`read_ids` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '已读用户id',
PRIMARY KEY (`notification_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2. 创建Java实体类
在Java中,我们需要创建一个实体类来映射数据库表
@Data注解省去了大量的getter,setter方法
@Data
public class Notification implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Long notificationId;
/**
* 消息类型(0-弹窗消息,1-普通消息)
*/
@ApiModelProperty("消息类型(0-弹窗消息,1-普通消息)")
private Integer notificationType;
/**
* 消息标题
*/
@ApiModelProperty("消息标题")
private String notificationTitle;
/**
* 消息内容
*/
@ApiModelProperty("消息内容")
private String notificationContent;
/**
* 消息开启时间
*/
@ApiModelProperty("消息开启时间")
private String notificationStart;
/**
* 消息状态(0开启,1关闭)默认开启
*/
@ApiModelProperty("消息状态(0开启,1关闭)默认开启")
private Integer isActive;
/**
* 创建时间
*/
private LocalDateTime createdDate;
/**
* 更新时间
*/
private LocalDateTime updatedDate;
}
3. 实现已读标记逻辑
新增消息,编辑消息等CRUD操作就不展示了,CV工程师必备技能。着重于已读功能实现
接下来,我们需要在Java中编写代码来实现消息的已读标记逻辑。首次阅读后将用户id存到数据库 中。这里主要展示对应的sql语句,controller,service层逻辑清晰,逐层调用暂不展示,sql如下,
userId就是前端传来的用户id参数,notificationId也是前端传来的消息id,后面做了重复添加消息的检测,如果已经存在该用户Id不会继续保存,也就是只有首次阅读时会添加id,后面再次阅读不会有影响。
<insert id="saveReadUser">
UPDATE notification
SET read_ids = JSON_ARRAY_APPEND(read_ids, '$', #{userId})
WHERE notification_id = #{notificationId}
and not JSON_CONTAINS(read_ids, JSON_ARRAY(#{userId}));
</insert>
4. 获取全部消息
该部分就是全量查询数据库表中消息已到展示时间的数据,service实现类代码如下,
@Override
public Map<String,Object> getNotification(Long userId) {
//查找已读消息
List<NotificationVo> readNotification = notificationMapper.getNotification(userId, 1);
//查找未读消息
List<NotificationVo> notReadnotification = notificationMapper.getNotification(userId, 0);
HashMap<String,Object> notificationHashMap = new HashMap<>();
notificationHashMap.put("read",readNotification);
notificationHashMap.put("notRead",notReadnotification);
return notificationHashMap;
}
对应的sql语句如下,用isRead字段标识1查询已读消息,标识0查询未读消息
<select id="getNotification" resultType="com.jxepro.clinflash.notification.enity.vo.NotificationVo">
<if test="isRead == 0">
select * from notification n where not JSON_CONTAINS(n.read_ids, JSON_ARRAY(#{userId}))
</if>
<if test="isRead == 1">
select * from notification n where JSON_CONTAINS(n.read_ids, JSON_ARRAY(#{userId}))
</if>
and
n.notification_start <= NOW()
and
n.is_active = 0;
</select>
5.对获取道德消息列表进行处理
上面查询到了所有已读和未读的消息,现在前端反馈数据过于混乱,要求整理的更人性化些!只能用摸鱼的时间修改下代码了。
在原有数据的基础上,增加一个已读未读标识,并将是所有消息按照时间排序,代码如下
在原实体类上加入该字段,方便前端对数据进行二次处理。
/**
* 是否已读 0-未读,1-已读
*/
private Integer status;
- 修改后的业务处理逻辑代码如下:将未读消息,已读消息配置好相应的字段,再合到一起根据时间排序,另外也将未读消息排序单独作为一个列表返回,仁至义尽了,爱咋咋吧。
@Override
public Map<String,Object> getNotification(Long userId) {
//查找已读消息
List<NotificationVo> readNotification = notificationMapper.getNotification(userId, 1);
//查找未读消息
List<NotificationVo> notReadnotification = notificationMapper.getNotification(userId, 0);
for(NotificationVo notificationVo : readNotification){
//标记为已读
notificationVo.setStatus(1);
}
for(NotificationVo notificationVo:notReadnotification){
notificationVo.setStatus(0);
}
notReadnotification.sort(new Comparator<NotificationVo>() {
@Override
public int compare(NotificationVo notification1, NotificationVo notification2) {
int startComparison = notification2.getNotificationStart().compareTo(notification1.getNotificationStart());
if (startComparison != 0) {
return startComparison; // 按照notificationStart倒序排序
} else {
return notification2.getCreatedDate().compareTo(notification1.getCreatedDate()); // 按照createtime倒序排序
}
}
});
ArrayList<NotificationVo> mergedNotifications = new ArrayList<>();
//全部消息(已读和未读)
mergedNotifications.addAll(readNotification);
mergedNotifications.addAll(notReadnotification);
mergedNotifications.sort(new Comparator<NotificationVo>() {
@Override
public int compare(NotificationVo notification1, NotificationVo notification2) {
int startComparison = notification2.getNotificationStart().compareTo(notification1.getNotificationStart());
if (startComparison != 0) {
return startComparison; // 按照notificationStart倒序排序
} else {
return notification2.getCreatedDate().compareTo(notification1.getCreatedDate()); // 按照createtime倒序排序
}
}
});
HashMap<String,Object> notificationHashMap = new HashMap<>();
notificationHashMap.put("all",mergedNotifications);
notificationHashMap.put("notRead",notReadnotification);
return notificationHashMap;
}
斯古一,前端联调完成,打到测试验收,准备摸鱼。什么,又又又有新需求,累了毁灭吧。