文档章节

Spring boot + LayIM + t-io 好友申请通知的实现

丶Pz
 丶Pz
发布于 2017/12/07 16:27
字数 1952
阅读 3520
收藏 143

前言

    在上一篇 Spring boot + LayIM + t-io 文件上传、 监听用户状态的实现 中,已经介绍了两个小细节:用户的在离线状态和群人数的状态变化。今天的主要内容就是用户加好友的实现。

简介

    加好友,大家用过QQ都知道,无非是发起好友申请,对方收到消息通知,然后处理。不过,本篇只讲前半部分,消息通知的处理留到下一篇去讲。因为内容有点多,怕是一时半会消化不了。在介绍主体流程之前,先给大家介绍一下准备工作。

准备工作

    首先,为了让数据更贴近实战,所以我用了比较“真实”的用户数据。结合fly模板,完善了用户中心头部的用户信息的数据绑定。数据绑定部分判断了是否已经是好友,来决定是否出现“加为好友”的按钮。示例如下,当用户自己看到自己的主页时,是这样的:

    看到非好友的用户主页,是这样的:

    绑定数据部分,简单给大家介绍一下,就是用thymleaf模板绑定。后台访问页面的时候,将 Model 赋值即可。

 /**
     * 属性赋值
     * */
    private void setModel(User user,Model model){
        long currentUserId = getUserId();
        long visitUserId = user.getId();
        //是否是自己
        boolean isSelf = currentUserId == visitUserId;
        //两个用户是否已经是好友
        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);
    }

    然后页面上,将model中的数据取出来。

 <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>来自杭州</span>-->
            <i class="iconfont icon-qq" th:if="${user.self==false}"></i><a lay-event="addFriend" href="#" title="添加TA为好友" th:if="${user.self==false}">加为好友</a>
        </p>
        <p class="fly-home-sign" th:text="${user.sign}"></p>
    </div>

     ok,以上就是简单的准备工作。想了解详情代码的可以去文末的github地址去搜寻。

发起好友申请

    我们先根据layim的业务分析。首先,要知道我们要加谁(toId)为好友。然后在加上一个备注(remark)。这些东西交给后台就OK了。为了避免连表查询,对于系统消息的存储我做了用户名和用户头像的冗余。表主要包含字段:用户ID,用户头像,用户名,被申请用户ID,申请时间,申请类型,备注,已读等其他属性。

    所以,发起好友申请就很简单了。就是一个添加功能,前端传的就是被申请人用户ID和申请备注,后端组织数据插入到数据库,代码如下:

    /**
     * 提交好友申请
     * */
    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("已经申请过");
        }

        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,申请完了,下面我们要做啥?没错,通知对方,喂,我向你发送了申请,快快处理。在这里呢我遇到了一个问题。由于springboot程序占用端口 8080,而t-io占用端口8888,也就是说,如果我想在8080端口的业务中主动调用8888的服务推送,我不知道如何获取相应的channelContext。不过经过询问作者之后,一句话解决了我的问题。

    拿到ServerGroupContext,问题迎刃而解。

    在之前的程序启动的时候注册了 LayimWebsocketStarter 这个bean。所以,在8080业务端如果能拿到它的话就没问题了。

    得到 LayimWebsocketStarter ,就能得到 ServerGroupContext,然后就能在服务端做主动推送了。

    当然可能没有开发过这个东西,对于上文中的问题不是很理解,没关系,其实我就想说明,如果从服务端主动向客户端推送消息的话,使用ServerGroupContext即可。

服务端主动推送

    以下代码在 com.fyp.layim.im.common.util.PushUtil

    OK,接上文,我们按照步骤来。

    第一步,获取 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();
        //找到用户
        ChannelContext channelContext = context.users.find(context, toId);
        return channelContext;
    }

    第四步,发射,这里的代码就和聊天中的那部分代码差不多了。核心部分就是,获取ChannelContext,然后给他发送消息。如果不在线就不用管。

/**
     * 服务端主动推送消息
     * */
    public static void pushApplyMessage(String toId) {
        logger.info("执行到了发送方法:pushApplyMessage");
        LayimToClientNoticeMsgBody body = new LayimToClientNoticeMsgBody();
        ChannelContext channelContext = getChannelContext(toId);
        //先判断是否在线,再去查询数据库,减少查询次数
        if (channelContext != null && !channelContext.isClosed()) {
            int count = getApplyService().getUnreadMsgCount(Long.parseLong(toId));
            body.setCount(count);

            push(channelContext, body);
        }
    }   


