之前已经创建好第一个属于我们的 Actor 系统, 但是内部完全没有任何交互,
现在就是开始设计业务来模拟日常业务, 这里需要定义设计个 世界boss 功能.
世界boos每分钟都会刷新血量websocket每10s都会推送客户端心跳包- 每个新连接
websocket都支持攻击1次, 随机返回攻击数值并扣除血量 - 如果结算时间到就会给在线会话推送攻击的扣除最高血量
这里就是简单的初步游戏业务功能, 这里需要设计网络运输网关层组件:
<!-- 第三方依赖 -->
<dependencies>
<!-- 之前的其他组件 -->
<!-- SpringWebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
之后就是改造 main 入口和配置全局属性:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
/**
* 游戏项目启动入口, 现在托管给 SpringBoot 来管理
* 默认启动WebSocket设置
*/
@EnableWebSocket
@SpringBootApplication
public class FusionActorApplication {
/**
* 启动项目方法
*
* @param args 启动方法
*/
public static void main(String[] args) {
SpringApplication.run(FusionActorApplication.class, args);
}
}
WebSocket 需要导出配置文件可以被识别到, 也就会是不再采用硬编码形式写入而是外部配置:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
/**
* WebSocket 配置属性
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "fusion.websocket")
public class WebSocketProperties {
/**
* 请求路径
*/
private String path = "/";
/**
* 传输数据缓存大小
*/
private Integer bufferSize = 8192;
/**
* 待机主动中断时间
*/
private Long idleTimeout = 30000L;
/**
* 跨域地址
*/
private String allowOrigins = "*";
/**
* 运行句柄
*/
private Class<? extends WebSocketHandler> handler;
}
之后就能在 application.yml 配置挂起的 WebSockt 参数:
fusion:
websocket:
handler: com.meteorcat.fusion.websocket.WebSocketApplication
path: /ws
handler 就是接下来我们需要编写个 TextWebSocketHandler(文本流传输) :
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.pekko.actor.typed.ActorSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* Text类型的WebSocket句柄
*/
@Slf4j
@Component
public class WebSocketApplication extends TextWebSocketHandler {
/**
* Actor全局系统
*/
final ActorSystem<String> system;
/**
* 全局JSON解析器
*/
final ObjectMapper objectMapper;
/**
* 构建方法
*
* @param objectMapper JSON解析器
*/
@Autowired
public WebSocketApplication(ActorSystem<String> system, ObjectMapper objectMapper) {
this.system = system;
this.objectMapper = objectMapper;
}
/**
* 连接回调
*/
@Override
public void afterConnectionEstablished(
@NonNull WebSocketSession session
) throws Exception {
log.info("Established: {}", session.getId());
}
/**
* 消息回调
* 以 JSON:{ id: number, data: {} } 来做消息传递
*/
@Override
protected void handleTextMessage(
@NonNull WebSocketSession session,
@NonNull TextMessage message
) throws Exception {
final String payload = message.getPayload();
if (payload.isEmpty()) {
return;
}
// 确认必须带 Object 并且格式为 { id: number, data: {} }
final JsonNode node = objectMapper.readTree(payload);
if (!node.isObject() || !node.has("id") || !node.has("data")) {
return;
}
// 提取数据
final JsonNode id = node.get("id");
final JsonNode data = node.get("data");
if (!id.isNumber() || !data.isObject()) {
return;
}
log.info("frame received({}): {}", id.asInt(), data);
// 先简单实现 data 文本推送 actor
// 后续再扩展出数据自定义格式
system.tell(data.toString());
}
/**
* 关闭回调
*/
@Override
public void afterConnectionClosed(
WebSocketSession session,
@NonNull CloseStatus status
) throws Exception {
log.info("Closed({}): {}", status, session.getId());
}
}
这里 @Component 仅仅作为全局唯一实例, 还要将参数和这个实例化绑定一起:
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* WebSocket 配置中心
*/
@Data
@Configuration
public class WebSocketConfiguration implements WebSocketConfigurer {
/**
* SpringBoot上下文
*/
private final ApplicationContext context;
/**
* 配置属性
*/
private final WebSocketProperties properties;
/**
* 构造方法
*/
@Autowired
WebSocketConfiguration(
ApplicationContext context,
WebSocketProperties properties
) {
this.context = context;
this.properties = properties;
}
/**
* WebSocket配置
*
* @param registry 设置器
*/
@Override
public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
registry.addHandler(context.getBean(properties.getHandler()), properties.getPath())
.setAllowedOrigins(properties.getAllowOrigins());
}
}
这里就是采用
SpringBoot当作底层工具好处, 内部可以通过ApplicationContext扫描到泛型类并实现自动加载.
现在需要该写 Actor 功能, 同样采用 application.yml 让其能够追加配置:
import lombok.Data;
import org.apache.pekko.actor.typed.ActorSystem;
import org.apache.pekko.actor.typed.javadsl.AbstractBehavior;
import org.apache.pekko.actor.typed.javadsl.ActorContext;
import org.apache.pekko.actor.typed.javadsl.Behaviors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* 全局 Actor 系统配置中心
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "fusion.actor")
public class ActorProperties {
/**
* Actor系统名称
*/
private String name;
/**
* Actor类型
*/
public Class<? extends AbstractBehavior<?>> behavior;
/**
* 实例句柄
*/
@Bean
public ActorSystem<?> factory() {
return ActorSystem.create(
Behaviors.setup((ctx) -> {
AbstractBehavior<?> instance = behavior.getConstructor(ActorContext.class).newInstance(ctx);
return instance.unsafeCast();
}),
this.name
);
}
}
可以通过在 application.yml 添加配置:
fusion:
actor:
name: fusion
behavior: com.meteorcat.fusion.actor.ActorSupervisor
behavior 就是我们再次编写好的 Actor 系统工具类:
import org.apache.pekko.actor.typed.Behavior;
import org.apache.pekko.actor.typed.javadsl.AbstractBehavior;
import org.apache.pekko.actor.typed.javadsl.ActorContext;
import org.apache.pekko.actor.typed.javadsl.Receive;
import org.slf4j.Logger;
/**
* 简单的 Actor 服务
*/
public class ActorSupervisor extends AbstractBehavior<String> {
/**
* 日志句柄
*/
final Logger log = getContext().getLog();
/**
* 构造方法
*
* @param context Actor上下文
*/
public ActorSupervisor(ActorContext<String> context) {
super(context);
}
/**
* 构建回调匹配
*
* @return Receive
*/
@Override
public Receive<String> createReceive() {
return newReceiveBuilder()
.onMessage(String.class, this::onMessage)
.build();
}
/**
* 回调内部
*
* @param msg 消息
* @return Behavior
*/
private Behavior<String> onMessage(String msg) {
log.info("ECHO : {}!", msg);
return this;
}
}
最后就是激动人心启动服务的时刻, 我本地启动打开 Postman 推送一条消息 { "id":1001, "data":{ "flag":100 }} 返回数据:
2025-04-19T23:15:26.105+08:00 INFO 2005432 --- [io-18080-exec-1] c.m.f.websocket.WebSocketApplication : Established: dea7144b-452d-564f-77de-8dab317f2b1e
2025-04-19T23:15:26.701+08:00 INFO 2005432 --- [io-18080-exec-2] c.m.f.websocket.WebSocketApplication : frame received(1001): {"flag":100}
2025-04-19T23:15:26.711+08:00 INFO 2005432 --- [lt-dispatcher-3] c.m.fusion.actor.ActorSupervisor : ECHO : {"flag":100}!
OK, 一个简单的单机Actor服务已经启动完成了, 现在我们可以开始主要功能就在 WebSocket|Actor 运行类,
基本功能已经完成现在就要开始设计最早上面提出的 世界BOSS 战斗服务!