Spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)

Spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)

Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)


网上好多例子都是群发的,本文实现一对一的发送,给指定客户端进行消息推送


1、本文使用到netty-socketio开源库,以及,所以首先在pom.xml中添加相应的依赖库

[html]

  1. <dependency>
  2. <groupId>com.corundumstudio.socketio</groupId>
  3. <artifactId>netty-socketio</artifactId>
  4. <version>1.7.11</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-data-jpa</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>mysql</groupId>
  12. <artifactId>mysql-connector-java</artifactId>
  13. </dependency>    
    2、修改application.properties, 添加端口及主机数据库连接等相关配置,

[html]

  1. wss.server.port=8081
  2. wss.server.host=localhost
  3. spring.datasource.url = jdbc:mysql://127.0.0.1:3306/springlearn
  4. spring.datasource.username = root
  5. spring.datasource.password = root
  6. spring.datasource.driverClassName = com.mysql.jdbc.Driver
  7. # Specify the DBMS
  8. spring.jpa.database = MYSQL
  9. # Show or not log for each sql query
  10. spring.jpa.show-sql = true
  11. # Hibernate ddl auto (create, create-drop, update)
  12. spring.jpa.hibernate.ddl-auto = update
  13. # Naming strategy
  14. spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
  15. # stripped before adding them to the entity manager)
  16. spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect    
    3、修改Application文件,添加nettysocket的相关配置信息

[java]

  1. package com.xiaofangtech.sunt;
  2. import org.springframework.beans.factory.annotation.Value;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.context.annotation.Bean;
  6. import com.corundumstudio.socketio.AuthorizationListener;
  7. import com.corundumstudio.socketio.Configuration;
  8. import com.corundumstudio.socketio.HandshakeData;
  9. import com.corundumstudio.socketio.SocketIOServer;
  10. import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
  11. @SpringBootApplication
  12. public class NettySocketSpringApplication {
  13. @Value("${wss.server.host}")
  14. private String host;
  15. @Value("${wss.server.port}")
  16. private Integer port;
  17. @Bean
  18. public SocketIOServer socketIOServer()
  19. {
  20. Configuration config = new Configuration();
  21. config.setHostname(host);
  22. config.setPort(port);
  23. //该处可以用来进行身份验证
  24. config.setAuthorizationListener(new AuthorizationListener() {
  25. @Override
  26. public boolean isAuthorized(HandshakeData data) {
  27. //http://localhost:8081?username=test&password=test
  28. //例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证
  29. //              String username = data.getSingleUrlParam("username");
  30. //              String password = data.getSingleUrlParam("password");
  31. return true;
  32. }
  33. });
  34. final SocketIOServer server = new SocketIOServer(config);
  35. return server;
  36. }
  37. @Bean
  38. public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
  39. return new SpringAnnotationScanner(socketServer);
  40. }
  41. public static void main(String[] args) {
  42. SpringApplication.run(NettySocketSpringApplication.class, args);
  43. }
  44. }    
    4、添加消息结构类MessageInfo.java

[java]

  1. package com.xiaofangtech.sunt.message;
  2. public class MessageInfo {
  3. //源客户端id
  4. private String sourceClientId;
  5. //目标客户端id
  6. private String targetClientId;
  7. //消息类型
  8. private String msgType;
  9. //消息内容
  10. private String msgContent;
  11. public String getSourceClientId() {
  12. return sourceClientId;
  13. }
  14. public void setSourceClientId(String sourceClientId) {
  15. this.sourceClientId = sourceClientId;
  16. }
  17. public String getTargetClientId() {
  18. return targetClientId;
  19. }
  20. public void setTargetClientId(String targetClientId) {
  21. this.targetClientId = targetClientId;
  22. }
  23. public String getMsgType() {
  24. return msgType;
  25. }
  26. public void setMsgType(String msgType) {
  27. this.msgType = msgType;
  28. }
  29. public String getMsgContent() {
  30. return msgContent;
  31. }
  32. public void setMsgContent(String msgContent) {
  33. this.msgContent = msgContent;
  34. }
  35. }    
    5、添加客户端信息,用来存放客户端的sessionid