/**
     * 服务端主动推送消息
     * */
    private static void push(ChannelContext channelContext,Object msg) {
        try {
            WsResponse response = BodyConvert.getInstance().convertToTextResponse(msg);
            Aio.send(channelContext, response);
        }catch (IOException ex){

        }
    }

    现在推送已经搞定了,那么什么时候推送呢?由于这个系统消息的推送可以不用那么即时,于是我看了下,springboot里面有类似的事件机制,于是乎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;
    }
}

    在创建一个Listener,监听事件。

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();
                //这里就要调用上文中的推送了
                PushUtil.pushApplyMessage(toId.toString());
            }
        }.start();

    }
}

    不过我有个疑问,发现listener中执行的时候是同步的。后来加了 @Async 和@EnableAsync 也没用,于是我就用了new Thread().start()实现异步,确保不影响主要申请流程。(这是个疑问,自己没搞明白的地方)

    最后,别忘了在Application启动的时候把listener加上。

 public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(LayimApplication.class);

        /**
         * 这里监听增加listener,listener才会触发
         * ApplyListener 是监听好友申请的事件
         * */
        springApplication.addListeners(new ApplyListener());

        springApplication.run(args);
    }

功能拼接

    马上就要成功了,我们在把事件串起来,在好友申请成功之后,发布事件。

 /**
     * 好友申请
     * */
    @PostMapping(value = "/apply-friend")
    public JsonResult apply(@RequestParam("toid") Long toId,@RequestParam("remark") String remark){

        JsonResult result = applyService.saveFriendApply(toId, remark);
        //申请成功,发布申请事件,通知 toId处理消息,如果不在线,不会进行处理
        if(result.isSuccess()){
            applicationContext.publishEvent(new ApplyEvent("apply",toId));
        }
        return result;
    }

功能演示

    讲了那么多,给大家看一下成品效果。(用户场景:安小鸟加皇上为好友,皇上接收消息并查看)

    

    皇上收到消息,系统弹出左下角的小数字4。(调用 layim.msgbox(msgCount) 方法)

    

    皇上点开消息盒子:

    

    皇上收到了四位爱妃的申请,寝食难安,他会怎么处理呢?欲知后事如何,且听下回分解~~~

总结

    本篇主要介绍了一个加好友的流程的实现。

  1. 好友申请按钮出不出现取决于用户是否为自己,是否已经是好友。(后端也要做验证)
  2. t-io的服务端主动推送,如何调用。关键词:ServerGroupContext
  3. event的使用,除了applicationEvent,还可以拓展其他类型,如消息队列,eventbus等。
  4. 各种细节处理,比如先判断对方是否在线,在去查询数据库。或者结合缓存等
  5. 由于是自己摸索,难免有代码繁杂混乱之处,

文中代码地址:https://github.com/fanpan26/SpringBootLayIM

© 著作权归作者所有

共有 人打赏支持
丶Pz
粉丝 66
博文 9
码字总数 15231
作品 0
程序员
私信 提问
加载中

评论(22)

我就是付新宇

引用来自“talent-tan”的评论

t-io内置的推送API是不是很方便?
???
我就是付新宇
不错
开源中国首席罗纳尔多
开源中国首席罗纳尔多

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

你是买了layim吗?

@开源中国首席卡牌中单 是的
那你放上去网上别人不就可以拿你买了的layim吗?

@开源中国首席卡牌中单 可是我并没有放到网上

@丶Pz 把网站发布上去不就是发在网上了吗?

@开源中国首席卡牌中单 不好意思,我说的不太明白,是layim.js不会上传到guthub的,ignore了

回复@丶Pz : 哦,我也想买一个,但是如果我做了个网站放在网上给别人看,那别人不是会可以偷我的layim.js吗?

@开源中国首席卡牌中单 其实,就算你去layim官网一点一点扣,也能吧代码扣下来,偷是防不住的

回复@丶Pz : 不能做一个js注册吗?就像extjs也是有收费版的,他们怎么防止的
丶Pz
丶Pz

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

你是买了layim吗?

@开源中国首席卡牌中单 是的
那你放上去网上别人不就可以拿你买了的layim吗?

@开源中国首席卡牌中单 可是我并没有放到网上

@丶Pz 把网站发布上去不就是发在网上了吗?

@开源中国首席卡牌中单 不好意思,我说的不太明白,是layim.js不会上传到guthub的,ignore了

回复@丶Pz : 哦,我也想买一个,但是如果我做了个网站放在网上给别人看,那别人不是会可以偷我的layim.js吗?

@开源中国首席卡牌中单 其实,就算你去layim官网一点一点扣,也能吧代码扣下来,偷是防不住的
开源中国首席罗纳尔多
开源中国首席罗纳尔多

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

你是买了layim吗?

@开源中国首席卡牌中单 是的
那你放上去网上别人不就可以拿你买了的layim吗?

@开源中国首席卡牌中单 可是我并没有放到网上

@丶Pz 把网站发布上去不就是发在网上了吗?

@开源中国首席卡牌中单 不好意思,我说的不太明白,是layim.js不会上传到guthub的,ignore了

