vue+springboot+netty實(shí)現(xiàn)ws連接并能聊天

需求背景

在項(xiàng)目知識庫管理系統(tǒng)下需要實(shí)現(xiàn)機(jī)器人智能問答的測試程序,需要在管理后臺系統(tǒng)實(shí)現(xiàn)一套測試聊天界面實(shí)現(xiàn)針對某個(gè)問題的提問能給出標(biāo)準(zhǔn)答案

技術(shù)方案

前端使用websocket與后端建立連接,并使用jwchat組件實(shí)現(xiàn)聊天界面的開發(fā)工作
后端使用springboot整合netty實(shí)現(xiàn)監(jiān)聽前端的連接請求,并能從數(shù)據(jù)庫獲取數(shù)據(jù)實(shí)現(xiàn)智能機(jī)問答并通過ws反饋給前端

界面效果

image.png

前端代碼

前端單獨(dú)一個(gè)界面用于聊天測試,基于ws與jwchat組件實(shí)現(xiàn)

<template>
  <JwChat-index
    :taleList="list"
    @enter="bindEnter"
    v-model="inputMsg"
  />
</template>

<style lang="scss" scoped>

</style>
<script>
export default {
  data() {
    return {
     wsUrl:"ws://localhost:8005/robot/test",
     socket: "",
     inputMsg: '',
     list: [],
    };
  },
  mounted: function(){
    this.init()
  },
  methods: {
    init() {
      if(typeof(WebSocket) === "undefined"){
        this.$message({
            type: 'error',
            message: '您的瀏覽器不支持socket'
          })
      }else{
        //實(shí)例化socket
        this.socket = new WebSocket(this.wsUrl)
        this.socket.onopen = this.open
        this.socket.onerror = this.error
        this.socket.onmessage = this.getMessage
      }
    },
    open(){
      console.log("socket連接成功")
    },
    error(){
      console.log("連接錯(cuò)誤")
    },
    getMessage(msg){
      console.log(msg.data)
      this.list.push({
        date: "2020/04/25 21:19:07",
        text: {"text": msg.data},
        mine: false,
        name: "xxy"
      })
    },
    send(params){
      this.socket.send(params)
    },
    close(){
      console.log("socket已經(jīng)關(guān)閉")
    },
    bindEnter() {
      this.list.push({
        date: "2020/04/25 21:19:07",
        text: {"text": this.inputMsg},
        mine: true,
        name: "xxy"
      })
      this.socket.send(this.inputMsg)
    }
  }
}
</script>

后端代碼

啟動(dòng)類

package com.gemini.admin;

import com.gemini.admin.ws.NettyServer;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @Author: XXY
 * @Date: 2021/2/19 19:10
 */
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.gemini.admin.dao.mapper")
@EnableTransactionManagement
public class RobotKbApplication {
    public static void main(String[] args) {
        SpringApplication.run(RobotKbApplication.class, args);
        try {
            new NettyServer(8005).start();
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

nettyserver

package com.gemini.admin.ws;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * @Author: XXY
 * @Date: 2021/2/19 19:36
 */
public class NettyServer {
    private final int port;

    public NettyServer(int port){
        this.port = port;
    }
    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            ServerBootstrap sb = new ServerBootstrap();
            sb.option(ChannelOption.SO_BACKLOG, 1024);
            sb.group(group, bossGroup).channel(NioServerSocketChannel.class)
                    .localAddress(this.port)//監(jiān)聽綁定端口
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            System.out.println("收到新連接");
                            //websocket本身是基于http協(xié)議的,所以這邊也要用http解碼器
                            socketChannel.pipeline().addLast(new HttpServerCodec());
                            //以塊的方式來寫的處理器
                            socketChannel.pipeline().addLast(new ChunkedWriteHandler());
                            socketChannel.pipeline().addLast(new HttpObjectAggregator(8192));
                            socketChannel.pipeline().addLast(new WebSocketServerProtocolHandler("/robot/test",
                                    null, true, 65536*10));
                            socketChannel.pipeline().addLast(new ChatWebSocketHandler());
                        }
                    });
            ChannelFuture cf = sb.bind().sync(); // 服務(wù)器異步創(chuàng)建綁定
            System.out.println(NettyServer.class + " 啟動(dòng)正在監(jiān)聽: " + cf.channel().localAddress());
            cf.channel().closeFuture().sync(); // 關(guān)閉服務(wù)器通道
        }finally {
            group.shutdownGracefully().sync();
            bossGroup.shutdownGracefully().sync();
        }
    }
}

channel通道管理池

package com.gemini.admin.ws;

import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * @Author: XXY
 * @Date: 2021/2/19 19:57
 * 管理所有webSocket連接
 */
public class ChatChannelHandlerPool {
    public ChatChannelHandlerPool(){}

    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}

自定義handler

核心為該類,你可以在該類中做各種處理,拿到數(shù)據(jù)反饋給前端,netty基礎(chǔ)用法可以自己看書學(xué)習(xí),上手比較快,需要了解netty原理還是要花很多功夫的

package com.gemini.admin.ws;

import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Author: XXY
 * @Date: 2021/2/19 19:54
 */
public class ChatWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static final Logger log = LoggerFactory.getLogger(ChatWebSocketHandler.class);
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("與客戶端建立連接,通道開啟");
        //添加到通道組
        ChatChannelHandlerPool.channelGroup.add(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("與客戶端連接斷開,通道關(guān)閉");
        ChatChannelHandlerPool.channelGroup.remove(ctx.channel());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //首次連接是FullHttpRequest,處理參數(shù) by zhengkai.blog.csdn.net
        if (msg instanceof FullHttpRequest) {
            /*FullHttpRequest request = (FullHttpRequest) msg;
            String uri = request.uri();

            Map paramMap=getUrlParams(uri);
            System.out.println("接收到的參數(shù)是:"+JSON.toJSONString(paramMap));
            //如果url包含參數(shù),需要處理
            if(uri.contains("?")){
                String newUri=uri.substring(0,uri.indexOf("?"));
                System.out.println(newUri);
                request.setUri(newUri);
            }*/

        }else if(msg instanceof TextWebSocketFrame){
            //正常的TEXT消息類型
            TextWebSocketFrame frame=(TextWebSocketFrame)msg;
            System.out.println("客戶端收到服務(wù)器數(shù)據(jù):" +frame.text());
            sendMessage(ctx, "你好,我是機(jī)器人小向");
        }
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {

    }

    private void sendMessage(ChannelHandlerContext ctx, String msg){
        ctx.channel().writeAndFlush(new TextWebSocketFrame(msg));
    }
}

結(jié)論

通過以上代碼,我們就能實(shí)現(xiàn)完整的前后端聊天了,簡單吧,關(guān)于jwchat可以從jwchat開源進(jìn)行了解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容