基于ssm+freemaker+WebSocket实现的购物商城抢单功能

业务流程描述

1. websocket-api.jar包引入

tomcat安装目录lib下的websocket-api.jar包
这里写图片描述

2. web.xml配置

在web.xml配置一个监听,作用为定时清除抢单缓存信息。

 <listener>  
      <listener-class>com.chw.webSocket.QdWebSocketListener</listener-class>  
 </listener>  

3. 线程类定义

public class QdClearMapThread extends Thread {
      @Override
      public void run() {  
        while (!this.isInterrupted()) {// 线程未中断执行循环  
            try {  
                //每隔1分钟执行一次  
                Thread.sleep(1000*60);
                if(!QdWebSocketUtil.webSocketMap.isEmpty()){
                    try {
                        QdWebSocketUtil.onMessage("errorQd", null);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //清除客户端webSocket缓存信息
                    QdWebSocketUtil.webSocketMap.clear();
                }
            } catch (InterruptedException  e) {  
                e.printStackTrace();  
                //错误日志打印
                ChwLogUtil.getTradeLogger().info(System.currentTimeMillis()+"删除webSocketMap失败!");  
            }  

        }  
    }
}

4. 监听类定义

主要监听线程类,规定时间间隔(1分钟)执行线程的run()方法

public class QdWebSocketListener implements ServletContextListener {
    private QdClearMapThread thread;  
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
         if (thread != null && thread.isInterrupted()) {  
             thread.interrupt();  
            } 
    }
    @Override
    public void contextInitialized(ServletContextEvent arg0) {
         String str = null;  
            if (str == null && thread == null) {  
                thread = new QdClearMapThread();  
                thread.start(); 
            }  
    }

}

5. 买家发布抢单(jsp页面省略,只显示基本js内容)

<script type="text/javascript">
        var websocket = null;
        //判断当前浏览器是否支持WebSocket
        if ('WebSocket' in window) {
            websocket = new WebSocket('${websocketUrl}');
        }else {
            alert('当前浏览器 Not support websocket')
        }   

        //接收到消息的回调方法
        websocket.onmessage = function (event) {
            var data = event.data;
            if(data.indexOf("无商家抢单")!=-1){
                 alert(event.data); 
            }else if(data.indexOf("有商家抢单")!=-1){
                 alert(event.data); 
                 window.location.href="${context_path}/lpt/qd/payQd";
            }
        }
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function () {
           closeWebSocket();
        }
        //关闭WebSocket连接
        function closeWebSocket() {
            websocket.close();
        }
        //发送消息
        function send() {
            //确定抢单,封装数据(运费、服务费、地址、到达时间)
            var message  = submitQd();
            //校验是否重复提交
            var url = encodeURI(encodeURI("${context_path}/lpt/qd/checkSendMessage/"+message));
            $.ajax({
                type:"get",
                url:url,
                cache: false,
                success : function(msg){
                    if(msg.type == "success"){
                        websocket.send(message);//调用websocket的send()方法发送信息
                        alert(msg.content);
                    } else {
                        alert(msg.content);
                    }
                }
            });   
        }
    </script>

6. 买家发布逻辑控制

从页面接收到的抢单信息会存在全局变量中。

public static Map<Session,String> webSocketMap = new HashMap<Session,String>();
  • 如新发布的抢单信息存在于webSocketMap中,返回’该抢单已发布,请等待商家抢单‘。
  • 如买家所发布过的抢单信息在时间(在监听中配置)内无卖家抢单,线程会清除webSocketMap全部信息并返回’您发布的抢单无商家抢单,请回购物车重新发布抢单‘。
  • 如买家所发布过的抢单信息在时间(在监听中配置)有卖家抢单,线程会清除webSocketMap中已被卖家抢中的订单信息并返回’您发布的抢单无商家抢单,请回购物车重新发布抢单‘。
  • 如校验通过,抢单信息会在卖家端的抢单列表中显示该信息。
