杭州企业网站制作公司,网站的详细设计,网站建设实习每天内容,做一个美食网站怎么做目录
1.Spring 整合 Kafka
2.发送系统通知
2.1 封装事件对象
2.2 开发事件的生产者和消费者
2.3 触发事件#xff1a;在评论、点赞、关注后通知编辑
3.显示系统通知
3.1 通知列表
3.1.1 数据访问层
3.1.2 业务层
3.1.3 表现层
3.2 开发通知详情
3.2.1 开发数据…目录
1.Spring 整合 Kafka
2.发送系统通知
2.1 封装事件对象
2.2 开发事件的生产者和消费者
2.3 触发事件在评论、点赞、关注后通知编辑
3.显示系统通知
3.1 通知列表
3.1.1 数据访问层
3.1.2 业务层
3.1.3 表现层
3.2 开发通知详情
3.2.1 开发数据访问层
3.1.2 业务层
3.1.3 表现层
3.3 未读消息 1.Spring 整合 Kafka
引入依赖kafka配置 Kafka配置 server、consumer访问 Kafka生产者——kafkaTemplate.send(topic, data); 消费者——KafkaListener(topics {test}) public void handleMessage(ConsumerRecord record) { } 引入依赖
!-- https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka --
dependencygroupIdorg.springframework.kafka/groupIdartifactIdspring-kafka/artifactIdversion3.1.0/version
/dependency配置 Kafka
# KafkaProperties
spring.kafka.bootstrap-serverslocalhost:9092
spring.kafka.consumer.group-idtest-consumer-group
spring.kafka.consumer.enable-auto-committrue
spring.kafka.consumer.auto-commit-interval3000
测试类
package com.example.demo;import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;RunWith(SpringRunner.class)
SpringBootTest
ContextConfiguration(classes DemoApplication.class)
public class KafkaTests {Autowiredprivate KafkaProducer kafkaProducer;Testpublic void testKafka() {kafkaProducer.sendMessage(test, 你好);kafkaProducer.sendMessage(test, 在吗);try {Thread.sleep(1000 * 10);} catch (InterruptedException e) {e.printStackTrace();}}}//生产者
Component
class KafkaProducer {Autowiredprivate KafkaTemplate kafkaTemplate;public void sendMessage(String topic, String content) {kafkaTemplate.send(topic, content);}}//消费者
Component
class KafkaConsumer {KafkaListener(topics {test})public void handleMessage(ConsumerRecord record) {System.out.println(record.value());}}
2.发送系统通知
触发事件评论后发布通知点赞后发布通知关注后发布通知处理事件封装事件对象开发事件的生产者开发事件的消费者
2.1 封装事件对象
定义一个对象对事件封装 事件触发相关一切信息在 entity 包下创建 Event 实体类
package com.example.demo.entity;import java.util.HashMap;
import java.util.Map;/*** 发送系统消息实体类*/
public class Event {private String topic;//主题private int userId;private int entityType;//发生在哪个实体类private int entityId;//实体idprivate int entityUserId;//实体作者private MapString, Object data new HashMap();//处理其他事件会有数据进行记录将额外的数据存到 map 中具有扩展性public String getTopic() {return topic;}public Event setTopic(String topic) {this.topic topic;return this;}public int getUserId() {return userId;}public Event setUserId(int userId) {this.userId userId;return this;}public int getEntityType() {return entityType;}public Event setEntityType(int entityType) {this.entityType entityType;return this;}public int getEntityId() {return entityId;}public Event setEntityId(int entityId) {this.entityId entityId;return this;}public int getEntityUserId() {return entityUserId;}public Event setEntityUserId(int entityUserId) {this.entityUserId entityUserId;return this;}public MapString, Object getData() {return data;}public Event setData(String key, Object value) {this.data.put(key, value);return this;}}
2.2 开发事件的生产者和消费者 新建 event 包创建 EventProducer 类开发事件的生产者
添加 Component 注解 调用 KafkaTemlate生产者提供处理事件的方法将事件发布到指定的主题
package com.example.demo.event;import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.Event;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;/*** 开发事件的生产者*/Component
public class EventProducer {Autowiredprivate KafkaTemplate kafkaTemplate;public void fireEvent(Event event) {//将事件发布到指定的主题kafkaTemplate.send(event.getTopic(), JSONObject.toJSONString(event));}
}
创建 EventConsumer 类开发事件的消费者
添加 Component 注解 如果出现异常需要记录日志声明日志最终需要往 message 中插入数据需要注入 MessageService添加方法处理事件消费主题添加 KafkaListener(topics 主题)定义主题常量在 CommunityConstant 接口类中添加 /*** 主题: 评论*/String TOPIC_COMMENT comment;/*** 主题: 点赞*/String TOPIC_LIKE like;/*** 主题: 关注*/String TOPIC_FOLLOW follow;//后边需要用到这里先声明/*** 系统用户ID*/int SYSTEM_USER_ID 1;
定义消费者方法添加参数接收相关数据首先判断数据是否为空为空记录日志不为空的话是一个 JSON 字符串然后将字符串解析成相应的事件再去判断对象是否为空为空记录日志不为空此时内容和格式都正确发送站内通知构造 Massage 对象、设置系统用户id消息的发布者、设置消息的接收者entityUserId、设置会话 id、设置创建当前时间、设置具体内容内容里面包含对象对象包含的是页面拼接话语的数据——这件事谁触发的、对哪个实体做操作、点击查看链接到帖子详情页面即需要知道帖子 id使用 Map 存储具体内容用户 id、实体类型、实体 id、触发事件还可能又有额外的数据也需要存入 Map 中以 key、vlaue 形式存储最终存储到 Massage 对象中最终存储 JSON 字符串上述即构造完成 Massage 对象再进行存储
package com.example.demo.event;import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.Event;
import com.example.demo.entity.Message;
import com.example.demo.service.MessageService;
import com.example.demo.util.CommunityConstant;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.HashMap;
import java.util.Map;Component
public class EventConsumer implements CommunityConstant {//如果出现异常需要记录日志声明日志private static final Logger logger LoggerFactory.getLogger(EventConsumer.class);//最终需要往 message 中插入数据需要注入 MessageServiceAutowiredprivate MessageService messageService;//添加方法处理事件消费主题添加 KafkaListener(topics 主题)KafkaListener(topics {TOPIC_COMMENT, TOPIC_LIKE, TOPIC_FOLLOW})//定义消费者方法添加参数接收相关数据首先判断数据是否为空为空记录日志public void handleCommentMessage(ConsumerRecord record) {if (record null || record.value() null) {logger.error(消息的内容为空!);return;}// 不为空的话是一个 JSON 字符串然后将字符串解析成相应的事件再去判断对象是否为空为空记录日志Event event JSONObject.parseObject(record.value().toString(), Event.class);//再去判断对象是否为空为空记录日志if (event null) {logger.error(消息格式错误!);return;}//不为空此时内容和格式都正确发送站内通知Message message new Message();//构造 Massage 对象message.setFromId(SYSTEM_USER_ID);//设置系统用户id消息的发布者message.setToId(event.getEntityUserId());//设置消息的接收者entityUserIdmessage.setConversationId(event.getTopic());//设置会话 idmessage.setCreateTime(new Date());//设置创建当前时间//设置具体内容内容里面包含对象对象包含的是页面拼接话语的数据//这件事谁触发的、对哪个实体做操作、点击查看链接到帖子详情页面即需要知道帖子 id//使用 Map 存储具体内容用户 id、实体类型、实体 id、触发事件还可能又有额外的数据也需要存入 Map 中以 key、vlaue 形式存储//最终存储到 Massage 对象中最终存储 JSON 字符串MapString, Object content new HashMap();//使用 Map 存储具体内容content.put(userId, event.getUserId());//用户 id、实体类型content.put(entityType, event.getEntityType());//实体类型content.put(entityId, event.getEntityId());//实体 id//发事件还可能又有额外的数据也需要存入 Map 中以 key、vlaue 形式存储if (!event.getData().isEmpty()) {for (Map.EntryString, Object entry : event.getData().entrySet()) {content.put(entry.getKey(), entry.getValue());}}//最终存储到 Massage 对象中最终存储 JSON 字符串message.setContent(JSONObject.toJSONString(content));//再进行存储messageService.addMessage(message);}
}
2.3 触发事件在评论、点赞、关注后通知
打开 CommentController 类进行修改
处理事件需要注入 EventProducer添加评论之后在进行通知在 addComment 之后进行通知触发评论事件构造事件对象包含事件相关内容——事件主题实现接口、事件登录者触发、实体类型、实体 id、点击查看链接需要帖子 id实体作者不可以在这里拼接因为评论的目标可能是帖子也可能是评论实体作者需要进行判断评论目标是帖子注入 DiscussPostService查询帖子 id补充实体作者评论目标是评论在 CommentMapper 类中添加选择评论 id 的方法
//选择评论 id
Comment selectCommentById(int id);
在 resources 资源文件下 mapper 包的 comment-mapper.xml 添加实现方法 select idselectCommentById resultTypeCommentselect include refidselectFields/includefrom commentwhere id #{id}/select
在 CommentService 类中实现方法 //调用 选择评论 id 的方法public Comment findCommentById(int id) {return commentMapper.selectCommentById(id);}
评论目标是评论这个时候就可以根据评论查询帖子 id补充实体作者调用 eventProducer 发布消息
打开 LikeController 类进行修改类似上述操作
点赞在点赞之后进行触发事件
点赞进行通知likeStatus 1取消赞则不需要通知得到帖子 id需要重构点赞方法传入帖子 id最后触发事件 public String like(int entityType, int entityId, int entityUserId, int postId) {//获取当前用户User user hostHolder.getUser();//实现点赞调用 LikeServicelikeService.like(user.getId(), entityType, entityId, entityUserId);//统计点赞数量、点赞状态返回页面页面根据返回值做数量和状态显示// 数量long likeCount likeService.findEntityLikeCount(entityType, entityId);// 状态int likeStatus likeService.findEntityLikeStatus(user.getId(), entityType, entityId);// 返回的结果(用 Map 封装)MapString, Object map new HashMap();map.put(likeCount, likeCount);map.put(likeStatus, likeStatus);// 触发点赞事件//点赞进行通知likeStatus 1取消赞则不需要通知if (likeStatus 1) {Event event new Event().setTopic(TOPIC_LIKE).setUserId(hostHolder.getUser().getId()).setEntityType(entityType).setEntityId(entityId).setEntityUserId(entityUserId).setData(postId, postId);//得到帖子 id需要重构点赞方法传入帖子 ideventProducer.fireEvent(event);}//返回页面return CommunityUtil.getJSONString(0,null,map);} 重构点赞方法传入帖子 id 需要修改帖子详情页面传入帖子 iddiscuss-detail.html
a hrefjavascript:; th:onclick|like(this,1,${post.id},${post.userId},${post.id});| classtext-primarya hrefjavascript:; th:onclick|like(this,2,${cvo.comment.id},${cvo.comment.userId},${post.id});| classtext-primarya hrefjavascript:; th:onclick|like(this,2,${rvo.reply.id},${rvo.reply.userId},${post.id});| classtext-primary
在 discuss.js 中修改 like 方法
function like(btn, entityType, entityId, entityUserId, postId) {$.post(CONTEXT_PATH /like,{entityType:entityType,entityId:entityId,entityUserId:entityUserId,postId:postId},function(data) {data $.parseJSON(data);if(data.code 0) {$(btn).children(i).text(data.likeCount);$(btn).children(b).text(data.likeStatus1?已赞:赞);} else {alert(data.msg);}});
}
打开 FollowController 类进行修改
在关注之后followService.follow进行触发事件 //关注请求当前登陆用户关注某一个实体传入实体参数并且注入 HostHolderRequestMapping(path /follow, method RequestMethod.POST)ResponseBodypublic String follow(int entityType, int entityId) {//首先获取当前用户、然后再去关注给页面返回结果User user hostHolder.getUser();followService.follow(user.getId(), entityType, entityId);// 触发关注事件Event event new Event().setTopic(TOPIC_FOLLOW).setUserId(hostHolder.getUser().getId()).setEntityType(entityType).setEntityId(entityId).setEntityUserId(entityId);eventProducer.fireEvent(event);return CommunityUtil.getJSONString(0, 已关注!);}
在 ServiceLogAspect 类中需要稍微进行修改attributes 是和请求有关的对象在 ServiceLogAspect 类所有的 Service 都是通过 Controller 进行访问。而刚才我们写了一个 EventConsumer消费者并且调用了 Service不是通过 Controller 调用这次调用得不到 Request会出现空指针异常
package com.example.demo.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;Component
Aspect
public class ServiceLogAspect {//实例化 Loggerprivate static final Logger logger LoggerFactory.getLogger(ServiceLogAspect.class);//声明切点所有业务组件都去处理Pointcut(execution(* com.example.demo.service.*.*(..)))public void pointcut() {}//使用前置通知在业务组件一开始记录日志Before(pointcut())public void before(JoinPoint joinPoint) {//记录格式用户[1.2.3.4],在[xxx],访问了[com.example.demo.service.xxx()].//用户 ip 通过 request 获取获取 requestRequestContextHolder.getRequestAttributes();ServletRequestAttributes attributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();//消费者调用了if (attributes null) {return;}HttpServletRequest request attributes.getRequest();String ip request.getRemoteHost();//拼接时间new Date然后实例化String now new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date());//访问某个类某个方法类名 方法名给方法添加 JoinPoint 连接点参数连接点指代程序植入的目标方法String target joinPoint.getSignature().getDeclaringTypeName() . joinPoint.getSignature().getName();//全部拼接logger.info(String.format(用户[%s],在[%s],访问了[%s]., ip, now, target));}
}
评论 点赞 关注 3.显示系统通知
通知列表显示评论、点赞、关注三种类型的通知通知详情分页显示某一类主题所包含的通知未读消息在页面头部显示所有的未读消息数量
3.1 通知列表
3.1.1 数据访问层
通知我们当时存放在 Message 表中因此对 MessageMapper 类增加方法 查询某个主题评论、点赞、关注下最新的通知查询某个用户通知传入用户 id、并且传入主题查询某个主题所包含的通知数量返回整数查询未读通知数量 //查询某个主题评论、点赞、关注下最新的通知Message selectLatestNotice(int userId, String topic);//查询某个主题所包含的通知数量int selectNoticeCount(int userId, String topic);//查询未读通知数量int selectNoticeUnreadCount(int userId, String topic);
方法申明之后再去实现方法在对应的 message-mapper.xml 中去实现
查询某个主题评论、点赞、关注下最新的通知询最新的 id并且状态status ! 2代表私信没有被删除发送通知是由系统通知用户from_id 1,避免和普通私信混淆接收者为用户 id主题是在 conversation_id 中存储查询某个主题所包含的通知数量同上只是查询的是数量不是最新 id查询未读通知数量同上未读则 status 0还需要查询整个通知的未读数量如果不传入 topic那么意味着查询的就是整个通知的未读数量 !--查询某个主题评论、点赞、关注下最新的通知--select idselectLatestNotice resultTypeMessageselect include refidselectFields/includefrom messagewhere id in (select max(id) from messagewhere status ! 2and from_id 1and to_id #{userId}and conversation_id #{topic})/select!--查询某个主题所包含的通知数量--select idselectNoticeCount resultTypeintselect count(id) from messagewhere status ! 2and from_id 1and to_id #{userId}and conversation_id #{topic}/select!--查询未读通知数量--select idselectNoticeUnreadCount resultTypeintselect count(id) from messagewhere status 0and from_id 1and to_id #{userId}if testtopic!nulland conversation_id #{topic}/if/select
3.1.2 业务层
通知业务层在 MessageService 类中添加方法
查询最新的通知直接调用 selectLatestNotice查询通知数量直接调用上述方法即可查询未读数量直接调用上述方法即可 //查询最新的通知直接调用 selectLatestNoticepublic Message findLatestNotice(int userId, String topic) {return messageMapper.selectLatestNotice(userId, topic);}//查询通知数量public int findNoticeCount(int userId, String topic) {return messageMapper.selectNoticeCount(userId, topic);}//查询未读数量public int findNoticeUnreadCount(int userId, String topic) {return messageMapper.selectNoticeUnreadCount(userId, topic);}
3.1.3 表现层
处理 Controller处理请求处理模板
在 MessageController 类中处理请求新增方法显示通知列表
声明访问路径查询为 GET 请求传入 Model向模板传数据获取当前对象查询三类通知评论、点赞、关注查询评论类通知实现接口传入常量查询到数据后再做一些补充的数据例如数量、user等创建 Map 用来聚合数据如果查询到的数据不为空进一步查询更多数据存放到 Map 中首先在 Map 中存入 message但是在 message 中 content 是一个字符串需要还原成对象放入 Map 中就可以方便访问数据 还原成对象使用 HtmlUtils 工具对 html 的标签、字符进行转义得到的 content 就没有转义字符再转化相应的对象再存入 Map 中user、entityType、entityId、postId还需要查询某一类的数量和未读数量存放到 Map 中最后传入模板查询点赞类通知与上述评论通知大致相同类型为点赞—— TOPIC_LIKE查询关注类通知类型为关注—— TOPIC_FOLLOW关注点击跳转链接跳转到关注的人而不是跳转到帖子所以不需要在 Map 中存 帖子 id显示系统通知的未读数量和朋友私信的未读数量 之前编写过查询私信列表的时候没有开发通知列表现在我们可以一边显示朋友私信的未读数量一边显示系统通知的未读数量在私信列表方法中的查询未读消息数量后添加显示系统通知的未读数量 RequestMapping(path /letter/list, method RequestMethod.GET)public String getLetterList(Model model, Page page) {//设置分页信息每页显示多少条数据、分页路径、一共多少条数据——查询当前会话的数据、传入 userId需要获取 UserUser user hostHolder.getUser();page.setLimit(5);page.setPath(/letter/list);page.setRows(messageService.findConversationCount(user.getId()));//查询会话列表得到数据(显示未读数量、每一次会话的未读数量、会话中包含多少条数据)//声明集合用 Map 封装将多个数据存入 Map 中ListMessage conversationList messageService.findConversations(user.getId(), page.getOffset(), page.getLimit());ListMapString, Object conversations new ArrayList();//遍历列表新建 HashMap 重构数据存入遍历的每一次数据、存入未读详细数据用户 id、会话 id// 、存入多少条数量会话 id、显示当前用户相对应的用户头像if (conversationList ! null) {for (Message message : conversationList) {MapString, Object map new HashMap();map.put(conversation, message);map.put(letterCount, messageService.findLetterCount(message.getConversationId()));map.put(unreadCount, messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));//寻找目标 id如果当前用户是消息的发起者目标就是接收人如果当前对象是消息的接收者目标就是发起者int targetId user.getId() message.getFromId() ? message.getToId() : message.getFromId();//将目标对象存入 HashMap 中注入 UserServicemap.put(target, userService.findUserById(targetId));//将得到的 HashMap 存入集合当中conversations.add(map);}}//最后传入模板中model.addAttribute(conversations, conversations);// 查询未读消息数量查询整个用户所有的未读消息数量传入 Model 中显示返回 Model 路径/site/letterint letterUnreadCount messageService.findLetterUnreadCount(user.getId(), null);model.addAttribute(letterUnreadCount, letterUnreadCount);// 查询系统通知的未读数量int noticeUnreadCount messageService.findNoticeUnreadCount(user.getId(), null);model.addAttribute(noticeUnreadCount, noticeUnreadCount);return /site/letter;}
} 最后返回到模板 //新增方法显示通知列表//声明访问路径查询为 GET 请求传入 Model向模板传数据RequestMapping(path /notice/list, method RequestMethod.GET)public String getNoticeList(Model model) {//获取当前对象查询三类通知评论、点赞、关注User user hostHolder.getUser();//查询评论类通知//实现接口传入常量查询到数据后再做一些补充的数据例如数量、user等创建 Map 用来聚合数据如果查询到的数据不为空进一步查询更多数据存放到 Map 中Message message messageService.findLatestNotice(user.getId(), TOPIC_COMMENT);MapString, Object messageVO new HashMap();//如果在if内如 message为nullmessageVo根本没有存放这个message,所以放外边messageVO.put(message, message);if (message ! null) {//但是在 message 中 content 是一个字符串需要还原成对象放入 Map 中就可以方便访问数据//使用 HtmlUtils 工具对 html 的标签、字符进行转义得到的 content 就没有转义字符String content HtmlUtils.htmlUnescape(message.getContent());//再转化相应的对象MapString, Object data JSONObject.parseObject(content, HashMap.class);messageVO.put(user, userService.findUserById((Integer) data.get(userId)));messageVO.put(entityType, data.get(entityType));messageVO.put(entityId, data.get(entityId));messageVO.put(postId, data.get(postId));//还需要查询某一类的数量和未读数量存放到 Map 中最后传入模板int count messageService.findNoticeCount(user.getId(), TOPIC_COMMENT);messageVO.put(count, count);int unread messageService.findNoticeUnreadCount(user.getId(), TOPIC_COMMENT);messageVO.put(unread, unread);}//最后传入模板model.addAttribute(commentNotice, messageVO);//查询点赞类通知message messageService.findLatestNotice(user.getId(), TOPIC_LIKE);messageVO new HashMap();messageVO.put(message, message);if (message ! null) {String content HtmlUtils.htmlUnescape(message.getContent());MapString, Object data JSONObject.parseObject(content, HashMap.class);messageVO.put(user, userService.findUserById((Integer) data.get(userId)));messageVO.put(entityType, data.get(entityType));messageVO.put(entityId, data.get(entityId));messageVO.put(postId, data.get(postId));int count messageService.findNoticeCount(user.getId(), TOPIC_LIKE);messageVO.put(count, count);int unread messageService.findNoticeUnreadCount(user.getId(), TOPIC_LIKE);messageVO.put(unread, unread);}model.addAttribute(likeNotice, messageVO);// 查询关注类通知message messageService.findLatestNotice(user.getId(), TOPIC_FOLLOW);messageVO new HashMap();messageVO.put(message, message);if (message ! null) {String content HtmlUtils.htmlUnescape(message.getContent());MapString, Object data JSONObject.parseObject(content, HashMap.class);messageVO.put(user, userService.findUserById((Integer) data.get(userId)));messageVO.put(entityType, data.get(entityType));messageVO.put(entityId, data.get(entityId));int count messageService.findNoticeCount(user.getId(), TOPIC_FOLLOW);messageVO.put(count, count);int unread messageService.findNoticeUnreadCount(user.getId(), TOPIC_FOLLOW);messageVO.put(unread, unread);}model.addAttribute(followNotice, messageVO);//显示系统通知的未读数量和朋友私信的未读数量int letterUnreadCount messageService.findLetterUnreadCount(user.getId(), null);model.addAttribute(letterUnreadCount, letterUnreadCount);int noticeUnreadCount messageService.findNoticeUnreadCount(user.getId(), null);model.addAttribute(noticeUnreadCount, noticeUnreadCount);//之前编写过查询私信列表的时候没有开发通知列表//现在我们可以一边显示朋友私信的未读数量一边显示系统通知的未读数量在私信列表方法中的查询未读消息数量后添加显示系统通知的未读数量return /site/notice;}
最后处理模板letter.html a classnav-link position-relative th:href{/notice/list}系统通知span classbadge badge-danger th:text${noticeUnreadCount} th:if${noticeUnreadCount!0}27/span
/a
处理通知模板notice.html声明模板 3.2 开发通知详情
3.2.1 开发数据访问层
对 MessageMapper 类增加方法支持分页 // 查询某个主题所包含的通知列表ListMessage selectNotices(int userId, String topic, int offset, int limit);
方法申明之后再去实现方法在对应的 message-mapper.xml 中去实现 !--查询某个主题所包含的通知列表--select idselectNotices resultTypeMessageselect include refidselectFields/includefrom messagewhere status ! 2and from_id 1and to_id #{userId}and conversation_id #{topic}order by create_time desclimit #{offset}, #{limit}/select
3.1.2 业务层
通知业务层在 MessageService 类中添加方法 //查询某个主题所包含的通知列表public ListMessage findNotices(int userId, String topic, int offset, int limit) {return messageMapper.selectNotices(userId, topic, offset, limit);}
3.1.3 表现层
在 MessageController 类中处理请求新增方法显示通知详情
声明访问路径查询某一类通知通过路径传入 topic查询为 GET 请求传入 Model向模板传数据方法中参数引入使用 PathVariable 得到路径中的 topic并且支持分页传入 Model获取当前用户查询当前用户的详细信息对分页进行设置分页条数、路径、行数查询某个主题的通知集合申明集合 存入Map用来存入聚合数据实例化新的 ArrayList如果这个集合非空聚合数据遍历集合为了存聚合数据实例化 Map存入通知、内容content 是一个字符串进行转义处理再还原成对象放入 Map还原成对象使用 HtmlUtils 工具对 html 的标签、字符进行转义得到的 content 就没有转义字符再转化相应的对象再存入 Map 中user、entityType、entityId、postId补充通知的作者将 map 存入集合中最终将数据存入 Model 中还需要设置已读最终返回给模板 //显示通知详情//声明访问路径查询某一类通知通过路径传入 topic查询为 GET 请求传入 Model向模板传数据RequestMapping(path /notice/detail/{topic}, method RequestMethod.GET)//方法中参数引入使用 PathVariable 得到路径中的 topic并且支持分页传入 Modelpublic String getNoticeDetail(PathVariable(topic) String topic, Page page, Model model) {//获取当前用户查询当前用户的详细信息对分页进行设置分页条数、路径、行数User user hostHolder.getUser();page.setLimit(5);page.setPath(/notice/detail/ topic);page.setRows(messageService.findNoticeCount(user.getId(), topic));//查询某个主题的通知集合ListMessage noticeList messageService.findNotices(user.getId(), topic, page.getOffset(), page.getLimit());//申明集合 存入Map用来存入聚合数据实例化新的 ArrayListListMapString, Object noticeVoList new ArrayList();//如果这个集合非空聚合数据if (noticeList ! null) {//遍历集合为了存聚合数据实例化 Mapfor (Message notice : noticeList) {MapString, Object map new HashMap();// 通知map.put(notice, notice);// 内容content 是一个字符串进行转义处理再还原成对象放入 MapString content HtmlUtils.htmlUnescape(notice.getContent());MapString, Object data JSONObject.parseObject(content, HashMap.class);map.put(user, userService.findUserById((Integer) data.get(userId)));map.put(entityType, data.get(entityType));map.put(entityId, data.get(entityId));map.put(postId, data.get(postId));// 通知作者map.put(fromUser, userService.findUserById(notice.getFromId()));noticeVoList.add(map);}}model.addAttribute(notices, noticeVoList);// 设置已读ListInteger ids getLetterIds(noticeList);if (!ids.isEmpty()) {messageService.readMessage(ids);}return /site/notice-detail;}
处理模板点击通知列表访问页面、处理详情页面notice-detail.html 3.3 未读消息 未读消息 朋友私信 系统通知使用拦截器处理
在 interceptor 包下创建 MessageInterceptor 类拦截器
添加注解 Component实现接口 HandlerInterceptor查新当前用户的未读数量注入 HostHolder注入 MessageService在 调用 Controller 之后模板之前进行拦截重写 postHandle 方法
package com.example.demo.controller.interceptor;import com.example.demo.entity.User;
import com.example.demo.service.MessageService;
import com.example.demo.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** 未读消息 朋友私信 系统通知使用拦截器处理*/
Component
public class MessageInterceptor implements HandlerInterceptor {//查新当前用户的未读数量注入 HostHolder注入 MessageServiceAutowiredprivate HostHolder hostHolder;Autowiredprivate MessageService messageService;//在 调用 Controller 之后模板之前进行拦截重写 postHandle 方法Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {User user hostHolder.getUser();if (user ! null modelAndView ! null) {//朋友私信int letterUnreadCount messageService.findLetterUnreadCount(user.getId(), null);//系统通知int noticeUnreadCount messageService.findNoticeUnreadCount(user.getId(), null);//综合modelAndView.addObject(allUnreadCount, letterUnreadCount noticeUnreadCount);}}
}
在 config 中配置拦截器WebMvcConfig 类
注入拦截器进行拦截
package com.example.demo.config;
import com.example.demo.controller.interceptor.LoginRequiredInterceptor;
import com.example.demo.controller.interceptor.LoginTicketInterceptor;
import com.example.demo.controller.interceptor.MessageInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 拦截器*/Configuration
public class WebMvcConfig implements WebMvcConfigurer {Autowiredprivate LoginTicketInterceptor loginTicketInterceptor;//登录状态拦截器Autowiredprivate LoginRequiredInterceptor loginRequiredInterceptor;//未读消息拦截器Autowiredprivate MessageInterceptor messageInterceptor;public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginTicketInterceptor).excludePathPatterns(/**/*.css, /**/*.js, /**/*.png, /**/*.jpg, /**/*.jpeg);registry.addInterceptor(loginRequiredInterceptor).excludePathPatterns(/**/*.css, /**/*.js, /**/*.png, /**/*.jpg, /**/*.jpeg);//未读消息拦截器registry.addInterceptor(messageInterceptor).excludePathPatterns(/**/*.css, /**/*.js, /**/*.png, /**/*.jpg, /**/*.jpeg);}}在 index.html 进行修改显示消息位置
a classnav-link position-relative th:href{/letter/list}消息span classbadge badge-danger th:text${allUnreadCount!0?allUnreadCount:}12/span/a