https://github.com/meteorgx/actor-spring-boot-starter
https://github.com/meteorgx/actor-spring-boot-starter
Last synced: over 1 year ago
JSON representation
- Host: GitHub
- URL: https://github.com/meteorgx/actor-spring-boot-starter
- Owner: MeteorGX
- License: apache-2.0
- Created: 2024-01-15T20:27:01.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-02-05T17:56:29.000Z (over 2 years ago)
- Last Synced: 2025-02-05T07:26:59.288Z (over 1 year ago)
- Language: Java
- Size: 51.8 KB
- Stars: 1
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Getting Started
Actor-like multi-threading library based on SpringBoot, developing a relatively simple websocket server.
## Example
Import the following libraries | 引入以下的类库:
> note: websocket is not required, can switch netty to enable tcp | websocket 并不是必须, 可以切换 netty 启用 tcp
```xml
org.springframework.boot
spring-boot-starter-websocket
com.meteorcat.spring.boot
actor-spring-boot-starter
{actor-starter-version}
```
First, create actor config | 首先, 创建 Actor 配置:
```java
@Configuration
public class ActorConfig {
private final ApplicationContext context;
public ActorConfig(ApplicationContext context) {
this.context = context;
}
@Bean
public ActorEventContainer searchActor() {
return new ActorEventContainer(new ActorEventMonitor(5), context);
}
}
```
Afterwards, generate websocket server | 之后, 生成 websocket 服务:
```java
/**
* Global WebSocket Service
*/
@Order
@Component
public class WebSocketApplication extends TextWebSocketHandler {
/**
* Logger
*/
Logger logger = LoggerFactory.getLogger(WebSocketApplication.class);
/**
* JSON Parser
*/
ObjectMapper mapper = new ObjectMapper();
/**
* Actor events
*/
final ActorEventContainer container;
/**
* construct method
*
* @param container Actor events
*/
public WebSocketApplication(ActorEventContainer container) {
this.container = container;
}
/**
* Established
*
* @param session handler
* @throws Exception Error
*/
@Override
public void afterConnectionEstablished(@NonNull WebSocketSession session) throws Exception {
logger.debug("Established = {}", session);
}
/**
* Closed
*
* @param session handler
* @param status close state
* @throws Exception Error
*/
@Override
public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus status) throws Exception {
logger.debug("Close = {},{}", session, status);
}
/**
* Text - Callback
*
* @param session Websocket
* @param message data
* @throws Exception Error
*/
@Override
protected void handleTextMessage(@NonNull WebSocketSession session, @NonNull TextMessage message) throws Exception {
logger.debug("Message = {},{}| Thread = {}", session, message.getPayload(), Thread.currentThread().getId());
// empty?
String payload = message.getPayload();
if (payload.isBlank()) return;
// format { op: int, args: object }
JsonNode json = mapper.readTree(payload);
if (!json.isObject()) {
logger.error("failed by transfer data");
return;
}
// op or args exist?
if (!json.has("op") || !json.has("args")) {
logger.error("failed by transfer data");
return;
}
// op is int? args is object?
JsonNode opNode = json.get("op");
JsonNode argsNode = json.get("args");
if (!opNode.isInt() || !argsNode.isObject()) {
logger.error("failed by transfer data");
return;
}
// push message queue
Integer op = opNode.asInt();
ActorConfigurer configurer = container.get(op);
if (configurer != null) {
configurer.invoke(op, 0, container, session, argsNode);
}
}
}
```
By the way, don't forget the websocket configuration | 随便说下, 不要忘记 websocket 配置:
```java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
/**
* path
*/
@Value("${websocket.server.path:/}")
private String serverPath;
/**
* buffer
*/
@Value("${websocket.buffer.max.size:8192}")
private Integer bufferMaxSize;
/**
* timeout
*/
@Value("${websocket.idle.timeout:600000}")
private Long idleTimeout;
/**
* origins
*/
@Value("${websocket.allowed.origins:*}")
private String allowOrigins;
/**
* load @Component websocket
*/
private final WebSocketApplication handler;
/**
* get @Component websocket
*
* @param handler instance
*/
public WebSocketConfig(WebSocketApplication handler) {
this.handler = handler;
}
/**
* register websocket
*
* @param registry register handler
*/
@Override
public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
if (handler == null) {
throw new RuntimeException("failed by WebSocketHandler: WebSocketHandler");
}
registry.addHandler(handler, serverPath).setAllowedOrigins(allowOrigins);
}
/**
* global Servlet configs
*
* @return ServletServerContainerFactoryBean
*/
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(bufferMaxSize);
container.setMaxBinaryMessageBufferSize(bufferMaxSize);
container.setMaxSessionIdleTimeout(idleTimeout);
return container;
}
}
```
Okay, now it's time to write the logic code | 好的, 现在可以编写逻辑代码:
```java
@EnableActor(owner = EchoLogic.class)
public class EchoLogic extends ActorConfigurer {
/**
* echo server
* request: { "op": 100, "args":{ "text": "hello.world"}}
* response: { "text": "hello.world"}
*
* @param session websocket handler
* @param node JSON
* @throws IOException ERROR
*/
@ActorMapping(value = 100)
public void echo(ActorEventContainer container, WebSocketSession session, JsonNode node) throws IOException {
session.sendMessage(new TextMessage(node.toString()));
}
public Map> keepAlive = new HashMap<>();
/**
* Init
*
*/
@Override
public void init() {
logger.warn("server startup");
}
/**
* Exit
*/
@Override
public void destroy() {
logger.warn("server exit");
}
/**
* keep-alive
* request: { "op": 111, "args":{ }}
* response: timestamp
*
* @param session websocket handler
* @throws IOException ERROR
*/
@ActorMapping(value = 111)
public void tick(ActorEventContainer container, WebSocketSession session, JsonNode node) throws IOException {
if (!keepAlive.containsKey(session)) {
ScheduledFuture> future = container.scheduleAtFixedRate(() -> {
if (!session.isOpen()) {
keepAlive.get(session).cancel(true);
keepAlive.remove(session);
}
try {
long timestamp = System.currentTimeMillis();
session.sendMessage(new TextMessage(Long.toString(timestamp)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}, 5, 5, TimeUnit.SECONDS);
keepAlive.put(session, future);
}
}
}
```
Request json data, test the logical code | 请求 JSON 数据, 测试这些逻辑代码:
```json lines
// echo mapping
{
"op": 100,
"args": {
"text": "hello.world"
}
}
// keep-alive request
{
"op": 111,
"args": {}
}
```
> note: If the Bean thread manager is unable to call scheduled tasks, please use the configuration '
> spring.main.allow-bean-definition-overriding=true'