@RequestMapping(value="/checkSendMessage/{message}",method = RequestMethod.GET)
@ResponseBody
public Message checkSendMessage(@PathVariable("message")String message) throws UnsupportedEncodingException{
    String sendInfo = java.net.URLDecoder.decode(message, "utf-8");
    if(QdWebSocketUtil.webSocketMap.size() > 0){
        for(Session key:QdWebSocketUtil.webSocketMap.keySet()){
            if(sendInfo.equals(QdWebSocketUtil.webSocketMap.get(key))){
                return Message.error("该抢单已发布,请等待商家抢单!");
            }
        }
    }
    return Message.success("发布抢单成功!");
}
@OnMessage
public static void onMessage(String message, Session session) throws IOException {
    System.out.println("来自客户端的消息session="+session+ message);
    for(QdWebSocketUtil item: webSocketSet){
        if(null !=session){//群发
            webSocketMap.put(session, message);
            for(Session key:webSocketMap.keySet()){
                item.sendMessage(webSocketMap.get(key));
            }
        }else if(null ==session && "errorQd".equals(message)){//返回客户端提示信息--抢单失败
            for(Session key:webSocketMap.keySet()){
                if(item.session.getId().equals(key.getId())){
                    item.sendMessage("您发布的抢单无商家抢单,请回购物车重新发布抢单!");
                }
            }
        }else if(null ==session && "successQd".equals(message.split(";")[0].toString())){//返回客户端提示信息--抢单成功
            for(Session key:webSocketMap.keySet()){
                if(webSocketMap.get(key).contains(message.split(";")[1].toString())){
                    item.sendMessage("您发布的抢单已有商家抢单,请及时查看!");
                    webSocketMap.remove(key);
                }
            }
        }
    }
}

7. 卖家抢单页面

  • 查看页面省略。
  • 抢单页面js(在抢单列表中可查看当前买家发布的抢单内容)。
 var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
    websocket = new WebSocket('${websocketUrl}');
}else {
    alert('当前浏览器 Not support websocket')
}

//接收到消息的回调方法
websocket.onmessage = function (event) {
    $.ajax({
        type:"get",
        url:$("form").attr("action"),
        cache: false,
        success : function(message){
            window.location.reload();
        }
    });
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
    closeWebSocket();
}
//关闭WebSocket连接
function closeWebSocket() {
    websocket.close();
}

//抢单
function grabSingle(infoStr){
    $.ajax({
        type:"get",
        url:"${context_path}/lpt/qd/grabSingle/"+encodeURI(encodeURI(infoStr)),
        cache: false,
        success : function(message){
            if(message.type == "success"){
                alert(message.content);
            } else {
                alert(message.content);
            }
            window.location.reload();
        }

    });
}

8. 卖家逻辑控制

抢单信息校验
- 如webSocketMap中为空,返回’对不起!该订单已失效’。
- 如webSocketMap中不为空,但根据session获取不到信息,返回’对不起!该订单已被其他商家抢单’。
商品校验
- ‘对不起!您的店铺内没有【”+buyDpspInfo.getSpmc()+”】商品,不能抢单’。
- ‘对不起!您的店铺内商品【”+buyDpspInfo.getSpmc()+”】库存不满足要求,不能抢单’.
- ‘对不起!您的店铺内没有商品,不能抢单’。

