当前位置:   article > 正文

WebSocket后台设计_websocket接口怎么写

websocket接口怎么写

正在做的项目,有一个需求。大屏展示后台数据,当时同事大佬推荐使用WebSocket来做,需求细分的话,主要有两部分,一部分是后台主动推送数据给前台,还有一部分是大屏通过点击筛选条件,实现数据的变更。

最初解决方法

因为刚开始大佬就给了模块化的设计理念,所以后台设计的第一版本就是页面分块进行数据的推送。就有了我最初的代码实现:每个模块对应后台的一个类,同时我使用了一个Service来放所有的获取数据的代码实现。推送的数据是一个Map,key就是一个枚举,value就是数据,前端接收到这个Map之后,对数据进行渲染。

优点:脑瘫如我都能写出来,并且实现最终的功能(虽然这样会很麻烦)
缺点
1.只有一个Service,这个类里面的代码量太大,后期维护费劲,修改费劲
2.由于给前端传的是一个Map,所以并不能限定Map的key就是对应的枚举,对于我自己来说还好,如果回头有同事加入这个项目,可能会导致,传给前端的,并不是标准的 枚举 -> 数据 的Map格式
3.正如上述的需求所说,页面还需要点击不同的筛选条件,展示的数据不同,由于筛选条件和具体改变的数据,其实并不在一个模块内,所以第一版本的设计,可能根本就实现不了这个需求(我也没想出来怎么实现)
4.对于每个模块来说,每个模块的实现类,都需要写一边WebSocket的推送数据的逻辑,很麻烦
5.整体代码的观赏性极差,也很不优雅
****(肯定还有,只不过还没发现)

最终解决方法

偶然间大佬同事看见了我的代码,指出了我这样写的不足,同时告诉我,类也可以是key-value格式,你可以把推送数据给抽象出来,每一个模块对应一个类,对应一个service实现,使用一个公共的推送数据的接口,每一个模块的实现,只用去关心怎么取出当前模块的数据,把推送数据的方法,给公共的接口去实现,只用去暴露这个推送数据的方法,同时使用泛型来约束推送数据,直接上代码

WebSocket实现类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cloud.top.tower.common.utils.SpringUtils;
import com.cloud.top.tower.enums.LargeIdentificationEnum;
import com.cloud.top.tower.model.largeScreen.ReceiveData;
import com.cloud.top.tower.model.largeScreen.first.accept.ConnectionDataAccept;
import com.cloud.top.tower.model.largeScreen.second.accept.MapQueryAccept;
import com.cloud.top.tower.model.largeScreen.second.accept.NetConnectionSelectAccept;
import com.cloud.top.tower.service.pushLogin.IPushService;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.yeauty.annotation.*;
import org.yeauty.pojo.Session;

import java.io.IOException;
import java.net.SocketAddress;
import java.util.List;
import java.util.Map;

/**
 * @author YanYan
 * @description: webSocket
 * @date 2022/12/30 13:59
 */

@Slf4j
@ServerEndpoint(path = "/ws", port = "8189")
@Component
public class WebSocketServer {


