需求背景
在項(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)行了解