Vert.x复健,写个WebSocket聊天室
前端和具体的聊天室内状态流转/Session维护逻辑就不贴了,感觉意义不大,主要放上来的还是Vert.x相关操作。
使用的是协议升级 (HTTP –> WebSocket)的方式,之所以用这种方式,是为了在代码类获取到一些前端传来的信息,一般是当前用户的标识符,当然由于我这就是个Demo,并没有用户系统,所以其实就是前端随机出来的UUID,用户名也是程序内存里自增出来的,看一乐就行。
环境
Vert.x:4.5.10
JDK:17
Maven
引入以下两个依赖:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>${vertx.version}</version>
</dependency>
然后是Vert.x打包成 fat jar (可通过 java -jar 执行的jar)的maven配置
需要注意的是,我这里并没有使用官方推荐的Verticle方式部署,而是用的main方法部署。
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>false</minimizeJar>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<!-- <Main-Class>io.vertx.core.Launcher</Main-Class>-->
<!-- <Main-Verticle>com.skypyb.VertxMain</Main-Verticle>-->
<Main-Class>com.skypyb.VertxMain</Main-Class>
</manifestEntries>
</transformer>
</transformers>
<artifactSet />
<outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar</outputFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
实现代码
入口:
public class VertxMain {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
HttpServer httpServer = vertx.createHttpServer();
Router router = Router.router(vertx);
router.route("/chatroom").handler(new ChatRoomWebsocketHandler());
router.route("/*").handler(StaticHandler.create());
//代理Vue路由
router.errorHandler(404, ctx -> {
if (ctx.request().method() == HttpMethod.GET) {
ctx.reroute("/index.html");
} else {
ctx.json(new JsonObject().put("code", 404).put("msg", "找不到"));
}
});
httpServer.requestHandler(router).listen(8888);
}
}
这里解释一下,errorHandler 用来代理Vue打包出来的静态文件,只要是Java程序处理不了的,就默认委派给Vue处理。
单纯是我自个写的时候为了测试jar打包,所以直接在Vue打包完后,把他丢进了 Vert.x 程序里,一般不会这样整,常见的前后端基本都是分开端口部署的嘛。
ws处理程序:
public class ChatRoomWebsocketHandler implements Handler<RoutingContext> {
private final AtomicInteger userNum = new AtomicInteger(0);
private final Map<String, JsonObject> userJsonMapping = new HashMap<>();
private final JsonObject DEFAULT_USER = new JsonObject().put("userId", "NAN").put("userName", "备用");
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd: HH:mm:ss");
@Override
public void handle(RoutingContext context) {
HttpServerRequest request = context.request();
// 获取每一个链接的ID
String id = request.getParam("user-id");
String format = LocalDateTime.now().format(dateTimeFormatter);
System.out.println(format + " " + request.uri() + " websocket链接, user-id: " + id);
//协议升级
Future<ServerWebSocket> future = request.toWebSocket();
future.onFailure(event -> System.out.println(event.getMessage()));
future.onSuccess(webSocket -> {
System.out.println(format + " websocket success, user-id: " + id);
webSocket.writePing(Buffer.buffer("PING"));
//进入聊天室
ChatRoom chatRoom = ChatRoomManager.getInstance().enterChatRoom(id, webSocket);
//构造一个Json对象给当前用户, 给他个名字
JsonObject object = new JsonObject();
object.put("userId", id).put(format + " userName", "用户" + userNum.incrementAndGet());
userJsonMapping.put(id, object);
// WebSocket 消息
webSocket.frameHandler(handler -> {
System.out.println("--消息--, id:" + id);
if (handler.isClose()) return;
String textData = handler.textData();
System.out.println(String.format("%s -> %s", id, textData));
//构造消息
JsonObject currentUser = userJsonMapping.getOrDefault(id, DEFAULT_USER);
JsonObject message = new JsonObject();
message.put("sender", currentUser.getString("userId"))
.put("userName", currentUser.getString("userName"))
.put("message", textData);
//给当前聊天室的每一个WebSocket连接发送消息
chatRoom.fanout(message.encode());
});
// 客户端WebSocket 关闭时,退出聊天室
webSocket.closeHandler(handler -> {
System.out.println(format + " --客户端关闭--, id:" + id);
ChatRoomManager.getInstance().quitChatRoom(id, webSocket);
userJsonMapping.remove(id);
});
});
}
}
ChatRoomManager 相关是用来做具体的链接维护、逻辑实现。
反正就是里边会维护 ServerWebSocket 这个对象,然后调用他的方法比如 ServerWebSocket#writeTextMessage 之类的,和 Vert.x 没有太大关系,单一职责嘛,就不多说了。
