需求背景
在项目知识库管理系统下需要实现机器人智能问答的测试程序,需要在管理后台系统实现一套测试聊天界面实现针对某个问题的提问能给出标准答案
技术方案
前端使用websocket与后端建立连接,并使用jwchat组件实现聊天界面的开发工作
后端使用springboot整合netty实现监听前端的连接请求,并能从数据库获取数据实现智能机问答并通过ws反馈给前端
界面效果
前端代码
前端单独一个界面用于聊天测试,基于ws与jwchat组件实现
<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{
//实例化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("连接错误")
},
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已经关闭")
},
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>
后端代码
启动类
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)//监听绑定端口
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
System.out.println("收到新连接");
//websocket本身是基于http协议的,所以这边也要用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(); // 服务器异步创建绑定
System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
cf.channel().closeFuture().sync(); // 关闭服务器通道
}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
核心为该类,你可以在该类中做各种处理,拿到数据反馈给前端,netty基础用法可以自己看书学习,上手比较快,需要了解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("与客户端连接断开,通道关闭");
ChatChannelHandlerPool.channelGroup.remove(ctx.channel());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//首次连接是FullHttpRequest,处理参数 by zhengkai.blog.csdn.net
if (msg instanceof FullHttpRequest) {
/*FullHttpRequest request = (FullHttpRequest) msg;
String uri = request.uri();
Map paramMap=getUrlParams(uri);
System.out.println("接收到的参数是:"+JSON.toJSONString(paramMap));
//如果url包含参数,需要处理
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("客户端收到服务器数据:" +frame.text());
sendMessage(ctx, "你好,我是机器人小向");
}
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));
}
}
结论
通过以上代码,我们就能实现完整的前后端聊天了,简单吧,关于jwchat可以从jwchat开源进行了解