参考牛客网高级项目教程
尚硅谷kafka教学笔记
功能需求
- 1.读取kafka消费者存入库中的系统消息,并在消息页面显示出来
- 2.显示整体通知页面会话列表
- 按照主题分为三种通知:评论、赞、关注
- 3.点击每个会话列表,会显示当前主题的详细通知列表
1. 开发会话列表显示
1. dao层添加查询数据库
接口方法定义
/** 查询系统给指定用户指定主题下最新的通知*/
Message selectLatestNotice(int userId, String topic);
/**
* 查询系统给指定用户指定主题下包含系统通知的数量
*/
int selectNoticeCount(int userId, String topic);
/**
* 查询未读系统通知的数量-动态传参
* 如果传入主题,就是当前主题未读数量-否则就是所有主题的未读消息
*/
int selectNoticeUnreadCount(int userId, String topic);
sql定义
<!-- 查询系统给指定用户指定主题下最新的通知-->
<select id="selectLatestNotice" resultMap="message">
<include refid="selectFields"></include>
where id in (
select max(id) from message
where status != 2
and from_id = 1
and to_id = #{userId}
and conversation_id = #{topic}
)
</select>
<!-- 查询系统给指定用户指定主题下包含系统通知的数量-->
<select id="selectNoticeCount" resultType="int">
select count(id) from message
where status != 2
and from_id = 1
and to_id = #{userId}
and conversation_id = #{topic}
</select>
<!-- 查询未读系统通知的数量-动态sql-->
<select id="selectNoticeUnreadCount" resultType="int">
select count(id) from message
where status != 2
and status = 0
and from_id = 1
and to_id = #{userId}
<if test="topic != null">
and conversation_id = #{topic}
</if>
</select>
2.service层封装业务
// 处理系统通知的查询业务
public 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.controller层处理请求
/**
* 查询系统通知列表的请求
* @param model
* @return
*/
@RequestMapping(value = "/notice/list", method = RequestMethod.GET)
public String getNoticeList(Model model) {
// 1.先获取当前登录用户
User user = hostHolder.getUser();
int userId = user.getId();
// 2.显示评论总体信息
addData(userId, TOPIC_COMMENT);
model.addAttribute("commentNotice", messageVo);
// 3. 显示点赞信息
addData(userId, TOPIC_LIKE);
model.addAttribute("likeNotice", messageVo);
// 4. 显示关注信息
addData(userId, TOPIC_FOLLOW);
model.addAttribute("followNotice", messageVo);
// 5. 查询总的未读消息数量
int letterUnreadCount = messageService.findLetterUnreadCount(userId, null);
model.addAttribute("letterUnreadCount", letterUnreadCount);
int noticeUnreadCount = messageService.findNoticeUnreadCount(userId, null);
model.addAttribute("noticeUnreadCount", noticeUnreadCount);
return "site/notice";
}
/**
* 封装处理系统通知封装数据的代码
* @param userId 当前登录的用户id
* @param topic 主题
*/
private void addData(int userId, String topic) {
message = messageService.findLatestNotice(userId, topic);
messageVo = new HashMap<>();
if(message != null) {
// 装进最新一条评论信息
messageVo.put("message", message);
// 将content内的json字符串恢复成对象,方便处理
// 先将转义字符反转成正常字符
message.setContent(HtmlUtils.htmlUnescape(message.getContent()));
Map<String, Object> data = JSONObject.parseObject(message.getContent(), Map.class);
// 触发者信息
messageVo.put("fromUser", userService.findUserById((Integer) data.get("userId")));
// 触发对象信息
messageVo.put("entityType", data.get("entityType"));
messageVo.put("entityId", data.get("entityId"));
Integer postId = (Integer)data.get("postId");
if(postId != null) { // 关注类通知没有这个数据,因为不需要
messageVo.put("postId", postId);
}
// 消息总数
int count = messageService.findNoticeCount(userId, topic);
messageVo.put("count", count);
// 未读消息数量
int unread = messageService.findNoticeUnreadCount(userId, topic);
messageVo.put("unread", unread);
}
}
4.处理模板页面
1)链接选择-总的未读消息展示
active
- active作用在哪个标签,表实哪个标签的显示样式不同-设定为选中样式
朋友私信
<li class="nav-item">
<a class="nav-link position-relative active" th:href="@{/letter/list}">朋友私信
<span class="badge badge-danger" th:text="${letterUnreadCount}" th:if="${letterUnreadCount!=0}">3</span></a>
</li>
<li class="nav-item">
<a class="nav-link position-relative" th:href="@{/notice/list}">
系统通知<span class="badge badge-danger" th:text="${noticeUnreadCount}" th:if="${noticeUnreadCount!=0}">27</span></a>
</li>
系统通知
<li class="nav-item">
<a class="nav-link position-relative" th:href="@{/letter/list}">
朋友私信<span class="badge badge-danger" th:text="${letterUnreadCount}" th:if="${letterUnreadCount!=0}">3</span></a>
</li>
<li class="nav-item">
<a class="nav-link position-relative active" th:href="@{/notice/list}">
系统通知<span class="badge badge-danger" th:text="${noticeUnreadCount}" th:if="${noticeUnreadCount!=0}">27</span></a>
</li>
2)通知列表显示
评论列表
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:if="${commentNotice.get(message)!=null}">
<span class="badge badge-danger" th:text="${commentNotice.unread}" th:if="${commentNotice.unread!=0}">3</span>
<img src="http://static.nowcoder.com/images/head/reply.png" class="mr-4 user-header" alt="通知图标">
<div class="media-body">
<h6 class="mt-0 mb-3">
<span>评论</span>
<span class="float-right text-muted font-size-12"
th:text="${#dates.format(commentNotice.message.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
</h6>
<div>
<a href="#">用户
<i th:utext="${commentNotice.fromUser.username}">nowcoder</i> 评论了你的<b>帖子</b> ...</a>
<ul class="d-inline font-size-12 float-right">
<li class="d-inline ml-2"><span class="text-primary">共 <i th:text="${commentNotice.count}">3</i> 条会话</span></li>
</ul>
</div>
</div>
</li>
点赞列表
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:if="${likeNotice.get(message)!=null}">
<span class="badge badge-danger" th:text="${likeNotice.unread!=0?likeNotice.unread:''}">3</span>
<img src="http://static.nowcoder.com/images/head/like.png" class="mr-4 user-header" alt="通知图标">
<div class="media-body">
<h6 class="mt-0 mb-3">
<span>赞</span>
<span class="float-right text-muted font-size-12"
th:text="${#dates.format(likeNotice.message.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
</h6>
<div>
<a href="#">
用户 <i th:utext="${likeNotice.fromUser.username}">nowcoder</i>
点赞了你的<b th:text="${likeNotice.entityType==1?'帖子':'回复'}">帖子</b> ...</a>
<ul class="d-inline font-size-12 float-right">
<li class="d-inline ml-2"><span class="text-primary">共 <i th:text="${likeNotice.count}">3</i> 条会话</span></li>
</ul>
</div>
</div>
</li>
关注列表
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:if="${followNotice.get(message)!=null}">
<span class="badge badge-danger" th:text="${followNotice.unread!=0?followNotice.unread:''}">3</span>
<img src="http://static.nowcoder.com/images/head/follow.png" class="mr-4 user-header" alt="通知图标">
<div class="media-body">
<h6 class="mt-0 mb-3">
<span>关注</span>
<span class="float-right text-muted font-size-12"
th:text="${#dates.format(followNotice.message.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
</h6>
<div>
<a href="#">
用户 <i th:utext="${followNotice.fromUser.username}">nowcoder</i>
关注了你 ...</a>
<ul class="d-inline font-size-12 float-right">
<li class="d-inline ml-2"><span class="text-primary">共 <i th:text="${followNotice.count}">3</i> 条会话</span></li>
</ul>
</div>
</div>
</li>
测试结果:
2. 开发通知列表详情页面
1. dao层处理数据
接口方法定义
- 特定主题下的通知列表-支持分页查询
/**
* 查询指定主题下的通知列表详情
* @param userId
* @param topic
* @param offset
* @param limit
* @return
*/
List<Message> selectNoticeList(int userId, String topic, int offset, int limit);
sql定义
!-- 查询特点主题下的通知列表详情-->
<select id="selectNoticeList" resultType="message">
<include refid="selectFields"></include>
where status != 2
and from_id = 1
and to_id = #{userId}
and conversation_id = #{topic}
order by id desc
limit #{offset}, #{limit}
</select>
2. service层封装业务
public List<Message> findNotices(int userId, String topic, int offset, int limit) {
return messageMapper.selectNoticeList(userId, topic, offset, limit);
}
3. controller层处理查询请求
- 1.要查询出每条消息-每条消息数据聚合封装
- 2.查看时,要将未读状态改为已读
/**
* 显示通知列表详情
* @param topic
* @param page
* @param model
* @return
*/
@RequestMapping(path = "/notice/detail/{topic}", method = RequestMethod.GET)
public 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));
List<Message> noticeList = messageService.findNotices(user.getId(), topic, page.getOffset(), page.getLimit());
List<Map<String, Object>> noticeVoList = new ArrayList<>();
List<Integer> ids = new ArrayList<>();
if (noticeList != null) {
for (Message notice : noticeList) {
// 先筛选出未读信息
addUnreadIds(ids,notice);
Map<String, Object> map = new HashMap<>();
// 通知
map.put("notice", notice);
// 内容
String content = HtmlUtils.htmlUnescape(notice.getContent());
Map<String, 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);
// 设置已读
if (!ids.isEmpty()) {
messageService.updateStatus(ids);
}
return "/site/notice-detail";
}
/**
* 统计未读消息的id
* 与查询逻辑一致,是发送对象为当前用户的私信且状态为0
* @param ids 收集未读消息id的列表
* @param message 要判定的每个消息对象
*/
private void addUnreadIds(List<Integer> ids, Message message) {
if(message.getToId() == hostHolder.getUser().getId() && message.getStatus() == 0) {
ids.add(message.getId());
}
}
4. 处理模板页面
- 处理链接
<a th:href="@{/notice/detail/comment}">用户
<i th:utext="${commentNotice.fromUser.username}">nowcoder</i> 评论了你的<b>帖子</b> ...</a>
- 分别根据路径中的主题展示不同的消息
<!-- 通知列表 -->
<ul class="list-unstyled mt-4">
<li class="media pb-3 pt-3 mb-2" th:each="map:${notices}">
<!-- 系统图标 -->
<img th:src="${map.fromUser.headerUrl}" class="mr-4 rounded-circle user-header" alt="系统图标">
<div class="toast show d-lg-block" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="mr-auto" th:utext="${map.fromUser.username}">落基山脉下的闲人</strong>
<small th:text="${#dates.format(map.notice.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-25 15:49:32</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="toast-body">
<!-- 评论列表 -->
<span th:if="${topic.equals('comment')}">
用户
<i th:utext="${map.user.username}">nowcoder</i>
评论了你的<b th:text="${map.entityType==1?'帖子':'回复'}">帖子</b>,
<a class="text-primary" th:href="@{|/discuss/detail/${map.postId}|}">点击查看</a> !
</span>
<!-- 点赞列表 -->
<span th:if="${topic.equals('like')}">
用户
<i th:utext="${map.user.username}">nowcoder</i>
点赞了你的<b th:text="${map.entityType==1?'帖子':'回复'}">帖子</b>,
<a class="text-primary" th:href="@{|/discuss/detail/${map.postId}|}">点击查看</a> !
</span>
<!-- 关注列表 -->
<span th:if="${topic.equals('follow')}">
用户
<i th:utext="${map.user.username}">nowcoder</i>
关注了你,
<a class="text-primary" th:href="@{|/user/profile/${map.user.id}|}">点击查看</a> !
</span>
</div>
</div>
</li>
</ul>
测试结果
4. 拦截统计所有未读消息数量
定义拦截器
- controller处理完数据,在渲染前,统计未读消息数量,将未读数量显示
@Component
public class MessageInterceptor implements HandlerInterceptor {
@Autowired
private HostHolder hostHolder;
@Autowired
private MessageService messageService;
@Override
public 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);
}
}
}
拦截器配置
- 静态资源不过滤
registry.addInterceptor(messageInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
主页模板中动态渲染
<a class="nav-link position-relative" th:href="@{/letter/list}">消息<span class="badge badge-danger" th:text="${allUnreadCount!=0?allUnreadCount:''}">12</span></a>
测试结果
5.关于向tymleaf模板传空值出现bug的解决
问题出现原因
EL1008E: Property or field 'message' cannot be found on object of type 'java.util.HashMap'
-
当controller处理器中,关注、评论或点赞的map的值为空,传入到前端后,例如followNotice的值为空
th:if="${followNotice.message!=null}"
- 这样,是找不到map中的key:message,不能用tymleaf的EL表达式来简化get方法
解决方法1
-
因此,解决方法是,要修改为原生的get方法,才能判断message的值
th:if="${followNotice.get(message)!=null}"
解决方法2
-
或者是,修改java代码,followNotice没有值时,设定为null,通过判断followNotice来决定标签是否执行
//每次操作前,都先设定为null,如果没有值,就不实例化 Map<String, Object>messageVo = null if(message != null) { messageVo.put("message", message); }
th:if="${followNotice!=null}"