@RequestMapping(value="/grabSingle/{infoStr}",method = RequestMethod.GET)
    @ResponseBody
    public Message grabSingle(@PathVariable("infoStr")String infoStr,HttpServletRequest request) throws IOException{
        String info = java.net.URLDecoder.decode(infoStr, "utf-8");
        //获取缓存信息判断抢单是否已被其他商家抢单
        if(QdWebSocketUtil.webSocketMap.isEmpty()){
            return Message.error("对不起!该订单已失效");
        }else{
            for(Session key:QdWebSocketUtil.webSocketMap.keySet()){
                if(!QdWebSocketUtil.webSocketMap.get(key).equals(info)){
                    return Message.error("对不起!该订单已被其他商家抢单");
                }
            }
        }
        //买家购物车
        List<LptDpSp> buyDpspInfos = new ArrayList<LptDpSp>();
        String[] allArray = info.split(";");//全部信息
        for(String str:allArray){
            if(str.split(",").length == 3){
                LptDpSp dpsp = new LptDpSp();
                LptGwcMx gwcmx = lptGwcService.getGwcmxById(str.split(",")[0]);
                LptDpSp buyDpspInfo = lptDpSpService.findById(gwcmx.getDpspid());
                dpsp.setKcsl(gwcmx.getSl());
                dpsp.setSpid(buyDpspInfo.getSpid());
                dpsp.setSpmc(buyDpspInfo.getSpmc());
                buyDpspInfos.add(dpsp);
            }
        }
        //卖家店铺商品详情
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("orgid", ShiroUtil.getCurentLoginUser().getOrganizationid());
        map.put("spid", "is not null");
        List<LptDpSp> sellDpspInfos = lptDpSpService.findList(map);

        if(!sellDpspInfos.isEmpty()&&null!=sellDpspInfos){
            for(LptDpSp sellDpspInfo:sellDpspInfos){
                for(LptDpSp buyDpspInfo:buyDpspInfos){
                    if(sellDpspInfo.getSpid().equals(buyDpspInfo.getSpid())&& 
                            sellDpspInfo.getKcsl() >= buyDpspInfo.getKcsl()){
                        //1、给前台返回message信息,清除webSocketMap对应信息,
                            QdWebSocketUtil.onMessage("successQd"+";"+info, null);
                         //2、添加抢单数据
                              lptDdSpService.insertQdData(info);
                         //3、清空购物车对应信息
                         lptGwcService.deleteGwcmxAndGwcmxSpSx(info);
                    }else if(!sellDpspInfo.getSpid().equals(buyDpspInfo.getSpid())){
                        return Message.error("对不起!您的店铺内没有【"+buyDpspInfo.getSpmc()+"】商品,不能抢单");
                    }else if(sellDpspInfo.getSpid().equals(buyDpspInfo.getSpid())&& 
                            sellDpspInfo.getKcsl() < buyDpspInfo.getKcsl()){
                        return Message.error("对不起!您的店铺内商品【"+buyDpspInfo.getSpmc()+"】库存不满足要求,不能抢单");
                    }
                }
            }
        }else{
            return Message.error("对不起!您的店铺内没有商品,不能抢单");
        }
        return Message.success("抢单成功!");
    }

9. 完整的websocketUtil代码

 @ServerEndpoint("/websocket")
public class QdWebSocketUtil {
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;

    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
    private static CopyOnWriteArraySet<QdWebSocketUtil> webSocketSet = new CopyOnWriteArraySet<QdWebSocketUtil>();

    //客户端提交的数据
    public static Map<Session,String> webSocketMap = new HashMap<Session,String>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    /**
     * 连接建立成功调用的方法
     * @param session  可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(){
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     * @throws IOException 
     */
    @OnMessage
    public static void onMessage(String message, Session session) throws IOException {
        System.out.println("来自客户端的消息session="+session+ message);
        for(QdWebSocketUtil item: webSocketSet){
            if(null !=session){//群发
                webSocketMap.put(session, message);
                for(Session key:webSocketMap.keySet()){
                    item.sendMessage(webSocketMap.get(key));
                }
            }else if(null ==session && "errorQd".equals(message)){//返回客户端提示信息--抢单失败
                for(Session key:webSocketMap.keySet()){
                    if(item.session.getId().equals(key.getId())){
                        item.sendMessage("您发布的抢单无商家抢单,请回购物车重新发布抢单!");
                    }
                }
            }else if(null ==session && "successQd".equals(message.split(";")[0].toString())){//返回客户端提示信息--抢单成功
                for(Session key:webSocketMap.keySet()){
                    if(webSocketMap.get(key).contains(message.split(";")[1].toString())){
                        item.sendMessage("您发布的抢单已有商家抢单,请及时查看!");
                        webSocketMap.remove(key);
                    }
                }
            }
        }
    }
    /**
     * 发生错误时调用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error){
        System.out.println("发生错误");
        error.printStackTrace();
    }
    /**
     * 发送信息
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException{
        this.session.getBasicRemote().sendText(message);
    }
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
        QdWebSocketUtil.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
        QdWebSocketUtil.onlineCount--;
    }
}

WebSocket程序部署到服务器上时出错

  1. 在Tomcat8部署的项目不要导入catalina.jar和websocket-api.jar这两个包,因为Tomcat8自带有这两个包。自己再导入的话会冲突。
  2. IP要写服务器的Ip地址,不要写localhost.
  3. .调试远程服务器的webSocket程序时,要关闭本地的Tomcat服务器。

WebSocket部署到服务器时,出现连接失败的问题解决与分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值