文章目录
参考牛客网高级项目教程
功能需求及处理策略
- 1.点击消息,能够显示与当前用户所有的会话列表-分为朋友私信和系统私信两个模块,本功能显示前者
- 按照会话列表显示,每个会话列表显示模块一样
- 每个会话中,内容显示两个用户最新的一条私信
- 2.点击每个会话列表的内容,可以显示该会话中两个用户的所有私信列表
- 3.要能显示出未读消息
- 包括总的未读消息
- 和每个会话中,两个用户之间的未读私信
1. dao层处理会话私信数据
数据库设计
- from_id:一个会话中,私信发送者
- to_id: 一个会话中,私信接收者
- conversation_id:冗余字段:会话的id
- 是为了方便查询会话列表,由from_id和to_id组成
- 没有前后顺序,A私信给B,B私信给A,都属于一种会话
- 为表示方便,一般,小数字在前,大数字在后
- **status:**用此字段管理私信处理结果,已读、未读、删除
CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`from_id` int(11) DEFAULT NULL,
`to_id` int(11) DEFAULT NULL,
`conversation_id` varchar(45) NOT NULL,
`content` text,
`status` int(11) DEFAULT NULL COMMENT '0-未读;1-已读;2-删除;',
`create_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_from_id` (`from_id`),
KEY `index_to_id` (`to_id`),
KEY `index_conversation_id` (`conversation_id`)
) ENGINE=InnoDB AUTO_INCREMENT=355 DEFAULT CHARSET=utf8
接口方法声明,sql编写
接口方法
@Mapper
public interface MessageMapper {
/** 查询指定用户的会话列表 -内容只显示最新一条私信*/
List<Message> selectConversationList(int userId, int offset, int limit);
/** 查询指定用户会话总数 */
int selectConversationCount(int userId);
/** 查询指定会话的两个用户之间的私信列表 */
List<Message> selectLetterList(int conversationId, int offset, int limit);
/** 查询指定会话的两个用户之间的私信总数 */
int selectLetterCount(int conversationId);
/** 查询未读私信数量-动态条件查询-当前用户总的未读私信数量-当前会话的未读私信数量 */
int selectUnreadCount(int userId, int conversationId);
}
sql-子查询语句
组函数与分组查询
-
查询指定用户的会话列表 -内容只显示最新一条私信
-
先进行组查询,查询到每组会话conversationId中的所有消息中最新的一条消息
-
要进行过滤掉删除的私信、系统发来的私信
-
以conversationId分组,可以查到一组私信消息
-
对这组消息,取id最大,因为id是自增生成,最新的消息id在当前组中是最大的
SELECT max(id) from message where status != 2 and from_id != 1 and (from_id = 111 or to_id = 111) group by conversation_id;
-
根据分组结果进行子查询
-
再根据组查询的id结果,进行会话列表的条件子查询
-
1.查询指定用户的会话列表 -内容只显示最新一条私信
<!-- 定义复用代码块-->
<sql id="selectFields">
select id, from_id, to_id, conversation_id, content, status, create_time from message
</sql>
<sql id="insertFields">
insert into message(from_id, to_id, conversation_id, content, status, create_time)
values (#{fromId}, #{toId}, #{conversationId}, #{content}, #{status}, #{createTime})
</sql>
<!-- 查询指定用户的会话列表 -内容只显示最新一条私信-->
<select id="selectConversationList" resultType="message">
<include refid="selectFields"></include>
where id in(
select max(id) from message
where status != 2
and from_id != 1
and (from_id = #{userId} or to_id = #{userId})
group by conversation_id
)
order by id desc
limit #{offset}, #{limit}
</select>
- 2.查询指定用户会话总数
<!-- 查询指定用户会话总数-->
<select id="selectConversationCount" resultType="int">
select count(id) from message
where id in (
select max(id) from message
where status != 2
and from_id != 1
and (from_id = #{userId} or to_id = #{userId})
group by conversation_id
)
</select>
-
3.查询指定会话的两个用户之间的私信列表
<!-- 查询指定会话的两个用户之间的私信列表--> <select id="selectLetterList" resultType="message"> <include refid="selectFields"></include> where status != 2 and from_id != 1 and conversation_id = #{conversationId} order by id desc limit #{offset}, #{limit} </select>
-
4.查询指定会话的两个用户之间的私信总数
<!-- 查询指定会话的两个用户之间的私信总数--> <select id="selectLetterCount" resultType="int"> select count(id) from message where status != 2 and from_id != 1 and conversation_id = #{conversationId} </select>
-
5.询未读私信数量-动态条件查询-当前用户总的未读私信数量-当前会话的未读私信数量
<!-- 询未读私信数量-动态条件查询-当前用户总的未读私信数量-当前会话的未读私信数量--> <select id="selectUnreadCount" resultType="int"> select count(id) from message where status = 0 and from_id != 1 and to_id = #{userId} <if test="conversationId != null"> and conversation_id = #{conversationId} </if> </select>
测试
@Test
public void testSelectLetters() {
int i = 0;
List<Message> list = messageMapper.selectConversationList(111, 0, 20);
for(Message message : list) {
System.out.println((++i) + " : " + message);
}
System.out.println("111会话列表总数 " + messageMapper.selectConversationCount(111));
list = messageMapper.selectLetterList("111_112", 0, 10);
i = 0;
for(Message message : list) {
System.out.println((++i) + " : " + message);
}
System.out.println("111与112会话列表总数 " + messageMapper.selectLetterCount("111_112"));
System.out.println("111未读私信总数 " + messageMapper.selectUnreadCount(131, null));
System.out.println("111_131未读私信总数 " + messageMapper.selectUnreadCount(131, "111_131"));
}
}
部分打印内容:
111会话列表总数 14
111与112会话列表总数 8
111未读私信总数 2
111_131未读私信总数 2
2.service层业务处理
@Service
public class MessageService {
@Autowired
MessageMapper messageMapper;
// 查询会话列表
public List<Message> findConversations(int userId, int offset, int limit) {
return messageMapper.selectConversationList(userId, offset, limit);
}
// 查询会话总数
public int findConversationCount(int userId) {
return messageMapper.selectConversationCount(userId);
}
// 查询私信列表
public List<Message> findLetters(String conversationId, int offset, int limit) {
return messageMapper.selectLetterList(conversationId, offset, limit);
}
// 查询私信总数
public int findLetterCount(String conversationId) {
return messageMapper.selectLetterCount(conversationId);
}
// 查询未读私信总数
public int findLetterUnreadCount(int userId, String conversationId) {
return messageMapper.selectUnreadCount(userId, conversationId);
}
}
3. Controller层处理请求
访问会话列表的请求
分页设置
// 分页信息
page.setLimit(5);
page.setPath("/letter/list");
page.setRows(messageService.findConversationCount(user.getId()));
拿到每条会话列表信息-封装显示
// 会话列表
// 拿到每条会话,同样,与前面帖子,评论类似,将与用户等信息封装在一块,用到List,Map结构
List<Message> conversationList = messageService.findConversations(
user.getId(), page.getOffset(), page.getLimit());
List<Map<String, Object>> conversationVo = new ArrayList<>();
if(conversationList != null) {
for(Message message : conversationList) {
Map<String, Object> map = new HashMap<>();
map.put("conversation", message);
map.put("letterCount", messageService.findLetterCount(message.getConversationId()));
map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));
// 查询用户,注意,要查到会话列表中的用户,不是当前用户
int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId();
map.put("target", userService.findUserById(targetId));
conversationVo.add(map);
}
model.addAttribute("conversations", conversationVo);
整体代码如下:
/** 会话列表显示 */
@RequestMapping(path = "/letter/list", method = RequestMethod.GET)
public String getLetterList(Model model, Page page) {
User user = hostHolder.getUser();
// 分页信息
page.setLimit(5);
page.setPath("/letter/list");
page.setRows(messageService.findConversationCount(user.getId()));
// 会话列表
// 拿到每条会话,同样,与前面帖子,评论类似,将与用户等信息封装在一块,用到List,Map结构
List<Message> conversationList = messageService.findConversations(
user.getId(), page.getOffset(), page.getLimit());
List<Map<String, Object>> conversationVo = new ArrayList<>();
if(conversationList != null) {
for(Message message : conversationList) {
Map<String, Object> map = new HashMap<>();
map.put("conversation", message);
map.put("letterCount", messageService.findLetterCount(message.getConversationId()));
map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));
// 查询用户,注意,要查到会话列表中的用户,不是当前用户
int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId();
map.put("target", userService.findUserById(targetId));
conversationVo.add(map);
}
model.addAttribute("conversations", conversationVo);
// 查询所有未读消息数量
int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);
model.addAttribute("letterUnreadCount", letterUnreadCount);
}
return "/site/letter";
}
访问私信列表的请求
split("_")
- 返回分割后的字符串数组
/**每个会话中的所有私信显示 */
@RequestMapping(path = "/letter/detail/{conversationId}", method = RequestMethod.GET)
public String getLetterDetail(@PathVariable("conversationId") String conversationId, Page page, Model model) {
// 分页信息
page.setLimit(5);
page.setPath("/letter/detail/" + conversationId);
page.setRows(messageService.findLetterCount(conversationId));
// 私信列表
List<Message> letterList = messageService.findLetters(conversationId,page.getOffset(), page.getLimit());
List<Map<String, Object>> letterVo = new ArrayList<>();
if(letterList != null) {
// 拿到二级私信列表信息
for(Message letter : letterList) {
Map<String, Object> map = new HashMap<>();
map.put("letter", letter);
map.put("fromUser", userService.findUserById(letter.getFromId()));
letterVo.add(map);
}
model.addAttribute("letters", letterVo);
model.addAttribute("letterCount", messageService.findLetterCount(conversationId));
// 私信目标,已经遍历完了,可以从之前遍历的里面取一条,如之前操作那样,也可以在遍历外从conversationId中获取
model.addAttribute("target", getLetterTarget(conversationId));
}
return "site/letter-detail";
}
private User getLetterTarget(String conversationId) {
String[] ids = conversationId.split("_");
int id0 = Integer.parseInt(ids[0]);
int id1 = Integer.parseInt(ids[1]);
if(hostHolder.getUser().getId() == id0) {
return userService.findUserById(id1);
} else {
return userService.findUserById(id0);
}
}
4. View视图模板处理
首页-动态链接
- 消息数目,由于index请求处理中没有查询私信列表,故,先不处理
<a class="nav-link position-relative" th:href="@{/letter/list}">消息<span class="badge badge-danger">12</span></a>
每个用户的所有私信会话列表
<!-- 选项 -->
<ul class="nav nav-tabs mb-3">
<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" href="notice.html">系统通知<span class="badge badge-danger">27</span></a>
</li>
</ul>
<!-- 私信会话列表 -->
<ul class="list-unstyled">
<li class="media pb-3 pt-3 mb-3 border-bottom position-relative" th:each="map:${conversations}">
<span class="badge badge-danger" th:text="${map.unreadCount}" th:if="${map.unreadCount!=0}">3</span>
<a href="profile.html">
<img th:src="${map.target.headerUrl}" class="mr-4 rounded-circle user-header" alt="用户头像" >
</a>
<div class="media-body">
<h6 class="mt-0 mb-3">
<span class="text-success" th:utext="${map.target.username}">落基山脉下的闲人</span>
<span class="float-right text-muted font-size-12" th:text="${#dates.format(map.conversation.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-28 14:13:25</span>
</h6>
<div>
<a th:href="@{|/letter/detail/${map.conversation.conversationId}|}" th:utext="${map.conversation.content}">米粉车, 你来吧!</a>
<ul class="d-inline font-size-12 float-right">
<li class="d-inline ml-2"><a href="#" class="text-primary">共<i th:text="${map.letterCount}">5</i>条会话</a></li>
</ul>
</div>
</div>
</li>
</ul>
每个会话的私信列表
定义返回的js函数
<div class="col-8">
<h6><b class="square"></b> 与 <i class="text-success" th:utext="${target.username}">落基山脉下的闲人</i>的<i th:text="${letterCount}">1</i>条私信</h6>
</div>
<div class="col-4 text-right">
<button type="button" class="btn btn-secondary btn-sm" onclick="back()">返回</button>
<button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#sendModal">给TA私信</button>
</div>
<script>
function back() {
location.href = CONTEXT_PATH + "/letter/list";
}
</script>
私信列表
<li class="media pb-3 pt-3 mb-2" th:each="map:${letters}">
<a href="profile.html">
<img th:src="${map.fromUser.headerUrl}" class="mr-4 rounded-circle user-header" alt="用户头像" ></a>
<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.letter.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" th:utext="${map.letter.content}">
君不见, 黄河之水天上来, 奔流到海不复回!
</div>
</div>
</li>