如何给网站添加网站地图,软文街怎么样,线上推广渠道主要有哪些,腾讯企点app文章目录 参考链接使用前端界面简单效果消息窗口平滑滚动至底部vue使用watch监听vuex中的变量变化 websocket握手认证ChatKeyCheckHandlerNettyChatServerNettyChatInitializer 参考链接
zzhua/netty-chat-web - 包括前后端
vue.js实现带表情评论功能前后端实现#xff08;仿… 文章目录 参考链接使用前端界面简单效果消息窗口平滑滚动至底部vue使用watch监听vuex中的变量变化 websocket握手认证ChatKeyCheckHandlerNettyChatServerNettyChatInitializer 参考链接
zzhua/netty-chat-web - 包括前后端
vue.js实现带表情评论功能前后端实现仿B站评论 vue.js实现带表情评论仿bilibili滚动加载效果
vue.js支持表情输入 vue.js表情文本输入框组件
个人说说vue组件
JS操作文本域获取光标/指定位置插入
分布式ID生成之雪花算法SnowFlake netty-demo-crazy - 疯狂架构师netty
IM即时通讯系统[SpringBootNetty]——梳理总 代码 im-system 在gitee
构建IM即使通讯Web页面 - B站视频仅前端代码代码 chat-demo 在gitee
禹神一小时快速上手Electron前端Electron开发教程笔记。一篇文章入门ElectronElectron集成Vite Vue 开发IM即使通讯easychat - gitee代码
基于vue3实现一个简单的输入框效果 vue3通过组合键实现换行操作的示例详解
easychat - B站视频前后端代码在gitee
subtlechat-mini - 前后端有mini版和完整版
lyf-im - 前后端项目
box-im - 盒子im很棒
MallChat - 前后端项目代码很棒 木杉/ 视频通话 netty webrtc websocket springboot uniapp
木杉/ /mushan-im
ProgHub/chat_room
yymao/chatroom - 仅前端im界面好看
H260788/PureChat - im界面好看前端难度大
netty-chatroom - netty实现仅后端代码
liurq_netty_barrage - netty实现的1个简单的弹幕效果
yorick_socket一套基于Netty的轻量级Socket和WebSocket框架可用于搭建聊天服务器和游戏同步服务器
【聊天系统】从零开始自己做一个wechat - uniapp 和 springboot
linyu-mini-weblinyu-mini-server gitee 前后端代码
aq-chat web端AQChatServeraqchat-mobileAQChat文档中心,
考拉开源/im-uniappim-platform 后台代码
ws-chat - 前后端代码
yan代码B站视频)
im-whale-shark代码B站视频
使用
前端使用vue.js vuex iconfont element-ui 后端使用springboot mybatisplus redis netty websocket spring security
可能有不少问题反正先按照自己思路一点一点写再参考下别人是怎么搞的再优化
前端界面
先写下大概的前端界面界面出来了才有继续写下去的动力 简单效果 消息窗口平滑滚动至底部
div classpanel-main-body refpanelMainBodyContainerRef!-- 对应会话 的消息列表 --div classmsg-item-list refmsgItemListContainerRefdiv :class[msg-item, familyChatMsg.senderId ! currUserId ? other : owner]v-for(familyChatMsg, idx) in familyChatMsgList :keyidxdiv classavatar-wrapper img :srcfamilyChatMsg.avatar classavatar fit-img alt/divdiv classmsgdiv classmsg-header{{ familyChatMsg.nickName }}/divdiv classmsg-content v-htmlfamilyChatMsg.content/div/div/div/div/divscript
export default {methods: {/* 滚动至底部不过调用此方法的时机应当在familyChatMsgList更新之后, 因此需要监听它 */scrollToEnd() {const panelMainBodyContainerRef this.$refs[panelMainBodyContainerRef]const msgItemListContainerRef this.$refs[msgItemListContainerRef]// console.log(msgItemListContainerRef.scrollTop);// console.log(panelMainBodyContainerRef.scrollHeight);msgItemListContainerRef.scrollTop msgItemListContainerRef.scrollHeightconsole.log(滚动至底部~);},}}
/scriptstyle
.msg-item-list {/* 平滑滚动 */scroll-behavior: smooth;
}
/stylevue使用watch监听vuex中的变量变化
computed: {...mapGetters(familyChatStore, [familyChatMsgList]),
},watch: {// 监听store中的数据 - 是通过监听getters完成的familyChatMsgList:{handler(newVal, oldVal) {// console.log(---------------------);// console.log(newVal.length, oldVal.length);this.$nextTick((){this.scrollToEnd()})}}},websocket握手认证
客户端在登录完成后可以请求后端的接口获取1个chatKey这个chatKey只有在用户登录后携带token访问时才能得到得到此chatKey后连接websocket客户端时把这个chatKey作为请求参数拼接到ws://xxxx.xx.xx:9091/ws?chatKeyxxx这样在握手的时候就可以拿到这个请求参数。但是我不想在握手完成事件时再去拿这个chatKey虽然这样做也没什么问题但感觉逻辑不是很好都已经握手完成了再来断掉ws连接有点不好因此设置1个ChatKeyCheckHandler它继承自SimpleInboundHandlerAdapter处理的泛型是FullHttpRequest并且把这个处理器放在WebSocketServerProtocolHandler的前面这样在处理握手请求时就可以拿到请求参数了而握手完成之后由于后面的消息是websocket协议帧数据它不会FullHttpRequest类型的因此不会经过这个处理器这样感觉比较好~
ChatKeyCheckHandler
Slf4j
ChannelHandler.Sharable
Component
public class ChatKeyCheckHandler extends SimpleChannelInboundHandlerFullHttpRequest {public ChatKeyCheckHandler() {super(false);}Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {log.info(http请求-chatKeyCheckHandler处理);FullHttpRequest request ((FullHttpRequest) msg);String uri request.uri();log.info(请求uri: {});log.info(请求header: {}, Arrays.toString(request.headers().names().toArray()));ListString chatKeys UriComponentsBuilder.fromUriString(uri).build().getQueryParams().get(Constants.CHAT_KEY);if (CollectionUtils.isEmpty(chatKeys)) {log.error(欲建立websocket连接,但未携带chatKey,直接略过);// 还得写个响应回去并且关闭HTTP连接HttpRequest req msg;FullHttpResponse response new DefaultFullHttpResponse(req.protocolVersion(), OK,Unpooled.wrappedBuffer(NOT ALLOWD WITHOUT CHAT_KEY.getBytes()));response.headers().set(CONTENT_TYPE, TEXT_PLAIN).setInt(CONTENT_LENGTH, response.content().readableBytes());// Tell the client were going to close the connection.response.headers().set(CONNECTION, CLOSE);ChannelFuture f ctx.writeAndFlush(response);f.addListener(ChannelFutureListener.CLOSE);return;}String chatKey chatKeys.iterator().next();ctx.channel().attr(WsContext.CHAT_KEY_ATTR).set(chatKey);log.info(建立websocket连接的握手请求, 携带了chatKey: {}, chatKey);// 在此处校验chatKey是否合理, 如果不合理, 则不允许建立websocket链接不会进行后面的握手处理ctx.fireChannelRead(request);}
}
NettyChatServer
Slf4j
Component
public class NettyChatServer implements SmartLifecycle {Autowiredprivate NettyProperties nettyProps;Autowiredprivate NettyChatInitializer nettyChatInitializer;private volatile boolean running false;private ServerChannel serverChannel;private EventLoopGroup bossGroup;private EventLoopGroup workerGroup;public void start() {log.info(starting netty server~);bossGroup new NioEventLoopGroup(1);workerGroup new NioEventLoopGroup(2);ServerBootstrap serverBootstrap new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(nettyChatInitializer);// 这里会异步调用ChannelFuture channelFuture serverBootstrap.bind(nettyProps.getPort());channelFuture.addListener(future - log.info(netty started, listening: {}, nettyProps.getPort()));// 保存对ServerSocketChannel的引用serverChannel (NioServerSocketChannel) channelFuture.channel();channelFuture.channel().closeFuture().addListener(future - log.info(netty stopped!));running true;}Overridepublic void stop() {log.info(stop netty server);try {serverChannel.close();} catch (Exception e) {log.error(关闭ServerSocketChannel失败);}if (bossGroup ! null) {bossGroup.shutdownGracefully();}if (workerGroup ! null) {workerGroup.shutdownGracefully();}running false;}Overridepublic boolean isRunning() {return this.running;}}NettyChatInitializer
Component
public class NettyChatInitializer extends ChannelInitializerSocketChannel {Autowiredprivate NettyProperties nettyProperties;Autowiredprivate DispatcherMsgHandler dispatcherMsgHandler;Autowiredprivate HandShakeHandler handShakeHandler;Autowiredprivate ChatKeyCheckHandler chatKeyCheckHandler;Overrideprotected void initChannel(SocketChannel ch) throws Exception {DefaultEventLoopGroup eventExecutors new DefaultEventLoopGroup(2);ChannelPipeline pipeline ch.pipeline();pipeline.addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));pipeline.addLast(http-decoder, new HttpRequestDecoder());pipeline.addLast(http-encoder, new HttpResponseEncoder());pipeline.addLast(aggregator, new HttpObjectAggregator(64 * 1024));pipeline.addLast(new ChunkedWriteHandler());WebSocketServerProtocolConfig wsServerConfig WebSocketServerProtocolConfig.newBuilder().websocketPath(nettyProperties.getWsPath()).checkStartsWith(true).maxFramePayloadLength(Integer.MAX_VALUE).build();pipeline.addLast(chatKeyHandler, chatKeyCheckHandler);pipeline.addLast(websocketHandler, new WebSocketServerProtocolHandler(wsServerConfig));pipeline.addLast(handShakeHandler, handShakeHandler);pipeline.addLast(heartBeanCheckHandler, new HeatBeatCheckHandler());pipeline.addLast(eventExecutors, dispatcherMsgHandler, dispatcherMsgHandler);}}