[java]

  1. package com.xiaofangtech.sunt.bean;
  2. import java.util.Date;
  3. import javax.persistence.Entity;
  4. import javax.persistence.Id;
  5. import javax.persistence.Table;
  6. import javax.validation.constraints.NotNull;
  7. @Entity
  8. @Table(name="t_clientinfo")
  9. public class ClientInfo {
  10. @Id
  11. @NotNull
  12. private String clientid;
  13. private Short connected;
  14. private Long mostsignbits;
  15. private Long leastsignbits;
  16. private Date lastconnecteddate;
  17. public String getClientid() {
  18. return clientid;
  19. }
  20. public void setClientid(String clientid) {
  21. this.clientid = clientid;
  22. }
  23. public Short getConnected() {
  24. return connected;
  25. }
  26. public void setConnected(Short connected) {
  27. this.connected = connected;
  28. }
  29. public Long getMostsignbits() {
  30. return mostsignbits;
  31. }
  32. public void setMostsignbits(Long mostsignbits) {
  33. this.mostsignbits = mostsignbits;
  34. }
  35. public Long getLeastsignbits() {
  36. return leastsignbits;
  37. }
  38. public void setLeastsignbits(Long leastsignbits) {
  39. this.leastsignbits = leastsignbits;
  40. }
  41. public Date getLastconnecteddate() {
  42. return lastconnecteddate;
  43. }
  44. public void setLastconnecteddate(Date lastconnecteddate) {
  45. this.lastconnecteddate = lastconnecteddate;
  46. }
  47. }    
    6、添加查询数据库接口ClientInfoRepository.java

[java]

  1. package com.xiaofangtech.sunt.repository;
  2. import org.springframework.data.repository.CrudRepository;
  3. import com.xiaofangtech.sunt.bean.ClientInfo;
  4. public interface ClientInfoRepository extends CrudRepository<ClientInfo, String>{
  5. ClientInfo findClientByclientid(String clientId);
  6. }    

7、添加消息处理类MessageEventHandler.

[java]

  1. package com.xiaofangtech.sunt.message;
  2. import java.util.Date;
  3. import java.util.UUID;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.stereotype.Component;
  6. import com.corundumstudio.socketio.AckRequest;
  7. import com.corundumstudio.socketio.SocketIOClient;
  8. import com.corundumstudio.socketio.SocketIOServer;
  9. import com.corundumstudio.socketio.annotation.OnConnect;
  10. import com.corundumstudio.socketio.annotation.OnDisconnect;
  11. import com.corundumstudio.socketio.annotation.OnEvent;
  12. import com.xiaofangtech.sunt.bean.ClientInfo;
  13. import com.xiaofangtech.sunt.repository.ClientInfoRepository;
  14. @Component
  15. public class MessageEventHandler
  16. {
  17. private final SocketIOServer server;
  18. @Autowired
  19. private ClientInfoRepository clientInfoRepository;
  20. @Autowired
  21. public MessageEventHandler(SocketIOServer server)
  22. {
  23. this.server = server;
  24. }
  25. //添加connect事件,当客户端发起连接时调用,本文中将clientid与sessionid存入数据库
  26. //方便后面发送消息时查找到对应的目标client,
  27. @OnConnect
  28. public void onConnect(SocketIOClient client)
  29. {
  30. String clientId = client.getHandshakeData().getSingleUrlParam("clientid");
  31. ClientInfo clientInfo = clientInfoRepository.findClientByclientid(clientId);
  32. if (clientInfo != null)
  33. {
  34. Date nowTime = new Date(System.currentTimeMillis());
  35. clientInfo.setConnected((short)1);
  36. clientInfo.setMostsignbits(client.getSessionId().getMostSignificantBits());
  37. clientInfo.setLeastsignbits(client.getSessionId().getLeastSignificantBits());
  38. clientInfo.setLastconnecteddate(nowTime);
  39. clientInfoRepository.save(clientInfo);
  40. }
  41. }
  42. //添加@OnDisconnect事件,客户端断开连接时调用,刷新客户端信息
  43. @OnDisconnect
  44. public void onDisconnect(SocketIOClient client)
  45. {
  46. String clientId = client.getHandshakeData().getSingleUrlParam("clientid");
  47. ClientInfo clientInfo = clientInfoRepository.findClientByclientid(clientId);
  48. if (clientInfo != null)
  49. {
  50. clientInfo.setConnected((short)0);
  51. clientInfo.setMostsignbits(null);
  52. clientInfo.setLeastsignbits(null);
  53. clientInfoRepository.save(clientInfo);
  54. }
  55. }
  56. //消息接收入口,当接收到消息后,查找发送目标客户端,并且向该客户端发送消息,且给自己发送消息
  57. @OnEvent(value = "messageevent")
  58. public void onEvent(SocketIOClient client, AckRequest request, MessageInfo data)
  59. {
  60. String targetClientId = data.getTargetClientId();
  61. ClientInfo clientInfo = clientInfoRepository.findClientByclientid(targetClientId);
  62. if (clientInfo != null && clientInfo.getConnected() != 0)
  63. {
  64. UUID uuid = new UUID(clientInfo.getMostsignbits(), clientInfo.getLeastsignbits());
  65. System.out.println(uuid.toString());
  66. MessageInfo sendData = new MessageInfo();
  67. sendData.setSourceClientId(data.getSourceClientId());
  68. sendData.setTargetClientId(data.getTargetClientId());
  69. sendData.setMsgType("chat");
  70. sendData.setMsgContent(data.getMsgContent());
  71. client.sendEvent("messageevent", sendData);
  72. server.getClient(uuid).sendEvent("messageevent", sendData);
  73. }
  74. }
  75. }    
    8、添加ServerRunner.java

