站内信
一 需求描述
站内信是当前用户与所有用户间对话的一个功能。
站内信模块需实现三个功能:
1. 互相发送站内信(写出接口,没有单独页面,用@ResponseBody直接在Postman进行验证);
2. 显示当前用户的所有站内信(与多个用户间);
3. 显示当前用户与某个指定用户间的对话消息。
二 具体实现
实现流程:DB>Model>Dao>Service>Controller>HTML
1.DB创建表Message
1 DROP TABLE IF EXISTS `message`; 2 CREATE TABLE `message`( 3 `id` INT NOT NULL AUTO_INCREMENT, 4 `from_id` INT NOT NULL, 5 `to_id` INT NOT NULL , 6 `content` TEXT NOT NULL , 7 `created_date` DATETIME NOT NULL , 8 `has_read` INT NULL , 9 `conversation_id` VARCHAR(45) NOT NULL , 10 PRIMARY KEY (`id`), 11 INDEX `conversation_index` (`conversation_id` ASC), 12 INDEX `created_date` (`created_date` ASC) 13 ) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
2. Model:
package com.nowcoder.model; import org.springframework.stereotype.Component; import java.util.Date; /** * Created by Administrator on 2017/4/29. */ @Component public class Message { private int id; private int fromId; private int toId; private String content; private Date createdDate; private int hasRead; private String conversationId; public int getId() { return id; } public void setId(int id) { this.id = id; } public int getFromId() { return fromId; } public void setFromId(int fromId) { this.fromId = fromId; } public int getToId() { return toId; } public void setToId(int toId) { this.toId = toId; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } public int getHasRead() { return hasRead; } public void setHasRead(int hasRead) { this.hasRead = hasRead; } public String getConversationId() { return conversationId; } public void setConversationId(String conversationId) { this.conversationId = conversationId; } }
3. Dao:
int addMessage(Message message);发送站内信
List<Message> getConversationDetail(@Param("conversationId") String conversationId,
@Param("offset") int offset,
@Param("limit") int limit); : 获取与某个具体用户的站内信
List<Message> getConversationList(@Param("userId") int userId,
@Param("offset") int offset,
@Param("limit") int limit); : 获取站内信列表
int getConvesationUnreadCount(@Param("userId") int userId,
@Param("conversationId") String conversationId); :统计未读信息
package com.nowcoder.dao; import com.nowcoder.model.Message; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * Created by Administrator on 2017/4/29. */ @Mapper public interface MessageDao { String TABLE_NAME = "message"; String INSERT_FIELDS = "from_id, to_id, content, created_date, has_read, conversation_id"; String SELECT_FIELDS = "id," + INSERT_FIELDS; @Insert({"insert into", TABLE_NAME, "(", INSERT_FIELDS, ") " + " values(#{fromId}, #{toId}, #{content}, #{createdDate}, #{hasRead}, #{conversationId})"}) int addMessage(Message message); @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where conversation_id=#{conversationId} order by id desc limit #{offset}, #{limit}"}) List<Message> getConversationDetail(@Param("conversationId") String conversationId, @Param("offset") int offset, @Param("limit") int limit); @Select({"select", INSERT_FIELDS, " ,count(conversation_id) AS id from (select * from ",TABLE_NAME, " where from_id=#{userId} or to_id=#{userId} order by id desc) tt group by conversation_id " + " order by created_date limit #{offset}, #{limit}"}) List<Message> getConversationList(@Param("userId") int userId, @Param("offset") int offset, @Param("limit") int limit); /** * 获取当前用户与指定用户间(conversationId)的对应未读信息 * @param userId * @return */ @Select({"select count(has_read) from ", TABLE_NAME, " where has_read=0 and to_id=#{userId} and conversation_id=#{conversationId}"}) int getConvesationUnreadCount(@Param("userId") int userId, @Param("conversationId") String conversationId); }
4.Service:
package com.nowcoder.service; import com.nowcoder.dao.MessageDao; import com.nowcoder.model.Message; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * Created by Administrator on 2017/4/29. */ @Service public class MessageService { @Autowired MessageDao messageDao; /** * 用户之间发送信息 * @param message * @return */ public int addMessage(Message message){ return messageDao.addMessage(message); } public List<Message> getConversationDetail(String conversationId, int offset, int limit){ return messageDao.getConversationDetail(conversationId, offset, limit); } public List<Message> getConversationList(int userId, int offset, int limit){ return messageDao.getConversationList(userId, offset, limit); } /** * 获取当前用户与指定用户间(conversationId)的对应未读信息 * @param userId * @return */ public int getConvesationUnreadCount(int userId, String conversationId){ return messageDao.getConvesationUnreadCount(userId, conversationId); } }
5. Controller: MessageController
(1)发送信息:
/** * 发送信息 * 发送那一方的id + 收到信息的那一方 id = 会话conversationId * : 即a发送给b 和 b 发送给a 的消息在a,b之间的站内信中心都能看见 * @param fromId 发送那一方的id * @param toId 收到信息的那一方id * @param content * @return */ @RequestMapping(path = {"/msg/addMsg/"}, method = RequestMethod.POST) @ResponseBody public String addMessage(@RequestParam("fromId") int fromId, @RequestParam("toId") int toId, @RequestParam("content") String content){ try { Message message = new Message(); message.setFromId(fromId); message.setToId(toId); message.setContent(content); message.setCreatedDate(new Date()); message.setHasRead(0);// 0 代表未读 1 代表已读 message.setConversationId(fromId < toId ? String.format("%d_%d", fromId, toId) : String.format("%d_%d", toId, fromId)); messageService.addMessage(message); return ToutiaoUtil.getJSONString(0); }catch (Exception e){ logger.error("私信发送失败" + e.getMessage()); return ToutiaoUtil.getJSONString(1, "私信发送失败!"); } }
(2)获取当前用户站内信列表:
/** * 当前用户的所有站内信,包括与所有用户的信息 * @param model * @return */ @RequestMapping(path = {"/msg/list"}, method = {RequestMethod.GET}) public String conversationList(Model model){ try{ int localUserId = hostHolder.getUser().getId(); List<Message> conversationListList = messageService.getConversationList(localUserId, 0, 20); List<ViewObject> conversationVOs = new ArrayList<>(); for(Message msg : conversationListList){ ViewObject vo = new ViewObject(); vo.set("conversation", msg); //获取与当前用户对话的user信息 //如果是我方发送(msg.getFromId() == localUserId),则user.id为消息发送的toId //如果不是我方发送(即我方为接收方),则user.id 为消息发送的fromId int targetId = (msg.getFromId() == localUserId) ? msg.getToId() : msg.getFromId(); User user = userService.getUser(targetId); vo.set("user", user); int unread = messageService.getConvesationUnreadCount(localUserId, msg.getConversationId()); vo.set("unread", unread); conversationVOs.add(vo); } model.addAttribute("conversations", conversationVOs); }catch (Exception e){ logger.error("获取站内信列表失败! " + e.getMessage()); } return "letter"; }
(3)获取与指定用户间的站内信:
/** * 当前用户与指定用户间的对话消息; * @param model * @param conversationId 与指定用户对话的id * @return */ @RequestMapping(path = {"/msg/detail"}, method = {RequestMethod.GET}) public String conversationDetail(Model model, @RequestParam("conversationId") String conversationId){ try{ //当前用户与多个用户的所有的站内信 List<Message> conversationList = messageService.getConversationDetail(conversationId, 0 ,10); List<ViewObject> messages = new ArrayList<>(); for(Message msg : conversationList){ ViewObject vo = new ViewObject(); vo.set("message", msg); //获取当前用户收到信息方的user(即获取我方收到所有信息对应的用户) User user = userService.getUser(msg.getFromId()); if(user == null){ continue; } vo.set("headUrl", user.getHeadUrl()); vo.set("userId", user.getId()); messages.add(vo); } model.addAttribute("messages", messages); }catch (Exception e){ logger.error("获取与用户的对话消息失败 !" + e.getMessage()); } return "letterDetail"; }
Controller所有的code:
package com.nowcoder.controller; import com.nowcoder.model.HostHolder; import com.nowcoder.model.Message; import com.nowcoder.model.User; import com.nowcoder.model.ViewObject; import com.nowcoder.service.MessageService; import com.nowcoder.service.UserService; import com.nowcoder.util.ToutiaoUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * Created by Administrator on 2017/4/29. */ @Controller public class MessageController { private static final Logger logger = LoggerFactory.getLogger(MessageController.class); @Autowired MessageService messageService; @Autowired UserService userService; @Autowired HostHolder hostHolder; /** * 当前用户的所有站内信,包括与所有用户的信息 * @param model * @return */ @RequestMapping(path = {"/msg/list"}, method = {RequestMethod.GET}) public String conversationList(Model model){ try{ int localUserId = hostHolder.getUser().getId(); List<Message> conversationListList = messageService.getConversationList(localUserId, 0, 20); List<ViewObject> conversationVOs = new ArrayList<>(); for(Message msg : conversationListList){ ViewObject vo = new ViewObject(); vo.set("conversation", msg); //获取与当前用户对话的user信息 //如果是我方发送(msg.getFromId() == localUserId),则user.id为消息发送的toId //如果不是我方发送(即我方为接收方),则user.id 为消息发送的fromId int targetId = (msg.getFromId() == localUserId) ? msg.getToId() : msg.getFromId(); User user = userService.getUser(targetId); vo.set("user", user); int unread = messageService.getConvesationUnreadCount(localUserId, msg.getConversationId()); vo.set("unread", unread); conversationVOs.add(vo); } model.addAttribute("conversations", conversationVOs); }catch (Exception e){ logger.error("获取站内信列表失败! " + e.getMessage()); } return "letter"; } /** * 当前用户与指定用户间的对话消息; * @param model * @param conversationId 与指定用户对话的id * @return */ @RequestMapping(path = {"/msg/detail"}, method = {RequestMethod.GET}) public String conversationDetail(Model model, @RequestParam("conversationId") String conversationId){ try{ //当前用户与多个用户的所有的站内信 List<Message> conversationList = messageService.getConversationDetail(conversationId, 0 ,10); List<ViewObject> messages = new ArrayList<>(); for(Message msg : conversationList){ ViewObject vo = new ViewObject(); vo.set("message", msg); //获取当前用户收到信息方的user(即获取我方收到所有信息对应的用户) User user = userService.getUser(msg.getFromId()); if(user == null){ continue; } vo.set("headUrl", user.getHeadUrl()); vo.set("userId", user.getId()); messages.add(vo); } model.addAttribute("messages", messages); }catch (Exception e){ logger.error("获取与用户的对话消息失败 !" + e.getMessage()); } return "letterDetail"; } /** * 发送信息 * 发送那一方的id + 收到信息的那一方 id = 会话conversationId * : 即a发送给b 和 b 发送给a 的消息在a,b之间的站内信中心都能看见 * @param fromId 发送那一方的id * @param toId 收到信息的那一方id * @param content * @return */ @RequestMapping(path = {"/msg/addMsg/"}, method = RequestMethod.POST) @ResponseBody public String addMessage(@RequestParam("fromId") int fromId, @RequestParam("toId") int toId, @RequestParam("content") String content){ try { Message message = new Message(); message.setFromId(fromId); message.setToId(toId); message.setContent(content); message.setCreatedDate(new Date()); message.setHasRead(0);// 0 代表未读 1 代表已读 message.setConversationId(fromId < toId ? String.format("%d_%d", fromId, toId) : String.format("%d_%d", toId, fromId)); messageService.addMessage(message); return ToutiaoUtil.getJSONString(0); }catch (Exception e){ logger.error("私信发送失败" + e.getMessage()); return ToutiaoUtil.getJSONString(1, "私信发送失败!"); } } }
对应HTML:
所有站内信letter.html:
#parse("header.html") <div id="main"> <div class="container"> <ul class="letter-list"> #foreach($conversation in $conversations) <li id="conversation-item-10005_622873"> <a class="letter-link" href="/msg/detail?conversationId=$!{conversation.conversation.conversationId}"></a> <div class="letter-info"> <span class="l-time">$!date.format('yyyy-MM-dd HH:mm:ss', $!{conversation.conversation.createdDate})</span> <div class="l-operate-bar"> <a href="javascript:void(0);" class="sns-action-del" data-id="10005_622873"> 删除 </a> <a href="/msg/detail?conversationId=$!{conversation.conversation.conversationId}"> 共$!{conversation.conversation.id}条会话 </a> </div> </div> <div class="chat-headbox"> <span class="msg-num"> $!{conversation.unread} </span> <a class="list-head" href="/user/$!{conversation.user.id}"> <img alt="头像" src="$!{conversation.user.headUrl}"> </a> </div> <div class="letter-detail"> <a title="$!{conversation.user.name}" class="letter-name level-color-1"> $!{conversation.user.name} </a> <p class="letter-brief"> <a href="/msg/detail?conversationId=$!{conversation.conversation.conversationId}"> $!{conversation.conversation.content} </a> </p> </div> </li> #end </ul> </div> <script type="text/javascript"> $(function(){ // If really is weixin $(document).on('WeixinJSBridgeReady', function() { $('.weixin-qrcode-dropdown').show(); var options = { "img_url": "", "link": "http://nowcoder.com/j/wt2rwy", "desc": "", "title": "读《Web 全栈工程师的自我修养》" }; WeixinJSBridge.on('menu:share:appmessage', function (argv){ WeixinJSBridge.invoke('sendAppMessage', options, function (res) { // _report('send_msg', res.err_msg) }); }); WeixinJSBridge.on('menu:share:timeline', function (argv) { WeixinJSBridge.invoke('shareTimeline', options, function (res) { // _report('send_msg', res.err_msg) }); }); // $(window).on('touchmove scroll', function() { // if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) { // $('div.backdrop').show(); // $('div.share-help').show(); // } else { // $('div.backdrop').hide(); // $('div.share-help').hide(); // } // }); }); }) </script> </div> #parse("footer.html")
与某个用户的站内信letterDetail.html:
#parse("header.html") <div id="main"> <div class="container"> <ul class="letter-chatlist"> #foreach($msg in $messages) <li id="msg-item-4009580"> <a class="list-head" href="/user/$!{msg.userId}"> <img alt="头像" src="$!{msg.headUrl}"> </a> <div class="tooltip fade right in"> <div class="tooltip-arrow"></div> <div class="tooltip-inner letter-chat clearfix"> <div class="letter-info"> <p class="letter-time">$date.format('yyyy-MM-dd HH:mm:ss', $!{msg.message.createdDate})</p> <a href="javascript:void(0);" id="del-link" name="4009580">删除</a> </div> <p class="chat-content"> $!{msg.message.content} </p> </div> </div> </li> #end </ul> </div> <script type="text/javascript"> $(function(){ // If really is weixin $(document).on('WeixinJSBridgeReady', function() { $('.weixin-qrcode-dropdown').show(); var options = { "img_url": "", "link": "http://nowcoder.com/j/wt2rwy", "desc": "", "title": "读《Web 全栈工程师的自我修养》" }; WeixinJSBridge.on('menu:share:appmessage', function (argv){ WeixinJSBridge.invoke('sendAppMessage', options, function (res) { // _report('send_msg', res.err_msg) }); }); WeixinJSBridge.on('menu:share:timeline', function (argv) { WeixinJSBridge.invoke('shareTimeline', options, function (res) { // _report('send_msg', res.err_msg) }); }); // $(window).on('touchmove scroll', function() { // if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) { // $('div.backdrop').show(); // $('div.share-help').show(); // } else { // $('div.backdrop').hide(); // $('div.share-help').hide(); // } // }); }); }) </script> </div> #parse("footer.html")
效果图:
当前用户所有的站内信列表:
与某个具体用户的站内信: