推送相关的总结

首先介绍下上图的情景:

当我们的前端请求后端子服务的时候,我们的后端子服务去处理相关的逻辑的时候,很有可能因为业务的复杂导致http请求连接超时,而且对于当前时代下,我们都要求高性能系统,用户提交一个请求后,隔了假设20s才响应了,显然也是不合理的。

传统的方式解决上述问题

前端在发送请求后,我们的子服务可以立即响应返回,但是返回的是请求成功而不是处理成功,然后在后端的业务逻辑处理完毕后,再返回给前端处理成功,这显然符合高性能系统的要求。但是也存在一个问题就是http请求在我们第一次响应后,后端没有能力再去通知前端,而且因为后端业务逻辑处理完成时间的不确定,传统的方式是前端轮询去调这个服务,这样子就会导致服务器的压力大大增加,显然上述的方式虽然能解决,但是我们并不采用。

推介方式解决上述问题

 如上图所示,我们建立一个推送服务(采用websocket协议,有的公司叫做消息中心只是名字的差异),当子服务没有能力将相应的消息推送给前端的时候,可以将推送的消息推送给消息中心,消息中心和前端保持长连接,这样就可以将消息推送给前端。(不同的前端用户我们可以根据用户id和对应的sessionId来建立一种映射关系)

Websocket是一种全双工的协议(服务器可以推送前端,前端也能推送给服务器),只是前端推送后端我们使用的http协议,也就是使用了一半websocket协议。

PS:Websocket协议相关介绍:HTML5 WebSocket | 菜鸟教程

下面使用Java语言在springboot工程中实现:

首先肯定是导入依赖:

<!-- websocket依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

建立后端:

①构建WebSocket

package com.qianfeng.gp09.server;

import com.alibaba.fastjson.JSONObject;
import com.qianfeng.gp09.dao.MessageDao;
import com.qianfeng.gp09.model.Message;
import com.qianfeng.gp09.model.MessageExample;
import com.qianfeng.gp09.util.SpringContextUtil;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * websocket服务器
 * 
 */
@ServerEndpoint("/webSocket/{uid}")
@Component
public class WebSocketServer {

    /**
     * 每一个用户都和服务器建立一个会话
     * 我们考虑保存会话
     * 一个uid对应一个session
     * 它应该是一个多线程并发情况下的map
     */
    private static ConcurrentHashMap<String,Session> sessionPool=new ConcurrentHashMap<>();

    @Resource
    private MessageDao messageDao;


    /**
     * 当建立连接的时候,就运行的方法。
     * @param session 会话对象
     * @param uid 用户id
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value="uid") String uid){
        sessionPool.put(uid,session);
        //查询uid的消息  state=0 且 overTime是大于当前时间。
        MessageExample example=new MessageExample();
        MessageExample.Criteria criteria = example.createCriteria();
        criteria.andUidEqualTo(uid);
        criteria.andStateEqualTo(0);
        criteria.andOverTimeGreaterThanOrEqualTo(new Date());
        if(messageDao==null){
            //手动获取这个对象。
            messageDao=SpringContextUtil.getBean(MessageDao.class);
        }
        List<Message> messages = messageDao.selectByExample(example);
        sendMessage(session, JSONObject.toJSONString(messages));
    }

    /**
     * 对方发送给我数据的时候,这个方法就运行。
     * @param message 消息内容
     */
    @OnMessage
    public void onMessage(String message){
        //不用。
    }

    /**
     * 当连接关闭时,这个方法就运行
     * @param uid 用户id
     */
    @OnClose
    public void onClose(@PathParam(value="uid")String uid){
        sessionPool.remove(uid);
    }


    /**
     * 当连接发生错误时,这个方法就运行
     * @param session 会话对象
     * @param throwable 错误
     */
    @OnError
    public void onError(Session session,Throwable throwable){
        throwable.printStackTrace();
    }


    /**
     * 根据session发送消息
     * @param session 与用户的会话
     * @param message 发送给用户的内容
     */
    private boolean sendMessage(Session session,String message){
        if(session!=null){
            synchronized (session){
                try{
                    //根据一个session,发送消息内容给对方。
                    session.getBasicRemote().sendText(message);
                    return true;
                }catch (Exception e){
                    e.printStackTrace();
                    return false;
                }
            }
        }
        return false;
    }


    /**
     * 指定id给用户发消息
     * 业务逻辑的实现
     * @param uid 用户id
     * @param message 发送给用户的消息内容
     * @return 发送是否成功
     */
    public boolean sendInfo(String uid,String message){
        Session session=sessionPool.get(uid);
        if(session==null){
            return false;
        }
        return sendMessage(session,message);
    }




}

②构建websockect需要配置如下:

package com.qianfeng.gp09.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 配置管理器
 * 
 */
@Configuration
public class WebSocketConfig {
    /**
     * 配置一个serverendpoint
     * 用于管理连接服务器的uri
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

③在controller中注入:

package com.qianfeng.gp09.controller;

import com.alibaba.fastjson.JSONObject;
import com.qianfeng.gp09.dto.MessageDTO;
import com.qianfeng.gp09.model.Message;
import com.qianfeng.gp09.model.User;
import com.qianfeng.gp09.result.R;
import com.qianfeng.gp09.server.WebSocketServer;
import com.qianfeng.gp09.service.PushService;
import com.qianfeng.gp09.service.UserService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;

/**
 * 用户的接口
 * 
 */
@RestController
@RequestMapping("qf/user")
public class UserController {


    @Resource
    private WebSocketServer webSocketServer;

    @Resource
    private PushService pushService;

  

    @PostMapping("pushWeb")
    public R pushWeb(@RequestBody MessageDTO messageDTO){
        String uid=messageDTO.getUid();
        String message=messageDTO.getMessage();
        Integer overTime=messageDTO.getOverTime();
        boolean flag = webSocketServer.sendInfo(uid, message);
        Message msg=new Message();
        msg.setUid(uid);
        msg.setContent(message);
        msg.setSendTime(new Date());
        msg.setOverTime(new Date(System.currentTimeMillis()+overTime*1000*60));
        if(flag){
            msg.setState(1);
            pushService.saveMessage(msg);
            return R.ok("发送成功");
        }else{
            msg.setState(0);
            pushService.saveMessage(msg);
            return R.ok("用户离线,推送任务已经设置。");
        }
    }


}

建立前端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <input type="text" id="uid" placeholder="请输入id"/>
    <br>
    <button onclick="onConnectionClick();">连接服务器</button>
    <br>
    <div id="messages"></div>

<script>
    let socket;
    function onConnectionClick(){
        if(typeof(WebSocket) == "undefined") {
            //此浏览器不支持WebSocket
            alert("请使用最新的Google Chrome浏览器");
            return ;
        }

        if(socket!=null){
            socket.close();
            socket=null;
        }
        //获取用户输入的id
        let uid=document.getElementById("uid").value;

        //连接服务器的url
        let url="ws://localhost:8080/webSocket/"+uid;


        //实例化webSocket
        socket=new WebSocket(url);

        socket.onopen=function(){
            console.info("已经与服务器建立连接");
        };
        socket.onmessage=function(msg){
            let receiveMessage=msg.data;
            document.getElementById("messages").innerHTML+="<span>"+receiveMessage+"</span><br>"

        };
        socket.onclose=function(){
            console.info("已经与服务器断开连接");
        };
        socket.onerror=function(){
            console.info("与服务器连接发生错误");
        };


    }


</script>
</body>
</html>

PS:在验证上述的逻辑中,遇到dao层注入时项目报空指针的问题,这是由于Spring在注入的时候,根据情况和注解,它注入的顺序不同,所以当时直接写了个工具类,可以直接使用注入,不需要关注顺序。工具类如下:

package com.qianfeng.gp09.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * spring上下文的工具类
 * 
 *
 */
@Component
public class SpringContextUtil implements ApplicationContextAware {
    /**
     * 它是spring的上下文对象,可以使用它获取spring容器中的任意对象
     */
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext=applicationContext;
    }



    public static <T> T getBean(Class<T> clazz){
        return applicationContext.getBean(clazz);
    }
}

上面还有种延伸出来的情况,详情如图,不做赘述:


手机端APP推送

 首先先描述下上述场景:

        当我们的一款手机App想要给手机进行消息的推送,我们使用到的是第三方的极光服务器或者是友盟服务器,解释下原因,如上图,如果不使用三方集成的服务器,针对不同的手机的服务器,我们的App都要分别去对接,并且需要专门的客服去保证这条线路的通畅,所以推介使用的是三方集成平台,由他们帮我们对接所有的手机厂商。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值