回复@丶Pz : 哦,我也想买一个,但是如果我做了个网站放在网上给别人看,那别人不是会可以偷我的layim.js吗?
丶Pz
丶Pz

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

你是买了layim吗?

@开源中国首席卡牌中单 是的
那你放上去网上别人不就可以拿你买了的layim吗?

@开源中国首席卡牌中单 可是我并没有放到网上

@丶Pz 把网站发布上去不就是发在网上了吗?

@开源中国首席卡牌中单 不好意思,我说的不太明白,是layim.js不会上传到guthub的,ignore了
开源中国首席罗纳尔多
开源中国首席罗纳尔多

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

你是买了layim吗?

@开源中国首席卡牌中单 是的
那你放上去网上别人不就可以拿你买了的layim吗?

@开源中国首席卡牌中单 可是我并没有放到网上

@丶Pz 把网站发布上去不就是发在网上了吗?
丶Pz
丶Pz

引用来自“开源中国首席卡牌中单”的评论

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

你是买了layim吗?

@开源中国首席卡牌中单 是的
那你放上去网上别人不就可以拿你买了的layim吗?

@开源中国首席卡牌中单 可是我并没有放到网上
开源中国首席罗纳尔多
开源中国首席罗纳尔多

引用来自“丶Pz”的评论

引用来自“开源中国首席卡牌中单”的评论

你是买了layim吗?

@开源中国首席卡牌中单 是的
那你放上去网上别人不就可以拿你买了的layim吗?
talent-tan
talent-tan

引用来自“老谭12345”的评论

用layim,跑你个demo还得花300买授权,呵呵哒,另外SimpleDateFormat静态的不怕出事?

引用来自“丶Pz”的评论

java初学者,本来这个项目就是自己练手的,嘿嘿。多谢指教
这实力完全不像个初学者
推荐一款插件layim.js 阿里大牛贤心制作的一款webim聊天插件

layim原本只是闲的蛋疼写的玩玩,长期被搁置未做升级,直至当前仍然出于半成品状态。但是鉴于越来越多人的关注,现决定抽个时间重新完整地写一套,然后正式开源出来,为大家所用,敬请关注。...

一一叶
2015/06/12
0
0
layim的websocket消息撤回功能实现

我的大概思路就是,前端根据选取的内容获得他的cid,我的cid是js生成的uuid, 然后:1、通过websocket广播给对应的人 去删除localstorage里的缓存, 2、ajax异步请求删除数据库里的数据记录 ...

jkxqj
2017/09/05
0
0
周记2——ios的日期格式bug

  转眼又到了周末,转眼又要上班,转眼...大概这就是一眼万年的意思吧。    这周继续IM(即时聊天),项目用的是LayIM移动端改装的,仅仅“借用”了一个聊天窗口。由于是内嵌App的页面,...

辛月
08/12
0
0
即时通讯项目 - LuliChat

LuliChat是一个以Nutz为后台支撑,T-io为通讯支持,LayIM为前台UI交互的纯国产框架开发的一个即时通讯项目。 没有高端的架构,没有难以理解的高层次封装,一切从简,只为实现功能,可乃新手学...

蛋蛋i
2017/12/20
399
1
宿命/tio-websocket-spring-boot-starter

tio-websocket-spring-boot-starter Intro spring-spring-boot-starter for tio-websocket Starter Repo Github Sample Gitee Sample T-io Repo Gitee Sample Sample Repo Github Sample Git......

宿命
11/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

用POLARDB构建客到智能餐饮系统实践

摘要: 在新零售成为大趋势的今天,餐饮行业也加入到这一浪潮之中。智能餐饮系统将帮助餐饮行业从多个维度提升自己的运营能力和收益,而打造智能餐饮系统SaaS化能力也成为了目前的一个热点。...

阿里云官方博客
16分钟前
0
0
aws S3 util demo

package com.example.demo;import com.amazonaws.AmazonClientException;import com.amazonaws.AmazonServiceException;import com.amazonaws.auth.BasicAWSCredentials;import co......

经常把天聊死的胖子
今天
4
0
linux下查看cpu、memo、io、swap性能数据脚本

直接贴脚本: 1、cpu #!/bin/bashCurrentDate=`date -d today '+%Y%m%d'`CurrentTime=`date -d today '+%Y%m%d%H%M'`mytext="$CurrentTime\t`top -b -n 1 | grep Cpu\(s\......

郑加威
今天
5
0
MySQL之——查询重复记录、删除重复记录方法大全

MySQL之——查询重复记录、删除重复记录方法大全

安小乐
今天
2
0
spring容器启动,停止,关闭事件监听-ApplicationEvent

ApplicationEvent ApplicationEvent相当于一个事件,所有自定义事件都需要继承这个抽象类。在Eclipse中Ctrl+Shift+H调用类的层次结构列表,可以看到如下 Application下抽象子类ApplicationCo...

tantexian
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部