[java]

  1. package com.xiaofangtech.sunt.message;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.boot.CommandLineRunner;
  4. import org.springframework.stereotype.Component;
  5. import com.corundumstudio.socketio.SocketIOServer;
  6. @Component
  7. public class ServerRunner implements CommandLineRunner {
  8. private final SocketIOServer server;
  9. @Autowired
  10. public ServerRunner(SocketIOServer server) {
  11. this.server = server;
  12. }
  13. @Override
  14. public void run(String... args) throws Exception {
  15. server.start();
  16. }
  17. }    
    9、工程结构
www.zeeklog.com  - Spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)



10、运行测试

1) 添加基础数据,中预置3个客户端testclient1,testclient2,testclient3

www.zeeklog.com  - Spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)



2) 创建客户端文件index.html,index2.html,index3.html分别代表testclient1 testclient2 testclient3三个用户

本文直接修改的https://github.com/mrniko/netty-socketio-demo/tree/master/client 中的index.html文件

其中clientid为发送者id, targetclientid为目标方id,本文简单的将发送方和接收方写死在html文件中

使用 以下代码进行连接    [html]

  1. io.connect('http://localhost:8081?clientid='+clientid);

index.html 文件内容如下    [html]

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <title>Demo Chat</title>
  6. <link href="bootstrap.css" rel="stylesheet">
  7. <style>
  8. body {
  9. padding:20px;
  10. }
  11. #console {
  12. height: 400px;
  13. overflow: auto;
  14. }
  15. .username-msg {color:orange;}
  16. .connect-msg {color:green;}
  17. .disconnect-msg {color:red;}
  18. .send-msg {color:#888}
  19. </style>
  20. <script src="js/socket.io/socket.io.js"></script>
  21. <script src="js/moment.min.js"></script>
  22. <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
  23. <script>
  24. var clientid = 'testclient1';
  25. var targetClientId= 'testclient2';
  26. var socket =  io.connect('http://localhost:8081?clientid='+clientid);
  27. socket.on('connect', function() {
  28. output('<span class="connect-msg">Client has connected to the server!</span>');
  29. });
  30. socket.on('messageevent', function(data) {
  31. output('<span class="username-msg">' + data.sourceClientId + ':</span> ' + data.msgContent);
  32. });
  33. socket.on('disconnect', function() {
  34. output('<span class="disconnect-msg">The client has disconnected!</span>');
  35. });
  36. function sendDisconnect() {
  37. socket.disconnect();
  38. }
  39. function sendMessage() {
  40. var message = $('#msg').val();
  41. $('#msg').val('');
  42. var jsonObject = {sourceClientId: clientid,
  43. targetClientId: targetClientId,
  44. msgType: 'chat',
  45. msgContent: message};
  46. socket.emit('messageevent', jsonObject);
  47. }
  48. function output(message) {
  49. var currentTime = "<span class='time'>" +  moment().format('HH:mm:ss.SSS') + "</span>";
  50. var element = $("<div>" + currentTime + " " + message + "</div>");
  51. $('#console').prepend(element);
  52. }
  53. $(document).keydown(function(e){
  54. if(e.keyCode == 13) {
  55. $('#send').click();
  56. }
  57. });
  58. </script>
  59. </head>
  60. <body>
  61. <h1>Netty-socketio Demo Chat</h1>
  62. <br/>
  63. <div id="console" class="well">
  64. </div>
  65. <form class="well form-inline" onsubmit="return false;">
  66. <input id="msg" class="input-xlarge" type="text" placeholder="Type something..."/>
  67. <button type="button" onClick="sendMessage()" class="btn" id="send">Send</button>
  68. <button type="button" onClick="sendDisconnect()" class="btn">Disconnect</button>
  69. </form>
  70. </body>
  71. </html>    

3、本例测试时

testclient1 发送消息给 testclient2

testclient2 发送消息给 testclient1

testclient3发送消息给testclient1

运行结果如下

www.zeeklog.com  - Spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)


