springboot+websocket+微信小程序实现评论区功能
springboot+websocket+微信小程序实现评论区功能
- WebSocket
- STOMP协议
- 具体实现
- 1.在pom文件中添加Spring WebSocket依赖
- 2. 创建WebSocket配置类
- 3.接收发送消息
- 4.前端
- 参考
WebSocket
1. 什么是WebSocket?
WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:
WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;
WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。
2. WebSocket的特点
- 全双工通信:WebSocket协议支持服务器和客户端之间的全双工通信,客户端和服务器可以同时发送消息。
- 持久连接:WebSocket连接一旦建立,将持续保持打开状态,直到客户端或服务器关闭连接。
- 跨域通信:WebSocket协议支持跨域通信,允许不同域的服务器与客户端建立连接。
STOMP协议
STOMP协议相当于Websocket的子协议。
STOMP 中文为: 面向消息的简单文本协议
websocket定义了两种传输信息类型:文本信息和二进制信息。类型虽然被确定,但是他们的传输体是没有规定的。所以,需要用一种简单的文本传输类型来规定传输内容,它可以作为通讯中的文本传输协议。
STOMP是基于帧的协议,客户端和服务器使用STOMP帧流通讯
一个STOMP客户端是一个可以以两种模式运行的用户代理,可能是同时运行两种模式。
作为生产者,通过SEND框架将消息发送给服务器的某个服务
作为消费者,通过SUBSCRIBE制定一个目标服务,通过MESSAGE框架,从服务器接收消息。
例如:
COMMAND
header1:value1
header2:value2Body^@
注:帧以commnand字符串开始,以EOL结束。其中包括可选回车符(13字节),紧接着是换行符(10字节)。command下面是0个或多个:格式的header条目, 每个条目由EOL结束。一个空白行(即额外EOL)表示header结束和body开始。body连接着NULL字节。本文档中的例子将使用^@代表NULL字节。NULL字节可以选择跟多个EOLs。欲了解更多关于STOMP帧的详细信息,请参阅STOMP1.2协议规范。
具体实现
微信小程序提供的关于WebSocket的API不是基于STOMP协议的,只能用websocket实现,当然可以对微信小程序的前端进行更改,使其可以使用基于STOMP协议的后端实现。
基于STOMP协议的websocket实现可以参考:Java实战:Spring Boot实现WebSocket实时通信
1.在pom文件中添加Spring WebSocket依赖
<dependencies><!-- Spring Boot Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot WebSocket依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
</dependencies>
2. 创建WebSocket配置类
package com.lixy.sharingcurriculum.config;import com.lixy.sharingcurriculum.controller.CommentController;
import com.lixy.sharingcurriculum.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {/*使用springboot内置tomcat需要该bean,打war包则注释掉该bean*/@Beanpublic ServerEndpointExporter serverEndpoint() {return new ServerEndpointExporter();}@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {//注册一个WebSocket端点,路径为"/ws"//registry.addEndpoint("/ws").withSockJS();//客户端调用的URL;registry.addEndpoint("/ws").setAllowedOrigins("*");}//因为websocket中不能直接使用@Autowired注入Service,添加下面配置 在socket引入Service@Autowiredpublic void socketUserService(CommentService commentService){CommentController.commentService = commentService;}
}
-
创建一个WebSocket的配置类
WebSocketConfig
实现WebSocketMessageBrokerConfigurer
接口,需要手动注入ServerEndpointExporter,这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint。
我的理解是为了将WebSocket与Spring容器整合,通过在配置类中添加@Bean,使其返回对象ServerEndpointExporter,意味着将该对象纳入spring框架的管理中,spring容器将能够处理WebSocket相关的配置和依赖。 -
registry.addEndpoint("/ws").setAllowedOrigins("*");
意思是允许/ws/*
的所有请求访问。websocket默认禁止了非同源访问
,如果没有加入自己的跨域配置或这个配置的话,前端将会报403错误。参考:客户端连接spring websocket 返回403错误 -
因为我需要同时对数据库进行修改,所以引入了commentService进行数据相关操作。
同源访问指的是WebSocket连接的客户端和服务器端拥有相同的协议、主机和端口。
在Web开发中,同源策略是一种安全机制,它要求前端的网页脚本只能与同源的服务器进行交互。具体来说,同源访问要求客户端的页面和服务器端的服务必须使用相同的协议(如HTTP或HTTPS)、相同的主机名(如www.example.com)以及相同的端口号(如80或443)。如果这些条件中有任何一个不匹配,那么就是非同源访问。
对于WebSocket而言,虽然它本身并不强制执行同源策略,但浏览器的安全限制通常要求WebSocket遵循同源策略。这意味着默认情况下,一个网页中的WebSocket脚本只能连接到与其同源的服务器。如果需要连接到不同源的服务器,可以使用CORS(跨域资源共享)来允许这种跨域访问。
总结来说,同源访问是WebSocket通信的一种默认安全限制,它要求客户端和服务器在协议、主机和端口上保持一致。如果需要进行非同源访问,开发者需要在服务器端配置相应的CORS策略,以允许跨域的WebSocket连接。
3.接收发送消息
webSocket接收和发送的消息的类型只能是String类型,所以在传数据的时候需要做相应的处理。
package com.lixy.sharingcurriculum.controller;import com.alibaba.fastjson.JSON;
import com.lixy.sharingcurriculum.entity.Message;
import com.lixy.sharingcurriculum.entity.dto.MessDto;
import com.lixy.sharingcurriculum.service.CommentService;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@Component
@ServerEndpoint("/ws/{courid}/{userid}")
@Slf4j
public class CommentController {//存储被课程id和正在讨论区中的用户id和对应会话public static final Map<String, Map<String, Session>> sessionMap = new ConcurrentHashMap<>();public static CommentService commentService;/**** 建立连接时调用* 1.第一层map存储被课程id和浏览的用户及会话* 2.第二层map存储用户id及会话*/@OnOpenpublic void onOpen(Session session, @PathParam("courid") String courid,@PathParam("userid") String userid){log.info("用户{}打开了课程{}的讨论区",userid,courid);if(sessionMap.containsKey(courid)){sessionMap.get(courid).put(userid,session);}else {Map<String,Session> map = new HashMap<>();map.put(userid,session);sessionMap.put(courid,map);}log.info(String.valueOf(sessionMap));}/*** 接收处理客户端发来的数据*/@OnMessagepublic void onMessage(String message) {
// 解析消息为java对象Message msg = JSON.parseObject(message, Message.class);log.info("msg:"+msg);if(msg != null&& msg.getContent() != null &&!msg.getContent().isEmpty()){//将评论内容存入数据库MessDto messDto=commentService.saveComment(msg);//将评论内容发送给其他正在浏览该文章的用户sendAllMessage(messDto);}else{System.out.println("评论内容为空");}}//服务端发送消息给客户端private void sendAllMessage(Message message) {log.info("sendAllMessage:{}",message);try {String courid = message.getCourid();log.info("courid:{}",courid);//根据评论发送课程的id,将该评论发送给正在讨论区中的用户Map<String,Session> passageMap = sessionMap.get(courid);for (Session session : passageMap.values()) {session.getBasicRemote().sendText(JSON.toJSONString(message));}} catch (Exception e) {e.printStackTrace();}}//关闭连接/*** 1.把登出的用户从sessionMap中剃除* 2.发送给所有人当前登录人员信息*/@OnClosepublic void onClose(@PathParam("courid") String courid,@PathParam("userid") String userid) {//用户退出讨论区,则将该用户及其会话移除Map<String,Session> passageMap = sessionMap.get(courid);passageMap.remove(userid);//没有用户在讨论区中,则将课程id从map中移除if(passageMap.isEmpty()){sessionMap.remove(courid);}}@OnErrorpublic void onError(Session session, Throwable error) {System.out.println("发生错误");error.printStackTrace();}}
4.前端
//创建WebSocket连接createWebSocket(){const websocket =wx.connectSocket({url: 'ws://localhost:8080/ws/'+this.data.courid+"/"+this.data.userid,success: () => {console.log('WebSocket连接成功');},fail: (error) => {console.log('WebSocket连接失败', error);}})// 监听WebSocket连接打开事件websocket.onOpen(() => {console.log('WebSocket连接已打开');console.log('readyState:', websocket.readyState); // 应该输出: OPEN (1) });// 监听WebSocket接收到服务器消息事件websocket.onMessage((message) => {console.log('收到服务器消息', message);// 处理服务器返回的消息,例如更新页面数据等操作var comment=JSON.parse(message.data)comment.createtime=this.timestampToDate(comment.createtime)var comment_list=this.data.comment_list;comment_list.push(comment)this.setData({comment_list})});// 监听WebSocket连接关闭事件websocket.onClose(() => {console.log('WebSocket连接已关闭');});// 监听WebSocket错误事件websocket.onError((error) => {console.log('WebSocket发生错误', error);});// 将WebSocket对象保存到data中this.setData({websocket: websocket,});},// 发送消息到WebSocket服务器if (this.data.websocket && this.data.websocket.readyState === 1) {this.data.websocket.send({data: JSON.stringify(message), // 要发送的消息内容success: () => {console.log('消息发送成功');},fail: (error) => {console.log('消息发送失败', error);},});} else {console.log('WebSocket连接未打开或不存在');}
参考
Java实战:Spring Boot实现WebSocket实时通信
WebSocket和Stomp协议
个人博客——使用websocket实现评论实时展示
客户端连接spring websocket 返回403错误
微信小程序 内容评论-回复评论-回复回复的实现
相关文章:

springboot+websocket+微信小程序实现评论区功能
springbootwebsocket微信小程序实现评论区功能 WebSocketSTOMP协议具体实现1.在pom文件中添加Spring WebSocket依赖2. 创建WebSocket配置类3.接收发送消息4.前端 参考 WebSocket 1. 什么是WebSocket? WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双…...

【项目】如何在面试中介绍自己的项目经验(附如何解决未知的问题成长路线)
【项目】如何在面试中介绍自己的项目经验(附如何解决未知的问题&成长路线) 文章目录 1、为什么要准备项目介绍?2、如何准备一份好的项目介绍?3、如何回答项目提问?4、如何避免低级错误?5、如何通过引导…...

解决Selenium元素拖拽不生效Bug
前几天在使用Selenium进行元素拖拽操作时,发现Selenium自带的元素拖拽方法(dragAndDrop())不生效,网上的回答也是五花八门,比较混乱,尝试了以下几种方法均无法解决。 方案1:通过dragAndDrop()方…...

提示工程中的10个设计模式
我们可以将提示词定义为向大型语言模型(Large Language Model,LLM)提供的一个查询或一组指令,这些指令随后使模型能够维持一定程度的自定义或增强,以改进其功能并影响其输出。我们可以通过提供细节、规则和指导来引出更有针对性的输出&#x…...

提高网站安全性,漏洞扫描能带来什么帮助
随着互联网的蓬勃发展,网站已经成为人们获取信息、交流思想、开展业务的重要平台。然而,与之伴随的是日益严重的网络安全问题,包括恶意攻击、数据泄露、隐私侵犯等。 为了保障网站的安全性,提前做好网站的安全检测非常有必要&…...

不要再使用 @Builder 注解了!有深坑呀!
曾经,我在《千万不要再随便使用 lombok 的 Builder 了!》 一文中提到 Builder 注解的其中一个大坑会导致默认值失效! 最近阅读了 《Oh !! Stop using Builder》 发现 Builder 的问题还不止一个,Builder 会让人误以为是遵循构建器…...

《UE5_C++多人TPS完整教程》学习笔记31 ——《P32 角色移动(Character Movement)》
本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P32 角色移动(Character Movement)》 的学习笔记,该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者&…...

怎么使用jwt,token以及redis进行续期?
怎么使用jwt,token以及redis进行续期? 什么是jwt? 什么是token? 结合 JWT、Token 和 Redis 进行续期的一般步骤: 生成 JWT: 用户登录成功后,服务器生成一个 JWT,并返回给客户端。 import io.jsonwebtok…...

AI日报:北大Open Sora视频生成更强了;文心一言可以定制你自己的声音;天工 SkyMusic即将免费开放;
🤖📱💼AI应用 北大Open Sora视频生成更强了!时长可达10秒,分辨率更高 【AiBase提要:】 ⭐️ Open-Sora-Plan v1.0.0模型发布 显著提升视频生成质量和文本控制能力 ⭐️ 支持华为昇腾910b芯片,提升运行效率和质量。 ⭐…...

替换空格(替换特定字符)
😀前言 在字符串处理中,经常会遇到需要替换特定字符的情况。本文将介绍一道经典的字符串替换问题:将字符串中的空格替换成 “%20”。我们将探讨一种高效的解决方法,通过倒序遍历字符串来实现原地替换,避免额外空间的开…...

ctfshow web入门 php特性 web123--web139
web123 必须传CTF_SHOW,CTF_SHOW.COM 不能有fl0g 在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,…...

pta L1-002 打印沙漏
L1-002 打印沙漏 分数 20 全屏浏览 切换布局 作者 陈越 单位 浙江大学 本题要求你写个程序把给定的符号打印成沙漏的形状。例如给定17个“*”,要求按下列格式打印 ************ *****所谓“沙漏形状”,是指每行输出奇数个符号;各行符号中…...

【简单讲解下PHP AES加解密示例】
🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共…...

设计模式总结-外观模式(门面模式)
外观模式 模式动机模式定义模式结构外观模式实例与解析实例一:电源总开关实例二:文件加密 模式动机 引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦…...

LiveGBS流媒体平台GB/T28181常见问题-系统服务日志如何配置日志个数日志路径日志时长web操作日志操如何配置保留天数及过滤
LiveGBS系统服务日志如何配置日志个数日志路径日志时长web操作日志操如何配置保留天数及过滤 1、系统服务日志1.1、日志目录1.2、配置日志文件个数及记录时间1.3、配置日志文件路径 2、Web 操作日志2.1、配置保留天数2.2、配置不记录操作日志2.1.1、不记录所有2.1.2、不记录指定…...

es6:set()和weakset()
一、Map() 1.1 简介 ES6 提供了 Set 数据结构,它类似于数组,但是值是唯一没有重复的。 我们可以通过 new Set()去创建它。 1.2. Set的创建、设置与获取 <script> const set new Set(); console.log(set.add(1)); //Set { 1 } …...

C#仿OutLook的特色窗体设计
目录 1. 资源图片准备 2. 设计流程: (1)用MenuStrip控件设计菜单栏 (2)用ToolStrip控件设计工具栏 (3)用StatusStrip控件设计状态栏 (4)ImageList组件装载树节点图…...

Jmeter的使用
Jmeter的使用 1.Jmeter简介 以下内容来自Jmeter中文网http://www.jmeter.com.cn/jieshao,很好的解释了Jmeter的作用: Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试…...

【蓝桥杯第十届省赛B】(部分详解)
特别数的和 #include <iostream> #include <string> using LLlong long; using namespace std;int main() {LL n;cin >> n;LL cnt 0;for (LL i 1; i < n; i) {string s to_string(i);for (LL j 0; j < s.size(); j) {if (s[j] 2 || s[j] 0 || s…...

计算机研究生规划
一、计算机研究生技术栈 两条腿走路: 左侧工程实践能力:要掌握python编程语言,它和机器学习、神经网络(这两门几乎是必须掌握的技能)的学习有很大关系 右侧学术创新能力 二、编程语言能力提升 左边基础,右边教你写…...

针孔相机、鱼眼相机、全景相机
先进性简述,后续慢慢会补充1. 针孔相机: 针孔相机是一种基于针孔成像原理的传统相机,它使用一个非常小的孔径(即“针孔”)来限制光线进入相机的方式。 这种相机通常具有简单的结构,由一个孔径较小的光学元…...

HTML5+CSS3+JS小实例:圣诞按钮
实例:圣诞按钮 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0&…...

【深度学习基础】
打基础日常记录 CNN基础知识1. 感知机2. DNN 深度神经网络(全连接神经网络)DNN 与感知机的区别DNN特点,全连接神经网络DNN前向传播和反向传播 3. CNN结构【提取特征分类】4. CNN应用于文本 RNN基础1. RNN的本质 词向量模型word2Vec1. 自然语言…...

银行业架构网络BIAN (Banking IndustryArchitecture Network)详细介绍
BIAN ( The Banking Industry Architecture Network) 是一个业界多方协作的非营利性组织,由全球领先银行、技术提供商、顾问和学者组成,定义了一个用以简化和标准化核心银行体系结构的银行技术框架。这一框架基于面向服务的架构 (SOA) 原则,银…...

[尚硅谷 flink] 基于时间的合流——双流联结(Join)
文章目录 8.1 窗口联结(Window Join)8.2 **间隔联结(Interval Join)** 8.1 窗口联结(Window Join) Flink为基于一段时间的双流合并专门提供了一个窗口联结算子,可以定义时间窗口,并…...

怎样恢复已删除的照片?教你3个方法,一键恢复!
很多人喜欢以拍照的形式记录生活,手机里的照片就很容易堆积成山,但当内存不够用时就不得不选择删除。可是这些美好的照片始终是很多人心中抹不去的记忆,那么该怎样恢复已删除的照片呢?下面几招,教你一键恢复࿰…...

植物糖基转移酶数据库-23年-地表最强系列-文献精读-6
pUGTdb: A comprehensive database of plant UDP-dependent glycosyltransferases pUGTdb:植物UDP依赖糖基转移酶的全面数据库 一篇关于植物糖基转移数据库的综述,地表最强,总结的最全面的版本之一,各位看官有推荐请留言评论区~…...

虚拟机打不开
问题 另一个程序已锁定文件的一部分,进程无法访问 打不开磁盘“G:\centeros\hadoop104kl\hadoop100-cl2.vmdk”或它所依赖的某个快照磁盘。 模块“Disk”启动失败。 未能启动虚拟机。 原因 前一次非正常关闭虚拟机导致.lck 文件是VMWare软件的一种磁盘锁文件&…...

MySQL数据库版本为5.5.62,时间戳超出2038年1月19日的解决方案
MySQL数据库版本是 5.5.62,已设置字段的类型为BIGINT,使用FROM_UNIXTIME()函数来转换时间戳,返回NULL。 SELECT FROM_UNIXTIME(1617970800)SELECT FROM_UNIXTIME(2185743121)MySQL数据库版本为5.5.62,已设置字段的类型为BIGINT&a…...

C++20 semaphore(信号量) 详解
头文件在C20中是并发库技术规范(Technical Specification, TS)的一部分。信号量是同步原语,帮助控制多线程程序中对共享资源的访问。头文件提供了标准C方式来使用信号量。 使用环境 Windows:VS中打开项目属性,修改C语…...