    private static Map<String, Session> AllSession = Maps.newConcurrentMap();


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) throws IOException {
        SocketAddress socketAddress = session.remoteAddress();
        String clientIp = socketAddress != null ? socketAddress.toString().replace("/", "").split(":")[0] : "";
        log.info("WebSocket提示信息,IP为:{}", clientIp + "连接成功");
        AllSession.put(clientIp, session);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        SocketAddress socketAddress = session.remoteAddress();
        String clientIp = socketAddress != null ? socketAddress.toString().replace("/", "").split(":")[0] : "";
        if (StringUtils.isNoneBlank(clientIp)) {
            AllSession.remove(clientIp);
        }
        log.info("WebSocket提示信息,一个连接关闭,ip为:{}", clientIp);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * @param session 发送消息的客户端
     * @return
     */
    @OnMessage
    public String onMessage(String message, Session session) {
        if (!"HeartBeat".equals(message)) {
            this.handleSend(message, session);
        } else {
            sendMessage(message);
        }
        return "servet 发送:" + message;
    }

    /**
     * webSocket报错后执行的方法
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("Websocket出错,错误原因为:{}", error.getMessage());
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(Object object) {
        for (String key : AllSession.keySet()) {
            AllSession.get(key).sendText(JSON.toJSONString(object));
        }
    }

    /**
     * 实现服务器根据session推送数据
     */
    public void sendMessage(Object object, Session session) {
        session.sendText(JSON.toJSONString(object));
    }


    /**
     * 处理前端传输的指令数据,并进行指定数据推送
     */
    private void handleSend(String message, Session session) {
        List<IPushService> pushBySendServiceList = SpringUtils.getPushServiceList();
        JSONObject jsonObject = JSONObject.parseObject(message);
        LargeIdentificationEnum key = LargeIdentificationEnum.getByStr(jsonObject.getString("key"));
        if (ObjectUtils.isEmpty(key)) {
            log.info("WebSocket提示:前端通信消息{}", jsonObject);
            return;
        }
        if (key.equals(LargeIdentificationEnum.ConnectionDataAll)) {
            if (StringUtils.isNotEmpty(jsonObject.getString("emission")) && StringUtils.isNotEmpty(jsonObject.getString("platform"))) {
                ConnectionDataAccept connectionData = new ConnectionDataAccept(jsonObject.getString("platform"), jsonObject.getString("emission"));
                ReceiveData<ConnectionDataAccept> receiveData = new ReceiveData<>(LargeIdentificationEnum.CustomerRunDataOil, connectionData);
                ReceiveData<ConnectionDataAccept> receiveDataAttend = new ReceiveData<>(LargeIdentificationEnum.CustomerRunDataAttend, connectionData);
                ReceiveData<ConnectionDataAccept> receiveDataMileage = new ReceiveData<>(LargeIdentificationEnum.CustomerRunDataMileage, connectionData);
                pushBySendServiceList.forEach(v -> v.pushBySend(receiveData, session));
                pushBySendServiceList.forEach(v -> v.pushBySend(receiveDataAttend, session));
                pushBySendServiceList.forEach(v -> v.pushBySend(receiveDataMileage, session));
            }
        } else if (key.equals(LargeIdentificationEnum.NetConnectionDataIsSecond)) {
            //第二屏-入网连接量数据
            NetConnectionSelectAccept netConnectionSelectAccept = new NetConnectionSelectAccept();
            netConnectionSelectAccept.setPlatformId(StringUtils.isNotEmpty(jsonObject.getString("platformId")) ? Integer.parseInt(jsonObject.getString("platformId")) : null);
            netConnectionSelectAccept.setBrandId(StringUtils.isNotEmpty(jsonObject.getString("brandId")) ? Integer.parseInt(jsonObject.getString("brandId")) : null);
            netConnectionSelectAccept.setTypeId(StringUtils.isNotEmpty(jsonObject.getString("typeId")) ? Integer.parseInt(jsonObject.getString("typeId")) : null);
            netConnectionSelectAccept.setEmissionId(StringUtils.isNotEmpty(jsonObject.getString("emissionId")) ? Integer.parseInt(jsonObject.getString("emissionId")) : null);
            ReceiveData<NetConnectionSelectAccept> acceptData = new ReceiveData<>(LargeIdentificationEnum.NetConnectionDataIsSecond, netConnectionSelectAccept);
            pushBySendServiceList.forEach(v -> v.pushBySend(acceptData, session));
        } else if (key.equals(LargeIdentificationEnum.MapData)) {
            //第二屏-地图数据
            MapQueryAccept mapQueryAccept = new MapQueryAccept();
            mapQueryAccept.setType(StringUtils.isNotEmpty(jsonObject.getString("type")) ? jsonObject.getString("type") : null);
            mapQueryAccept.setProvinceStr(StringUtils.isNotEmpty(jsonObject.getString("provinceStr")) ? jsonObject.getString("provinceStr") : null);
            ReceiveData<MapQueryAccept> receiveData = new ReceiveData<>(LargeIdentificationEnum.MapData, mapQueryAccept);
            pushBySendServiceList.forEach(v -> v.pushBySend(receiveData, session));
        } else {
            pushBySendServiceList.forEach(v -> v.push(key));
        }
        log.info("WebSocket提示:前端通信消息{}", jsonObject);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147

此处可以先不看handleSend方法,等后面所有的逻辑都理清之后,再看

推送数据的公共类

import com.cloud.top.tower.enums.LargeIdentificationEnum;
import lombok.Getter;
import lombok.Setter;

/**
 * @author YanYan
 * @description: 推送数据对象
 * @date 2023/2/7 10:13
 */

@Setter
@Getter
public class PushData<T> {

    /**
     * 推送数据的key
     */
    private LargeIdentificationEnum key;

    /**
     * 推送数据的value
     */
    private T value;


    public PushData(LargeIdentificationEnum key, T value) {
        this.key = key;
        this.value = value;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

推送数据的公共类中,用构造函数来约束key必须为大屏展示的每个模块对应的枚举,此处只要和前端约束好枚举,就能实现分模块推送,同时泛型是每个模块对应的具体数据的实现类,这样就完全把推送数据对象给抽象出来了,对于业务来说,推送数据就是这个对象,具体实现就看程序员怎么写。

接收数据的公共类

package com.cloud.top.tower.model.largeScreen;

import com.cloud.top.tower.enums.LargeIdentificationEnum;
import lombok.Getter;
import lombok.Setter;

/**
 * @author YanYan
 * @description: 接收数据对象
 * @date 2023/2/13 14:30
 */

@Setter
@Getter
public class ReceiveData<T> {

    /**
     * 接收数据的key
     */
    private LargeIdentificationEnum key;

    /**
     * 接收数据的value
     */
    private T value;


    public ReceiveData(LargeIdentificationEnum key, T value) {
        this.key = key;
        this.value = value;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

此处是通过大佬的想法,我把接收数据的类,也抽象为一个公共类,和推送数据公共类原理是一样的,此类的主要功能就是,接收前端传给后端的消息,通过此消息,推送指定的数据
(如:进入页面的时候,可以向后端发送一个推送全部数据的指令,后端通过此指令,推送指定的数据)

推送数据的公共Service

import com.cloud.top.tower.common.utils.SpringUtils;
import com.cloud.top.tower.common.webSocket.WebSocketServer;
import com.cloud.top.tower.enums.LargeIdentificationEnum;
import com.cloud.top.tower.model.largeScreen.PushData;
import com.cloud.top.tower.model.largeScreen.ReceiveData;
import org.yeauty.pojo.Session;

/**
 * @author YanYan
 * @description: 推送接口(包括后端主动推送数据给前端;前端通过给后端发送指令,后端推送数据)
 * @date 2023/2/7 11:04
 */
public interface IPushService<T, S> {

    WebSocketServer webSocketService = SpringUtils.getBean(WebSocketServer.class);

    /**
     * 获取推送数据的方法
     *
     * @param receiveData 接收数据对象
     * @return 推送数据类
     */
    PushData<S> getPushData(ReceiveData<T> receiveData);

    /**
     * 主动推送数据的方法
     */
    default PushData<S> getPushData(LargeIdentificationEnum largeIdentificationEnum) {
        ReceiveData<T> receiveData = new ReceiveData<>(largeIdentificationEnum, null);
        return getPushData(receiveData);
    }


    /**
     * 推送数据,需要前端传送指令
     */
    default void pushBySend(ReceiveData<T> receiveData, Session session) {
        PushData<S> pushData = getPushData(receiveData);
        if (receiveData.getKey().equals(pushData.getKey())) {
            webSocketService.sendMessage(pushData, session);
        }
    }

    /**
     * 推送数据,根据枚举常量即可
     */
    default void push(LargeIdentificationEnum largeIdentificationEnum) {
        PushData<S> pushData = getPushData(largeIdentificationEnum);
        /*
         * 判断是否获取数据 写在每一个service实现里面了,只需要判断value是否为null 就可以来判断是否推送
         * */
        if (pushData.getValue() != null) {
            webSocketService.sendMessage(pushData);
        }
    }

    /**
     * 批量推送
     */
    default void push(LargeIdentificationEnum... largeIdentificationEnums) {
        for (LargeIdentificationEnum largeIdentificationEnum : largeIdentificationEnums) {
            this.push(largeIdentificationEnum);
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

推送数据的公共Service是整个设计的核心。
getPushData方法的入参是接收数据对象,此处主要用来实现业务中前端选择筛选条件后,后端可以接收筛选条件,然后通过处理筛选条件,进行指定数据的推送,同时此处还不收模块化的影响,可以根据筛选条件,推送不同模块的数据。
getPushData方法的返回值,就是推送给前端的数据,此返回值必定包括模块的枚举key,数据可有可无,同时数据的获取逻辑是每一个实现推送数据公共Serivce的实现类里面。
push方法,实现了后端主动向前端推送数据,push方法的入参就是推送指定模块的枚举,只需要调用default PushData<S> getPushData(LargeIdentificationEnum largeIdentificationEnum) 此方法,组装接收数据对象,不需要填写筛选条件,填写指定模块的key即可实现,同时每一个公共Service的实现类里面,都会判断传输过来的枚举,是否为当前模块的枚举,如果不是,直接返回PushData的value为空,push方法通过判断,来找出真正需要推送的模块,调用webSocket的推送数据的方法,完成推送
其他的方法,都是在此的扩展之下,如果读懂了上述逻辑,那么是非常好理解的。

展示其中一个模块的数据推送实现类

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.cloud.top.tower.common.cache.DictCache;
import com.cloud.top.tower.common.utils.LargeIdentificationJudgeUtil;
import com.cloud.top.tower.entity.bu.BuFleetConfigInformation;
import com.cloud.top.tower.enums.DictBelongEnum;
import com.cloud.top.tower.enums.LargeIdentificationEnum;
import com.cloud.top.tower.enums.ServiceEnum;
import com.cloud.top.tower.model.base.DictVO;
import com.cloud.top.tower.model.largeScreen.PushData;
import com.cloud.top.tower.model.largeScreen.ReceiveData;
import com.cloud.top.tower.model.largeScreen.second.NumberOfIncomingConnections;
import com.cloud.top.tower.model.largeScreen.second.accept.NetConnectionSelectAccept;
import com.cloud.top.tower.service.bu.IFleetConfigInformationService;
import com.cloud.top.tower.service.pushLogin.IPushService;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author YanYan
 * @description: 第二屏-入网连接量数据推送实现
 * @date 2023/2/14 10:36
 */

@Service
@Setter(onMethod = @__(@Autowired))
public class NumberOfIncomingConnectionsServiceImpl implements IPushService<NetConnectionSelectAccept, NumberOfIncomingConnections> {


    private IFleetConfigInformationService fleetConfigInformationService;


    /**
     * 获取推送数据的方法
     *
     * @param receiveData 接收数据对象
     * @return 推送数据类
     */
    @Override
    public PushData<NumberOfIncomingConnections> getPushData(ReceiveData<NetConnectionSelectAccept> receiveData) {
        if (LargeIdentificationJudgeUtil.judge(LargeIdentificationEnum.NetConnectionDataIsSecond, receiveData)) {
            return new PushData<>(LargeIdentificationEnum.NetConnectionDataIsSecond, null);
        }
        NumberOfIncomingConnections numberOfIncomingConnections = new NumberOfIncomingConnections();

        //获取需要查询逻辑
        NetConnectionSelectAccept accept = receiveData.getValue();
        numberOfIncomingConnections.setIsAllData(ObjectUtils.isEmpty(accept) ? ServiceEnum.yes.getStateCode() : ServiceEnum.no.getStateCode());
        //根据条件获取车辆
        List<BuFleetConfigInformation> fleetConfigInformationList = fleetConfigInformationService.getByNetConnectionAccept(accept);
        numberOfIncomingConnections.setCarCount(fleetConfigInformationList.size());

        //获取所有车辆
        List<BuFleetConfigInformation> fleetConfigInformationListAll = fleetConfigInformationService.list();
        //获取字典数据缓存中 需要展示的 动力类型的数据集合
        List<DictVO> powerTypes = DictCache.DictVoMap.values().stream().filter(v -> v.getIdentification().equals(DictBelongEnum.powerType.getCode())
                && v.getBigLarger().equals(ServiceEnum.yes.getStateCode())).collect(Collectors.toList());
        List<NumberOfIncomingConnections.KeyValue> connectionList = new ArrayList<>();
        powerTypes.forEach(v -> {
            NumberOfIncomingConnections.KeyValue connections = new NumberOfIncomingConnections.KeyValue();
            connections.setName(v.getModule() + "连接量:");
            connections.setCount(fleetConfigInformationListAll.stream().filter(s -> s.getPowerType().equals(v.getId())).count());
            connectionList.add(connections);
        });
        numberOfIncomingConnections.setConnections(connectionList);
        return new PushData<>(LargeIdentificationEnum.NetConnectionDataIsSecond, numberOfIncomingConnections);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

此实现类,就是本项目的集大成者,抽象出来的最终形态,此模块既有后端主动给前端推送数据,也有前端发送指令,后端通过指令筛选条件,然后推送数据。中间的,都是获取数据的逻辑,不用在意,主要还是学习思想

还有一个注意的点,由于公共推送数据Service具有很多实现,所以通过自动注入是不行的,推送指定模块数据的代码大概是这个样子的,就两行

//获取推送数据的所有Service实现
List<IPushService> pushBySendServiceList = SpringUtils.getPushServiceList();
//此处的key就是 每个模块对应的枚举
pushBySendServiceList.forEach(v -> v.push(key));
  • 1
  • 2
  • 3
  • 4

最后,各位,思想无价,这个设计,可能还有很多不足之处,不过对我来说,受益终身,现在做的项目,学到这个,很值。

同时也欢迎各位在下面讨论,我肯定还有很多说的不够严谨的地方,大家可以指出,同时有不理解的地方,也欢迎大家进行讨论和提问,我看见一定会回答。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/241790
推荐阅读
相关标签
  

闽ICP备14008679号