www.zeeklog.com  - Spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)
www.zeeklog.com  - Spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)


 

Read more

【记录】Github|Github账号意外被封以及不需要手机号解封的全过程(被封原因:一台设备上登录过多个账号)

【记录】Github|Github账号意外被封以及不需要手机号解封的全过程(被封原因:一台设备上登录过多个账号)

文章目录 * 前言 * 解封全过程 * 提交工单 * 页面一 账号微死 * 工单内容 * 页面二 账号微活 * 工单内容 * 毫无感情正经申诉版本(推荐) * 带情绪的申诉版本 * 邮件battle过程 * Round 1:搞清楚怎么被封的 * GitHub Support * 分析 * Round 2: 删除已知小号然后道歉 * Me * 分析 * Round 3: 处理所有小号 * Github Support * 分析 * Me * Round 4: 后续关于follower和fork权限等问题 * Github Support * Me * Github Support * 尾声 前言 一开始被封我以为是我发表了不当言论,因为我刚发一条discussion就被封了,但是后面发现这不是根本原因。应该是之前有人看我【记录】Copilot|Github Copilot重新学生认

By Ne0inhk

GitHub 爆火的 30+ 个 OpenClaw 真实场景全拆解

大家好,我是玄姐。 最近,霸榜 GitHub 的 OpenClaw 彻底火出圈了。作为一款能直接“看懂”屏幕、操控鼠标键盘的本地 AI Agent 框架,它证明了 AI 已经从“云端对话框”进化成了“超级打工人”。 很多读者在后台留言:“装是装上了,但我到底该用它干嘛?” 没问题。今天我们不搞虚的,直接把 GitHub 上开源的那份最具参考价值的 30+ 真实使用案例进行完整拆解。这 30 个案例不是玩具 Demo,而是实实在在运行在海外开发者、业务运营和数字游民电脑里的生产力工作流。 PS: 为了让大家更深度的搞懂 OpenClaw 和 Skills 技术体系实践,我会开场直播,欢迎点击预约,直播见。 为了方便阅读,我将这 30 个硬核案例分为了五大核心场景。

By Ne0inhk
深度盘点:GitHub 上十大必装 Claude Skill,让你的 AI 助手效率提升 4 倍

深度盘点:GitHub 上十大必装 Claude Skill,让你的 AI 助手效率提升 4 倍

深度盘点:GitHub 上十大必装 Claude Skill,让你的 AI 助手效率提升 4 倍 Claude Code 已经很强大,但如果搭配这些精心设计的 Skills,它将变身超级生产力工具。本文为你深度解析 GitHub 上最受欢迎的 10 大 Claude Skills,帮助你找到最适合的配置方案。 引言:为什么 Claude Skills 如此重要? 在 2025-2026 年,Claude Code 生态经历了爆发式增长。Skills 系统的出现,让 Claude 从一个"对话助手"升级为"专业工具"。通过安装不同的 Skills,你可以:

By Ne0inhk
Git 到底是干啥的?零基础小白听完都懂了并且轻松驾驭它

Git 到底是干啥的?零基础小白听完都懂了并且轻松驾驭它

git,通俗的来说就是一种用来多人文件版本合作的工具,但是对一些非程序员的项目小白或者没有程序基础的但是想要入行做程序员的人来说,完完全全理解起来稍微有点困难。这篇文章不像很多文章一样是枯涩的码字教学。现在,我们就用最通俗易懂的方式,让你从零基础理解他,并且使用他。这种教学方法不是把你当白痴的教学方法,反而是让你快速入门深刻理解它,并记住它的教学方法。因为可能说得比较详细,篇幅较长,还得请你耐心的把他看完。 一、git的作用 1、git的版本控制 文件永远不会只有一个版本,这句话我们似乎用亲身经历证明过。你是否有过以下经历👇 📘论文会有“终稿v1、终稿v2、终稿最终版”、 ✍设计稿会有“改版A、改版B、改版C”、 🧺甚至自己写的文章也会来回改十几遍。 🥚更不用说单独只通过一个本地夹操刀一个大型项目了 突然有一天你觉得你的论文、设计稿、文章、项目某一个节点开始脱离了原本的方向或者发生了一些错误,但是你已经对其进行多处修改了,单独再修改不仅费事废经历,还容易发生遗漏。 你或许信誓旦旦的告诉我,你可以这样做。。。👇 论文_最终v1.docx 论文_

By Ne0inhk