SpringBoot+LayIM+t-io 實(shí)現(xiàn)好友申請(qǐng)通知流程
前言
在上一篇 Spring boot + LayIM + t-io 文件上傳、 監(jiān)聽(tīng)用戶(hù)狀態(tài)的實(shí)現(xiàn) 中,已經(jīng)介紹了兩個(gè)小細(xì)節(jié):用戶(hù)的在離線狀態(tài)和群人數(shù)的狀態(tài)變化。今天的主要內(nèi)容就是用戶(hù)加好友的實(shí)現(xiàn)。
簡(jiǎn)介
加好友,大家用過(guò)QQ都知道,無(wú)非是發(fā)起好友申請(qǐng),對(duì)方收到消息通知,然后處理。不過(guò),本篇只講前半部分,消息通知的處理留到下一篇去講。因?yàn)閮?nèi)容有點(diǎn)多,怕是一時(shí)半會(huì)消化不了。在介紹主體流程之前,先給大家介紹一下準(zhǔn)備工作。
準(zhǔn)備工作
首先,為了讓數(shù)據(jù)更貼近實(shí)戰(zhàn),所以我用了比較“真實(shí)”的用戶(hù)數(shù)據(jù)。結(jié)合fly模板,完善了用戶(hù)中心頭部的用戶(hù)信息的數(shù)據(jù)綁定。數(shù)據(jù)綁定部分判斷了是否已經(jīng)是好友,來(lái)決定是否出現(xiàn)“加為好友”的按鈕。示例如下,當(dāng)用戶(hù)自己看到自己的主頁(yè)時(shí),是這樣的:
看到非好友的用戶(hù)主頁(yè),是這樣的:
綁定數(shù)據(jù)部分,簡(jiǎn)單給大家介紹一下,就是用thymleaf模板綁定。后臺(tái)訪問(wèn)頁(yè)面的時(shí)候,將 Model 賦值即可。
/** * 屬性賦值 * */ private void setModel(User user,Model model){ long currentUserId = getUserId(); long visitUserId = user.getId(); //是否是自己 boolean isSelf = currentUserId == visitUserId; //兩個(gè)用戶(hù)是否已經(jīng)是好友 boolean isFriend = groupService.isFriend(currentUserId,visitUserId); Map<String,Object> userMap = new HashMap<>(8); userMap.put("avatar",user.getAvatar()); userMap.put("name",user.getUserName()); userMap.put("addtime", TimeUtil.formatDate(user.getCreateAt())+" 加入"); if(user.getSign()==null ||user.getSign().length()==0) { userMap.put("sign", ""); }else { userMap.put("sign", "(" + user.getSign() + ")"); } userMap.put("uid",user.getId()); userMap.put("self",isSelf || isFriend); model.addAttribute("user",userMap); }
然后頁(yè)面上,將model中的數(shù)據(jù)取出來(lái)。
<div class="fly-home" style="background-image: url();"> <input type="hidden" th:value="${user.uid}" id="visitUid"/> <img src="" th:src="${user.avatar}" th:alt="${user.name}"/> <h1> <p th:text="${user.name}"></p> <i class="iconfont icon-nan"></i> </h1> <p class="fly-home-info"> <!--<i class="iconfont icon-zuichun" title="飛吻"></i><span style="color: #FF7200;">67206飛吻</span>--> <i class="iconfont icon-shijian"></i><span th:text="${user.addtime}"></span> <!--<i class="iconfont icon-chengshi"></i><span>來(lái)自杭州</span>--> <i class="iconfont icon-qq" th:if="${user.self==false}"></i><a lay-event="addFriend" href="#" rel="external nofollow" title="添加TA為好友" th:if="${user.self==false}">加為好友</a> </p> <p class="fly-home-sign" th:text="${user.sign}"></p> </div>
ok,以上就是簡(jiǎn)單的準(zhǔn)備工作。想了解詳情代碼的可以去文末的github地址去搜尋。
發(fā)起好友申請(qǐng)
我們先根據(jù)layim的業(yè)務(wù)分析。首先,要知道我們要加誰(shuí)(toId)為好友。然后在加上一個(gè)備注(remark)。這些東西交給后臺(tái)就OK了。為了避免連表查詢(xún),對(duì)于系統(tǒng)消息的存儲(chǔ)我做了用戶(hù)名和用戶(hù)頭像的冗余。表主要包含字段:用戶(hù)ID,用戶(hù)頭像,用戶(hù)名,被申請(qǐng)用戶(hù)ID,申請(qǐng)時(shí)間,申請(qǐng)類(lèi)型,備注,已讀等其他屬性。
所以,發(fā)起好友申請(qǐng)就很簡(jiǎn)單了。就是一個(gè)添加功能,前端傳的就是被申請(qǐng)人用戶(hù)ID和申請(qǐng)備注,后端組織數(shù)據(jù)插入到數(shù)據(jù)庫(kù),代碼如下:
/** * 提交好友申請(qǐng) * */ public JsonResult saveFriendApply(long toId,String remark){ remark = HtmlUtils.htmlEscape(remark); ContextUser user = ShiroUtil.getCurrentUser(); long userId = Long.parseLong(user.getUserid()); int record = applyRepository.countByToidAndUidAndTypeAndResult(toId,userId,ApplyType.friend,0); if(record > 0){ return JsonResult.fail("已經(jīng)申請(qǐng)過(guò)"); } Apply apply = new Apply(); apply.setType(ApplyType.friend); apply.setToid(toId); apply.setRemark(remark); apply.setUid(userId); apply.setAvatar(user.getAvatar()); apply.setName(user.getUsername()); apply.setRead(false); apply.setResult(0); return saveApply(apply); }
OK,申請(qǐng)完了,下面我們要做啥?沒(méi)錯(cuò),通知對(duì)方,喂,我向你發(fā)送了申請(qǐng),快快處理。在這里呢我遇到了一個(gè)問(wèn)題。由于springboot程序占用端口 8080,而t-io占用端口8888,也就是說(shuō),如果我想在8080端口的業(yè)務(wù)中主動(dòng)調(diào)用8888的服務(wù)推送,我不知道如何獲取相應(yīng)的channelContext。不過(guò)經(jīng)過(guò)詢(xún)問(wèn)作者之后,一句話解決了我的問(wèn)題。
拿到 ServerGroupContext ,問(wèn)題迎刃而解。
在之前的程序啟動(dòng)的時(shí)候注冊(cè)了 LayimWebsocketStarter 這個(gè)bean。所以,在8080業(yè)務(wù)端如果能拿到它的話就沒(méi)問(wèn)題了。
得到 LayimWebsocketStarter ,就能得到 ServerGroupContext, 然后就能在服務(wù)端做主動(dòng)推送了。
當(dāng)然可能沒(méi)有開(kāi)發(fā)過(guò)這個(gè)東西,對(duì)于上文中的問(wèn)題不是很理解,沒(méi)關(guān)系,其實(shí)我就想說(shuō)明,如果從服務(wù)端主動(dòng)向客戶(hù)端推送消息的話,使用ServerGroupContext即可。
服務(wù)端主動(dòng)推送
以下代碼在 com.fyp.layim.im.common.util.PushUtil 中
OK,接上文,我們按照步驟來(lái)。
第一步,獲取 LayimWebsocketStarter 。
/** * 獲取starter */ private static LayimWebsocketStarter getStarter(){ return (LayimWebsocketStarter)SpringUtil.getBean("layimWebsocketStarter"); }
第二步,獲取 ServerGroupContext
private static ServerGroupContext getServerGroupContext(){ return getStarter().getServerGroupContext(); }
第三步,獲取 ChannelContext。
/** * 獲取channelContext * */ private static ChannelContext getChannelContext(String toId) { ServerGroupContext context = getServerGroupContext(); //找到用戶(hù) ChannelContext channelContext = context.users.find(context, toId); return channelContext; }
第四步,發(fā)射,這里的代碼就和聊天中的那部分代碼差不多了。核心部分就是,獲取ChannelContext,然后給他發(fā)送消息。如果不在線就不用管。
/** * 服務(wù)端主動(dòng)推送消息 * */ public static void pushApplyMessage(String toId) { logger.info("執(zhí)行到了發(fā)送方法:pushApplyMessage"); LayimToClientNoticeMsgBody body = new LayimToClientNoticeMsgBody(); ChannelContext channelContext = getChannelContext(toId); //先判斷是否在線,再去查詢(xún)數(shù)據(jù)庫(kù),減少查詢(xún)次數(shù) if (channelContext != null && !channelContext.isClosed()) { int count = getApplyService().getUnreadMsgCount(Long.parseLong(toId)); body.setCount(count); push(channelContext, body); } } /** * 服務(wù)端主動(dòng)推送消息 * */ private static void push(ChannelContext channelContext,Object msg) { try { WsResponse response = BodyConvert.getInstance().convertToTextResponse(msg); Aio.send(channelContext, response); }catch (IOException ex){ } }
現(xiàn)在推送已經(jīng)搞定了,那么什么時(shí)候推送呢?由于這個(gè)系統(tǒng)消息的推送可以不用那么即時(shí),于是我看了下,springboot里面有類(lèi)似的事件機(jī)制,于是乎 ApplyEvent 就誕生了。
public class ApplyEvent extends ApplicationEvent { public ApplyEvent(Object source) { super(source); } private long toid; public long getToId(){ return toid; } public ApplyEvent(Object source, long toId) { super(source); this.toid = toId; } }
在創(chuàng)建一個(gè)Listener,監(jiān)聽(tīng)事件。
public class ApplyListener implements ApplicationListener<ApplyEvent> { private Logger logger = LoggerFactory.getLogger(ApplyListener.class); @Override public void onApplicationEvent(ApplyEvent applyEvent) { new Thread(){ public void run(){ Long toId = applyEvent.getToId(); //這里就要調(diào)用上文中的推送了 PushUtil.pushApplyMessage(toId.toString()); } }.start(); } }
不過(guò)我有個(gè)疑問(wèn),發(fā)現(xiàn)listener中執(zhí)行的時(shí)候是同步的。后來(lái)加了@Async 和@EnableAsync 也沒(méi)用,于是我就用了new Thread().start()實(shí)現(xiàn)異步,確保不影響主要申請(qǐng)流程。(這是個(gè)疑問(wèn),自己沒(méi)搞明白的地方)
最后,別忘了在Application啟動(dòng)的時(shí)候把listener加上。
public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(LayimApplication.class); /** * 這里監(jiān)聽(tīng)增加listener,listener才會(huì)觸發(fā) * ApplyListener 是監(jiān)聽(tīng)好友申請(qǐng)的事件 * */ springApplication.addListeners(new ApplyListener()); springApplication.run(args); }
功能拼接
馬上就要成功了,我們?cè)诎咽录饋?lái),在好友申請(qǐng)成功之后,發(fā)布事件。
/** * 好友申請(qǐng) * */ @PostMapping(value = "/apply-friend") public JsonResult apply(@RequestParam("toid") Long toId,@RequestParam("remark") String remark){ JsonResult result = applyService.saveFriendApply(toId, remark); //申請(qǐng)成功,發(fā)布申請(qǐng)事件,通知 toId處理消息,如果不在線,不會(huì)進(jìn)行處理 if(result.isSuccess()){ applicationContext.publishEvent(new ApplyEvent("apply",toId)); } return result; }
功能演示
講了那么多,給大家看一下成品效果。(用戶(hù)場(chǎng)景:安小鳥(niǎo)加皇上為好友,皇上接收消息并查看)
皇上收到消息,系統(tǒng)彈出左下角的小數(shù)字4。(調(diào)用 layim.msgbox(msgCount) 方法)
皇上點(diǎn)開(kāi)消息盒子:
皇上收到了四位愛(ài)妃的申請(qǐng),寢食難安,他會(huì)怎么處理呢?欲知后事如何,且聽(tīng)下回分解~~~
總結(jié)
本篇主要介紹了一個(gè)加好友的流程的實(shí)現(xiàn)。
- 好友申請(qǐng)按鈕出不出現(xiàn)取決于用戶(hù)是否為自己,是否已經(jīng)是好友。(后端也要做驗(yàn)證)
- t-io的服務(wù)端主動(dòng)推送,如何調(diào)用。關(guān)鍵詞: ServerGroupContext
- event的使用,除了applicationEvent,還可以拓展其他類(lèi)型,如消息隊(duì)列,eventbus等。
- 各種細(xì)節(jié)處理,比如先判斷對(duì)方是否在線,在去查詢(xún)數(shù)據(jù)庫(kù)?;蛘呓Y(jié)合緩存等
- 由于是自己摸索,難免有代碼繁雜混亂之處,
文中代碼地址: https://github.com/fanpan26/SpringBootLayIM
http://xiazai.jb51.net/201712/yuanma/SpringBootLayIM-master.rar
以上所述是小編給大家介紹的SpringBoot+LayIM+t-io 實(shí)現(xiàn)好友申請(qǐng)通知流程,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Spring?Boot?實(shí)現(xiàn)Redis分布式鎖原理
這篇文章主要介紹了Spring?Boot實(shí)現(xiàn)Redis分布式鎖原理,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)
本篇文章主要介紹了詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03Java8時(shí)間api之LocalDate/LocalDateTime的用法詳解
在項(xiàng)目中,時(shí)間的使用必不可少,而java8之前的時(shí)間api?Date和Calander等在使用上存在著很多問(wèn)題,于是,jdk1.8引進(jìn)了新的時(shí)間api-LocalDateTime,本文就來(lái)講講它的具體使用吧2023-05-05JAVA MyBatis入門(mén)學(xué)習(xí)過(guò)程記錄
MyBatis是一個(gè)支持普通SQL查詢(xún),存儲(chǔ)過(guò)程和高級(jí)映射的優(yōu)秀持久層框架。這篇文章主要介紹了mybatis框架入門(mén)學(xué)習(xí)教程,需要的朋友可以參考下,希望能幫助到你2021-06-06Spring中Controller和RestController的區(qū)別詳解
這篇文章主要介紹了Spring中Controller和RestController的區(qū)別詳解,@Controller是標(biāo)識(shí)一個(gè)Spring類(lèi)是Spring MVC controller處理器,@Controller類(lèi)中的方法可以直接通過(guò)返回String跳轉(zhuǎn)到j(luò)sp、ftl、html等模版頁(yè)面,需要的朋友可以參考下2023-09-09SpringBoot+Vue前后端分離實(shí)現(xiàn)審核功能的示例
在實(shí)際開(kāi)發(fā)中,審核功能是一個(gè)非常常用的功能,本文就來(lái)介紹一下使用SpringBoot+Vue前后端分離實(shí)現(xiàn)審核功能的示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02