refactor: 重构开发环境

BREAKING CHANGE: 由com.r3944realms.dg_lab_api -> com.r3944realms.dg_lab.api
This commit is contained in:
叁玖领域 2025-09-13 10:57:55 +08:00
commit 4b15d70e06
5435 changed files with 779535 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$PROJECT_DIR$/../projEnv/gradle/gradle-8.12" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/Common" />
<option value="$PROJECT_DIR$/CommonApi" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

14
.idea/misc.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ASMIdeaPluginConfiguration">
<asm skipDebug="false" skipFrames="false" skipCode="false" expandFrames="false" />
<groovy codeStyle="LEGACY" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

90
Common/build.gradle Normal file
View File

@ -0,0 +1,90 @@
plugins {
id 'java'
id 'java-library'
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'maven-publish'
}
group = project_group
version = project_version
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
base {
archivesName = "${project_name}-${common_suffix}"
}
repositories {
mavenCentral()
}
dependencies {
// Common CommonApi
api project(':CommonApi')
implementation 'org.apache.logging.log4j:log4j-core:2.23.1'
implementation 'org.apache.logging.log4j:log4j-api:2.23.1'
implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.23.1'
implementation 'org.realityforge.org.jetbrains.annotations:org.jetbrains.annotations:1.7.0'
implementation 'org.apache.commons:commons-lang3:3.17.0'
implementation 'com.google.guava:guava:33.3.0-jre'
implementation 'io.netty:netty-all:4.1.109.Final'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.slf4j:slf4j-api:2.0.16'
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
// Sources JAR
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
archiveClassifier = 'sources'
}
// Javadoc JAR
task javadocJar(type: Jar) {
dependsOn javadoc
from javadoc.destinationDir
archiveClassifier = 'javadoc'
}
// Shadow JAR: CommonApi Common
shadowJar {
// CommonApi + Common
from sourceSets.main.output // Common
from project(':CommonApi').sourceSets.main.output // CommonApi
// runtimeClasspath
configurations = [] //
mergeServiceFiles()
archiveClassifier.set('') // jar
}
// shadowJar build
build.dependsOn shadowJar
// Javadoc
javadoc {
options.encoding = 'UTF-8'
}
//
publishing {
publications {
mavenJava(MavenPublication) {
artifact shadowJar
artifact sourcesJar
artifact javadocJar
groupId = project.group.toString()
artifactId = base.archivesName.get()
version = project.version.toString()
}
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* The type Dg lab.
*/
public class DgLab {
/**
* The constant LOGGING_HANDLER.
*/
public static LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
/**
* Sets logging handler level.
*
* @param level the level
*/
public void setLoggingHandlerLevel(LogLevel level) {
LOGGING_HANDLER = new LoggingHandler(level);
}
}

View File

@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.manager;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.websocket.PowerBoxWSClient;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import com.r3944realms.dg_lab.websocket.sharedData.ClientPowerBoxSharedData;
/**
* The type Dgpb client manager.
*/
public class DGPBClientManager implements IDGLabManager {
/**
* The Client.
*/
protected final PowerBoxWSClient client;
/**
* Instantiates a new Dgpb client manager.
*
* @param client the client
*/
public DGPBClientManager(PowerBoxWSClient client) {
this.client = client;
}
@Override
public void start() {
client.start();
}
@Override
public void stop() {
client.stop();
}
@Override
public ClientPowerBoxSharedData getSharedData() {
return client.getSharedData();
}
@Override
public Status getStatus() {
return client.getStatus();
}
@Override
public void setStatus(Status status) {
client.setStatus(status);
}
/**
* Sets address.
*
* @param address the address
*/
public void setAddress(String address) {
client.setAddress(address);
}
/**
* Sets port.
*
* @param port the port
*/
public void setPort(int port) {
client.setPort(port);
}
/**
* Sets address and port.
*
* @param address the address
* @param port the port
*/
public void setAddressAndPort(String address, int port) {
client.setAddressAndPort(address, port);
}
/**
* Send.
*
* @param message the message
*/
public void send(Message message) {
client.send(message);
}
}

View File

@ -0,0 +1,85 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.manager;
import com.r3944realms.dg_lab.api.manager.IDGLabManager;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.websocket.PowerBoxWSServer;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import com.r3944realms.dg_lab.websocket.sharedData.ServerPowerBoxSharedData;
/**
* The type Dgpb server manager.
*/
public class DGPBServerManager implements IDGLabManager {
/**
* The Server.
*/
protected final PowerBoxWSServer server;
/**
* Instantiates a new Dgpb server manager.
*
* @param server the server
*/
public DGPBServerManager(PowerBoxWSServer server) {
this.server = server;
}
@Override
public void start() {
server.start();
}
@Override
public void stop() {
server.stop();
}
@Override
public Status getStatus() {
return server.getStatus();
}
@Override
public void setStatus(Status status) {
server.setStatus(status);
}
@Override
public ServerPowerBoxSharedData getSharedData() {
return server.getSharedData();
}
/**
* Send.
*
* @param connectId the connect id
* @param message the message
*/
public void send(String connectId, Message message) {
server.send(connectId, message);
}
/**
* Sets port.
*
* @param port the port
*/
public void setPort(int port) {
server.setPort(port);
}
}

View File

@ -0,0 +1,81 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.manager;
import com.r3944realms.dg_lab.utils.unSafe.AtomicClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* DGLab管理接口
*/
public interface IDGLabManager extends com.r3944realms.dg_lab.api.manager.IDGLabManager {
/**
* The constant CLIENT_INSTANCE.
*/
AtomicClass<DGPBClientManager> CLIENT_INSTANCE = new AtomicClass<>(null);
/**
* The constant SERVER_INSTANCE.
*/
AtomicClass<DGPBServerManager> SERVER_INSTANCE = new AtomicClass<>(null);
/**
* The constant logger.
*/
Logger logger = LoggerFactory.getLogger(IDGLabManager.class);
/**
* 设置单例客户端管理类
*
* @param instance 单例客户端管理类
*/
static void setClientManagerInstance(DGPBClientManager instance) {
CLIENT_INSTANCE.set(instance);
}
/**
* 设置单例服务器管理类
*
* @param instance 单例服务器管理类
*/
static void setServerManagerInstance(DGPBServerManager instance) {
SERVER_INSTANCE.set(instance);
}
/**
* 获取当前单例客户端管理类
*
* @return 单例客户端管理类 client manager instance
*/
static DGPBClientManager getClientManagerInstance() {
DGPBClientManager dgpbClientManager = CLIENT_INSTANCE.get();
if (dgpbClientManager == null)
throw new IllegalStateException("Client manager is not set or is null");
return dgpbClientManager;
}
/**
* 获取当前单例服务器管理类
*
* @return 单例服务器管理类 server manager instance
*/
static DGPBServerManager getServerManagerInstance() {
DGPBServerManager dgpbServerManager = SERVER_INSTANCE.get();
if (dgpbServerManager == null)
throw new IllegalStateException("Server manager is not set or is null");
return dgpbServerManager;
}
}

View File

@ -0,0 +1,29 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.utils.annoation;
/**
* 需要未来完善
*/
public @interface NeedCompletedInFuture {
/**
* Future target string.
*
* @return 未来完善的方向 string
*/
String futureTarget() default "";
}

View File

@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.utils.enums;
/**
* The enum Send mode.
*/
public enum SendMode {
/**
* 仅文本 <br/>
* 即双端均用统一的格式发送Json信息<br/>
* <br/>
* PowerBox通讯<br/>Json如下<br/>
* <code>{"type":"XXX",clientId:"XXX-XXX-XXX-XXX",targetId:"XXX-XXX-XXX-XXX","message":"XXX"}</code>
* <br/>详细可见{@link com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData}
*/
OnlyText,
/**
* 对客户端使用包含更多信息Message<br/>
* 仅客户端与服务器通讯采用app端仍然为原Text格式<br/>
* 客户端与服务器端PowerBox通讯<br/>Json如下: <br/>
* <code>{"commandType":"XXX","direction":{"sender":{"name":"uuid","type":"T_CLIENT"},"receiver":{"name":"uuid","type":"T_SERVER"}},"payload":{"timer_A":0,"timer_B":0,"type":"","clientId":"XXX-XXX-XXX-XXX","targetId":"XXX-XXX-XXX-XXX","message":"XXX"}}</code>
* <br/>详细可见{@link com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage}
*/
ClientMessage
}

View File

@ -0,0 +1,90 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.utils.stringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* The type String handler util.
*/
public class StringHandlerUtil {
/**
* Gets char for string.
*
* @param str the str
* @param index the index
* @return the char for string
*/
public static char getCharForString(String str, int index) {
return str.charAt(index);
}
/**
* Build web socket url string.
*
* @param address the address
* @param port the port
* @param ssl the ssl
* @return the string
*/
public static String buildWebSocketURL(String address, int port, boolean ssl) {
String scheme = ssl ? "wss" : "ws";
return String.format("%s://%s:%d/", scheme, address, port).replace("%","%25");
}
/**
* Add quotes string [ ].
*
* @param arr the arr
* @return the string [ ]
*/
public static String[] addQuotes(String[] arr) {
String[] newArr = new String[arr.length];
for (int i = 0; i < arr.length; i++) {
newArr[i] = "\"" + arr[i] + "\"";
}
return newArr;
}
/**
* Remove quotes string [ ].
*
* @param arr the arr
* @return the string [ ]
*/
public static String[] removeQuotes(String[] arr) {
String[] newArr = new String[arr.length];
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i].substring(1, arr[i].length() - 1);
}
return newArr;
}
/**
* 将String[] 转化为 <code>["xxxxxxxxxxxxxxxx",......,"xxxxxxxxxxxxxxxx"]</code><br/><br/>
* <b> :</b><code>[ff00ff, ac013021, 23990123]</code> -> <code>["ff00ff","ac013021","23990123"]</code>
*
* @param waveDataList the wave data list
* @return the string
*/
public static String reformWaveDataList(String[] waveDataList) {
List<String> b = new ArrayList<>(Arrays.asList(addQuotes(waveDataList)));
return b.toString().replace(" ","");//去除空格
}
}

View File

@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.utils.stringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The type Url validator.
*/
public class UrlValidator {
private static final String DOMAIN_REGEX = "^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$";
private static final String IP_REGEX = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
private static final Pattern DOMAIN_PATTERN = Pattern.compile(DOMAIN_REGEX);
private static final Pattern IP_PATTERN = Pattern.compile(IP_REGEX);
/**
* Is valid address boolean.
*
* @param address the address
* @return the boolean
*/
public static boolean isValidAddress(String address) {
return isValidDomain(address) || isValidIP(address);
}
private static boolean isValidDomain(String domain) {
String[] parts = domain.split("\\.");
if (parts.length < 2) {
return false;
}
for (String part : parts) {
Matcher matcher = DOMAIN_PATTERN.matcher(part);
if (!matcher.matches()) {
return false;
}
}
return true;
}
private static boolean isValidIP(String ip) {
Matcher matcher = IP_PATTERN.matcher(ip);
return matcher.matches();
}
/**
* Is valid port boolean.
*
* @param Port the port
* @return the boolean
*/
public static boolean isValidPort(int Port) {
return Port >= 0 && Port <= 65535;
}
}

View File

@ -0,0 +1,112 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.utils.timeTask;
import com.r3944realms.dg_lab.utils.annoation.NeedCompletedInFuture;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.api.websocket.message.role.PlaceholderRole;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketClientRole;
import io.netty.channel.ChannelHandlerContext;
import java.util.TimerTask;
import java.util.function.Consumer;
/**
* The type Dg lab timer task.
*/
public class DgLabTimerTask extends TimerTask {
/**
* The Client.
*/
final ChannelHandlerContext client;
/**
* The Target.
*/
final ChannelHandlerContext target;
/**
* The Client id.
*/
String clientId, /**
* The Target id.
*/
targetId;
/**
* The Total sends.
*/
Integer totalSends;
/**
* The Send message data.
*/
final Message sendMessageData;
/**
* The Timer consumer.
*/
final Consumer<Object> timerConsumer;
/**
* The Channel.
*/
final char channel;
/**
* Instantiates a new Dg lab timer task.
*
* @param client the client
* @param target the target
* @param sendMessageData the send message data
* @param totalSends the total sends
* @param timerConsumer the timer consumer
* @param channel the channel
*/
public DgLabTimerTask(ChannelHandlerContext client, ChannelHandlerContext target, Message sendMessageData, Integer totalSends, Consumer<Object> timerConsumer, char channel) {
this.channel = channel;
this.client = client;
this.target = target;
this.sendMessageData = sendMessageData;
this.totalSends = totalSends;
this.timerConsumer = timerConsumer;
}
/**
* Sets id.
*
* @param clientId the client id
* @param targetId the target id
*/
public void setId(String clientId, String targetId) {
this.clientId = clientId;
this.targetId = targetId;
}
@Override
public void run() {
if(totalSends > 0) {//一一个固定频率发送特定的客户端
send(target,sendMessageData);
totalSends--;
}
if(totalSends <= 0) {//达到发送上限
PowerBoxMessage over = PowerBoxMessage.createPowerBoxMessage("clientMsg", clientId, targetId, "send-over", new PlaceholderRole("Server"), new WebSocketClientRole("Cl" + clientId));
send(client, over);
timerConsumer.accept(null);
}
}
@NeedCompletedInFuture
private void send(ChannelHandlerContext target, Message message) {
target.writeAndFlush(message.getDataJson());
}
}

View File

@ -0,0 +1,73 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.utils.unSafe;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
/**
* The type Atomic class.
*
* @param <T> the type parameter
*/
@SuppressWarnings("FieldMayBeFinal")
public class AtomicClass<T> {
private static final VarHandle VALUE;
private volatile T value;
/**
* Instantiates a new Atomic class.
*
* @param value the value
*/
public AtomicClass(T value) {
this.value = value;
}
static {
try {
Field valueField = AtomicClass.class.getDeclaredField("value");
VALUE = MethodHandles.lookup().unreflectVarHandle(valueField);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Get t.
*
* @return the t
*/
public T get() {
return value;
}
/**
* Set.
*
* @param newValue the new value
*/
public void set(T newValue) {
while (true) {
//noinspection unchecked
T prev = (T) VALUE.getVolatile(this);
if (VALUE.compareAndSet(this, prev, newValue)) {
break;
}
}
}
}

View File

@ -0,0 +1,384 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket;
import com.r3944realms.dg_lab.DgLab;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.utils.stringUtils.StringHandlerUtil;
import com.r3944realms.dg_lab.utils.stringUtils.UrlValidator;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.concurrent.CompletableFuture;
/**
* 主要是处理netty逻辑以建立客户端
*/
public abstract class AbstractWebSocketClient {
/**
* 日志
*/
protected static final Logger logger = LoggerFactory.getLogger(AbstractWebSocketClient.class);
/**
* 地址
*/
protected volatile String Address;
/**
* 端口
*/
protected volatile int Port;
/**
* 客户端启动类
*/
private Bootstrap ClientBootstrap;
/**
* 客户端事件线程组
*/
private EventLoopGroup ClientEventLoopGroup;
/**
* The Client channel.
*/
protected Channel ClientChannel;
private Thread WebSocketClientThread;
/**
* The Client status.
*/
protected volatile Status ClientStatus = Status.WAITING_FOR_INIT;
/**
* Gets status.
*
* @return the status
*/
public Status getStatus() {
return this.ClientStatus;
}
/**
* Sets status.
*
* @param status the status
*/
public void setStatus(Status status) {
this.ClientStatus = status;
}
/**
* Instantiates a new Abstract web socket client.
*/
protected AbstractWebSocketClient() {
try {
this.Address = InetAddress.getLocalHost().getHostAddress();
this.Port = 9000;
} catch (UnknownHostException e) {
logger.error(e.getMessage());
}
}
/**
* Instantiates a new Abstract web socket client.
*
* @param port the port
*/
protected AbstractWebSocketClient(int port) {
try {
InetAddress localHost = InetAddress.getLocalHost();
this.Address = localHost.getHostAddress();
this.Port = UrlValidator.isValidPort(port) ? port : 9000;
} catch (UnknownHostException e) {
logger.error(e.getMessage());
}
}
/**
* Instantiates a new Abstract web socket client.
*
* @param address the address
* @param port the port
*/
protected AbstractWebSocketClient(String address, int port) {
try {
this.Address = UrlValidator.isValidAddress(address) ? address : InetAddress.getLocalHost().getHostAddress();
this.Port = UrlValidator.isValidPort(port) ? port : 9000;
} catch (UnknownHostException e) {
logger.error(e.getMessage());
}
}
/**
* Sets port.
*
* @param port the port
*/
public void setPort(int port) {
if (this.ClientStatus != Status.WAITING_FOR_INIT && this.ClientStatus != Status.STOPPED) {
logger.error("Unable to change port to {}", port);
return;
}
this.Port = UrlValidator.isValidPort(port) ? port : 9000;
}
/**
* Sets address.
*
* @param address the address
*/
public void setAddress(String address) {
if (this.ClientStatus != Status.WAITING_FOR_INIT && this.ClientStatus != Status.STOPPED) {
logger.error("Unable to change address to {}", address);
return;
}
try {
this.Address = UrlValidator.isValidAddress(address) ? address : InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
/**
* Sets address and port.
*
* @param address the address
* @param port the port
*/
public void setAddressAndPort(String address, int port) {
try {
setAddress(address);
setPort(port);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
/**
* Gets url.
*
* @return the url
*/
public String getUrl() {
return StringHandlerUtil.buildWebSocketURL(Address, Port, false);
}
/**
* 提供实现此处添加对 Message处理的 Handler
*
* @param pipeline 管道
*/
protected abstract void MessagePipeLineHandler(ChannelPipeline pipeline);
/**
* Init thread 0.
*/
protected void initThread0() {}
/**
* Init thread.
*/
protected final void initThread() {
ClientEventLoopGroup = new NioEventLoopGroup();
WebSocketClientThread = new Thread(() -> {
ClientStatus = Status.STARTING;
ClientBootstrap = new Bootstrap();
ClientBootstrap.group(ClientEventLoopGroup);
ClientBootstrap.channel(NioSocketChannel.class);
ClientBootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(DgLab.LOGGING_HANDLER,
new HttpClientCodec(),
new HttpObjectAggregator(65536),
new WebSocketClientProtocolHandler(
WebSocketClientHandshakerFactory.newHandshaker(
URI.create(StringHandlerUtil.buildWebSocketURL(Address, Port, false)),
WebSocketVersion.V13,
null,
false,
new DefaultHttpHeaders()
)
),
new WebSocketFrameAggregator(65536));
MessagePipeLineHandler(pipeline);
}
});
try {
ChannelFuture channelFuture = ClientBootstrap.connect(Address, Port);
ClientStatus = Status.RUNNING;
started();
ClientChannel = channelFuture.sync().channel();
ClientChannel.closeFuture().sync();
} catch (Exception e) {
startingError();
ClientStatus = Status.ERROR;
logger.error(e.getMessage());
} finally {
if(ClientStatus == Status.ERROR || ClientStatus == Status.RUNNING) stop();
}
}, "WebSocketClient");
initThread0();
}
/**
* Start 0.
*/
protected void start0() {}
/**
* Start.
*/
public final void start() {
starting();
switch (ClientStatus) {
case STARTING -> {
startingError();
logger.info("Client is already starting.");
}
case RUNNING -> {
startingError();
logger.info("Client is already running.");
}
case STOPPING -> {
startingError();
logger.info("Client is stopping");
}
case STOPPED, WAITING_FOR_INIT -> {
initThread();
WebSocketClientThread.start();
start0();
}
}
}
/**
* Stop 0 completable future.
*
* @return the completable future
*/
protected CompletableFuture<Void> stop0() {
return CompletableFuture.runAsync(() -> {});
}
/**
* Starting.
*/
protected abstract void starting();
/**
* Starting error.
*/
protected abstract void startingError();
/**
* Started.
*/
protected abstract void started();
/**
* Stopping.
*/
protected abstract void stopping();
/**
* Stopping error.
*/
protected abstract void stoppingError();
/**
* Stopped.
*/
protected abstract void stopped();
/**
* Stop.
*/
public final void stop() {
stopping();
switch (ClientStatus) {
case WAITING_FOR_INIT -> {
stoppingError();
logger.warn("Not Init. (It shouldn't be happened)");
}
case STARTING -> {
stoppingError();
logger.info("Client is starting, please waiting.");
}
case RUNNING, ERROR -> {
ClientStatus = Status.STOPPING;
CompletableFuture<Void> CliChanClose = CompletableFuture.runAsync(() -> {
if (ClientChannel != null && ClientChannel.isOpen()) {
try {
ClientChannel.close().sync();
} catch (InterruptedException e) {
logger.error(e.getMessage());
} finally {
ClientChannel = null;
}
}
});
CompletableFuture<Void> CliEvenClose = CliChanClose.thenRunAsync(() -> {
if (ClientEventLoopGroup != null) {
try {
ClientEventLoopGroup.shutdownGracefully().sync();
} catch (InterruptedException e) {
logger.error(e.getMessage());
} finally {
ClientEventLoopGroup = null;
}
}
});
CliEvenClose.thenCombineAsync(stop0(), (a, b) -> {
ClientStatus = Status.STOPPED;
ClientBootstrap = null;
stopped();
return null;
});
}
case STOPPING -> {
stoppingError();
logger.info("Client is already stopping");
}
case STOPPED -> {
stoppingError();
logger.info("Client has stopped");
}
}
}
/**
* 向所连接的服务器发送消息
*
* @param message 消息
*/
public abstract void send(Message message);
}

View File

@ -0,0 +1,345 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket;
import com.r3944realms.dg_lab.DgLab;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.utils.stringUtils.UrlValidator;
import com.r3944realms.dg_lab.websocket.handler.server.HttpRequestHandler;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
/**
* 主要是处理netty逻辑以建立服务端
*/
@SuppressWarnings({"DuplicatedCode", "LoggingSimilarMessage"})
public abstract class AbstractWebSocketServer {
/**
* The constant logger.
*/
protected static final Logger logger = LoggerFactory.getLogger(AbstractWebSocketServer.class);
private int Port;
/**
* The Server bootstrap.
*/
protected ServerBootstrap ServerBootstrap;
/**
* The Boss group.
*/
protected EventLoopGroup BossGroup,
/**
* The Worker group.
*/
WorkerGroup;
/**
* The Server channel.
*/
protected Channel ServerChannel;
/**
* The Websocket server thread.
*/
protected Thread WebsocketServerThread;
/**
* The Server status.
*/
protected volatile Status ServerStatus = Status.WAITING_FOR_INIT;
/**
* Instantiates a new Abstract web socket server.
*/
protected AbstractWebSocketServer() {
Port = 9000;
}
/**
* Instantiates a new Abstract web socket server.
*
* @param port the port
*/
protected AbstractWebSocketServer(int port) {
Port = port;
}
/**
* Gets status.
*
* @return the status
*/
public Status getStatus() {
return this.ServerStatus;
}
/**
* Sets status.
*
* @param status the status
*/
public void setStatus(Status status) {
this.ServerStatus = status;
}
/**
* Sets port.
*
* @param port the port
*/
public void setPort(int port) {
if(this.ServerStatus != Status.WAITING_FOR_INIT && this.ServerStatus != Status.STOPPED) {
logger.error("Unable to Change Port to {}", port);
return;
}
this.Port = UrlValidator.isValidPort(port) ? port : 9000;
}
/**
* Gets port.
*
* @return the port
*/
public int getPort() {
return Port;
}
/**
* 提供实现此处添加对Message处理Handler
*
* @param pipeline 管道
*/
protected abstract void MessagePipeLineHandler(ChannelPipeline pipeline);
/**
* Init thread 0.
*/
protected void initThread0() {}
/**
* Init thread.
*/
protected final void initThread() {
BossGroup = new NioEventLoopGroup();
WorkerGroup = new NioEventLoopGroup();
WebsocketServerThread = new Thread(() ->{
try {
ServerBootstrap = new ServerBootstrap();
ServerStatus = Status.STARTING;
ServerBootstrap.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ServerBootstrap.group(BossGroup, WorkerGroup);
ServerBootstrap.channel(NioServerSocketChannel.class);
ServerBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(DgLab.LOGGING_HANDLER);
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(new HttpRequestHandler());//去除路径请求APP会发送带路径请求的HTTP升级请求目前用不到
pipeline.addLast("WSP",new WebSocketServerProtocolHandler("/"));
pipeline.addLast(new WebSocketFrameAggregator(65536));
MessagePipeLineHandler(pipeline);
}
});
logger.debug("WebSocketServer try binding port ... ");
ChannelFuture channelFuture = ServerBootstrap.bind("0.0.0.0",Port);
channelFuture.sync();
ServerChannel = channelFuture.channel();
ServerStatus = Status.RUNNING;
started();
logger.info("WebSocketServer start on the port of {}", Port);
logger.debug("WebSocketServer listening on port {}", Port);
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
ServerStatus = Status.ERROR;
stoppingError();
logger.error(e.getMessage());
} finally {
if(ServerStatus == Status.ERROR || ServerStatus == Status.RUNNING) stop();
}
}, "WebSocketServer");
initThread0();
}
/**
* Starting.
*/
protected abstract void starting();
/**
* Starting error.
*/
protected abstract void startingError();
/**
* Started.
*/
protected abstract void started();
/**
* Stopping.
*/
protected abstract void stopping();
/**
* Stopping error.
*/
protected abstract void stoppingError();
/**
* Stopped.
*/
protected abstract void stopped();
/**
* 自定义Start后续逻辑
*/
protected void start0() {
//NOOP
}
/**
* Start.
*/
public final void start() {
starting();
switch (ServerStatus) {
case STARTING -> {
startingError();
logger.info("Server is already starting.");
}
case RUNNING -> {
startingError();
logger.info("Server is already running.");
}
case STOPPING -> {
startingError();
logger.info("Server is stopping");
}
case STOPPED, WAITING_FOR_INIT -> {
initThread();
WebsocketServerThread.start();
start0();
}
}
}
/**
* 自定义Stop后续逻辑
*
* @return the completable future
*/
protected CompletableFuture<Void> stop0() {
return CompletableFuture.runAsync(() -> {});
}
/**
* Stop.
*/
public final void stop() {
stopping();
switch (ServerStatus) {
case WAITING_FOR_INIT -> {
stoppingError();
logger.warn("Not Init. (It shouldn't be happened)");
}
case STARTING -> {
stoppingError();
logger.info("Server is starting, please waiting.");
}
case RUNNING -> {
ServerStatus = Status.STOPPING;
// Close the server channel if it's open
CompletableFuture<Void> SerChanClose = CompletableFuture.runAsync(() -> {
if (ServerChannel != null && ServerChannel.isOpen()) {
try {
ServerChannel.close().sync();
} catch (InterruptedException e) {
logger.error(e.getMessage());
} finally {
ServerChannel = null;
}
}
});
// Shutdown the event loop groups
CompletableFuture<Void> BosGroClose = SerChanClose.thenRunAsync(() -> {
if (BossGroup != null) {
try {
BossGroup.shutdownGracefully().sync();
} catch (InterruptedException e) {
logger.error(e.getMessage());
} finally {
BossGroup = null;
}
}
}
);
CompletableFuture<Void> WorGroClose = SerChanClose.thenRunAsync(() -> {
if (WorkerGroup != null) {
try {
WorkerGroup.shutdownGracefully().sync();
} catch (InterruptedException e) {
logger.error(e.getMessage());
} finally {
WorkerGroup = null;
}
}
});
BosGroClose.thenCombineAsync(WorGroClose, (a,b) -> {
stop0().thenRunAsync(() -> {
ServerStatus = Status.STOPPED;
ServerBootstrap = null;
stopped();
});
return null;
});
}
case STOPPING -> {
stoppingError();
logger.info("Server is already stopping");
}
case STOPPED -> {
stoppingError();
logger.info("Server has stopped");
}
}
}
/**
* 向指定的客户端发送消息
*
* @param clientId 客户端UUID
* @param message 消息
*/
public abstract void send(String clientId, Message message);
}

View File

@ -0,0 +1,382 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket;
import com.r3944realms.dg_lab.api.operation.ClientOperation;
import com.r3944realms.dg_lab.websocket.handler.client.ClientDLPBHandler;
import com.r3944realms.dg_lab.websocket.handler.client.ClientDLPBHandlerContextWrapper;
import com.r3944realms.dg_lab.websocket.handler.client.DefaultClientOperation;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketClientRole;
import com.r3944realms.dg_lab.websocket.sharedData.ClientPowerBoxSharedData;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
/**
* 处理PowerBox的Websocket消息的客户端实现
*/
public class PowerBoxWSClient extends AbstractWebSocketClient {
/**
* The Shared data.
*/
protected final ClientPowerBoxSharedData sharedData;
/**
* The Role.
*/
protected final WebSocketClientRole role;
/**
* The Operation.
*/
protected final ClientOperation operation;
/**
* The Use role msg mode.
*/
protected final boolean useRoleMsgMode;
private final ClientDLPBHandlerContextWrapper contextWrapper;
/**
* Instantiates a new Power box ws client.
*
* @param sharedData the shared data
* @param role the role
* @param operation the operation
*/
public PowerBoxWSClient(ClientPowerBoxSharedData sharedData, WebSocketClientRole role, ClientOperation operation) {
super();
this.useRoleMsgMode = false;
this.sharedData = sharedData;
this.operation = operation;
this.role = role;
sharedData.address = this.Address;
sharedData.port = this.Port;
contextWrapper = new ClientDLPBHandlerContextWrapper(sharedData, role, operation, false);
}
/**
* Instantiates a new Power box ws client.
*
* @param sharedData the shared data
* @param role the role
* @param operation the operation
* @param address the address
* @param port the port
*/
public PowerBoxWSClient(ClientPowerBoxSharedData sharedData, WebSocketClientRole role, ClientOperation operation, String address, int port) {
super(address, port);
this.useRoleMsgMode = false;
this.sharedData = sharedData;
this.operation = operation;
this.role = role;
sharedData.address = this.Address;
sharedData.port = this.Port;
contextWrapper = new ClientDLPBHandlerContextWrapper(sharedData, role, operation, false);
}
/**
* Instantiates a new Power box ws client.
*
* @param sharedData the shared data
* @param role the role
*/
public PowerBoxWSClient(ClientPowerBoxSharedData sharedData, WebSocketClientRole role) {
this(sharedData, role, new DefaultClientOperation());
}
/**
* Instantiates a new Power box ws client.
*
* @param sharedData the shared data
* @param role the role
* @param address the address
* @param port the port
*/
public PowerBoxWSClient(ClientPowerBoxSharedData sharedData, WebSocketClientRole role, String address, int port) {
this(sharedData, role, new DefaultClientOperation(), address, port);
}
/**
* Instantiates a new Power box ws client.
*
* @param sharedData the shared data
* @param role the role
* @param operation the operation
* @param useRoleMsgMode the use role msg mode
*/
public PowerBoxWSClient(ClientPowerBoxSharedData sharedData, WebSocketClientRole role, ClientOperation operation, boolean useRoleMsgMode) {
super();
this.useRoleMsgMode = useRoleMsgMode;
this.sharedData = sharedData;
this.operation = operation;
this.role = role;
sharedData.address = this.Address;
sharedData.port = this.Port;
contextWrapper = new ClientDLPBHandlerContextWrapper(sharedData, role, operation, useRoleMsgMode);
}
/**
* Instantiates a new Power box ws client.
*
* @param sharedData the shared data
* @param role the role
* @param operation the operation
* @param useRoleMsgMode the use role msg mode
* @param address the address
* @param port the port
*/
public PowerBoxWSClient(ClientPowerBoxSharedData sharedData, WebSocketClientRole role, ClientOperation operation, boolean useRoleMsgMode,String address, int port) {
super(address, port);
this.useRoleMsgMode = useRoleMsgMode;
this.sharedData = sharedData;
this.operation = operation;
this.role = role;
sharedData.address = this.Address;
sharedData.port = this.Port;
contextWrapper = new ClientDLPBHandlerContextWrapper(sharedData, role, operation, useRoleMsgMode);
}
/**
* Instantiates a new Power box ws client.
*
* @param sharedData the shared data
* @param role the role
* @param useRoleMsgMode the use role msg mode
*/
public PowerBoxWSClient(ClientPowerBoxSharedData sharedData, WebSocketClientRole role, boolean useRoleMsgMode) {
this(sharedData, role, new DefaultClientOperation(), useRoleMsgMode);
}
/**
* Instantiates a new Power box ws client.
*
* @param sharedData the shared data
* @param role the role
* @param useRoleMsgMode the use role msg mode
* @param address the address
* @param port the port
*/
public PowerBoxWSClient(ClientPowerBoxSharedData sharedData, WebSocketClientRole role, boolean useRoleMsgMode, String address, int port) {
this(sharedData, role, new DefaultClientOperation(), useRoleMsgMode, address, port);
}
@Override
protected void MessagePipeLineHandler(ChannelPipeline pipeline) {
pipeline.addLast(new ClientDLPBHandler(contextWrapper));
}
@Override
protected void starting() {
operation.ClientStartingHandler();
}
@Override
protected void startingError() {
operation.ClientStartingErrorHandler();
}
@Override
protected void started() {
operation.ClientStartedHandler();
}
@Override
protected void stopping() {
operation.ClientStoppingHandler();
}
@Override
protected void stoppingError() {
operation.ClientStoppingErrorHandler();
}
@Override
protected void stopped() {
operation.ClientStoppedHandler();
}
@Override
public void send(Message message) {
if(message instanceof PowerBoxMessage PBMessage) {
this.ClientChannel.writeAndFlush(new TextWebSocketFrame(PBMessage.getMsgJson()));
} else {
logger.error("Message is not a PowerBoxMessage");
}
}
/**
* Gets shared data.
*
* @return the shared data
*/
public ClientPowerBoxSharedData getSharedData() {
return sharedData;
}
/**
* 必要要设置{@link ClientPowerBoxSharedData sharedData}{@link WebSocketClientRole role}正确值
*/
public final static class Builder {
/**
* The Shared data.
*/
ClientPowerBoxSharedData sharedData;
/**
* The Role.
*/
WebSocketClientRole role;
/**
* The Operation.
*/
ClientOperation operation;
/**
* The Address.
*/
String address;
/**
* The Port.
*/
int port;
/**
* The Use role msg mode.
*/
boolean useRoleMsgMode;
private Builder() {
role = null;
sharedData = null;
operation = null;
address = null;
port = -1;
useRoleMsgMode = false;
}
/**
* Gets builder.
*
* @return the builder
*/
public static Builder getBuilder() {
return new Builder();
}
/**
* Shared data builder.
*
* @param sharedData the shared data
* @return the builder
*/
public Builder sharedData(ClientPowerBoxSharedData sharedData) {
this.sharedData = sharedData;
return this;
}
/**
* Role builder.
*
* @param role the role
* @return the builder
*/
public Builder role(WebSocketClientRole role) {
this.role = role;
return this;
}
/**
* Address and port builder.
*
* @param address the address
* @param port the port
* @return the builder
*/
public Builder addressAndPort(String address, int port) {
this.address = address;
this.port = port;
return this;
}
/**
* Operation builder.
*
* @param operation the operation
* @return the builder
*/
public Builder operation(ClientOperation operation) {
this.operation = operation;
return this;
}
/**
* Address builder.
*
* @param address the address
* @return the builder
*/
public Builder address(String address) {
this.address = address;
return this;
}
/**
* Port builder.
*
* @param port the port
* @return the builder
*/
public Builder port(int port) {
this.port = port;
return this;
}
/**
* Use role msg mode builder.
*
* @param useRoleMsgMode the use role msg mode
* @return the builder
*/
public Builder useRoleMsgMode(boolean useRoleMsgMode) {
this.useRoleMsgMode = useRoleMsgMode;
return this;
}
/**
* Build power box ws client.
*
* @return the power box ws client
*/
public PowerBoxWSClient build() {
if (sharedData == null || role == null)
throw new UnsupportedOperationException("Lost key value, failed to build");
boolean isAddressAndPortDefaultValue = port == -1 || address == null;//使用默认地址与端口
boolean isOperationDefaultValue = this.operation == null;//使用默认操作
return (useRoleMsgMode) ?
isOperationDefaultValue ?
isAddressAndPortDefaultValue ?
new PowerBoxWSClient(sharedData, role, true) :
new PowerBoxWSClient(sharedData, role,true, address, port)
:
isAddressAndPortDefaultValue ?
new PowerBoxWSClient(sharedData, role, operation, true) :
new PowerBoxWSClient(sharedData, role, operation, true, address, port)
: isOperationDefaultValue ?
isAddressAndPortDefaultValue ?
new PowerBoxWSClient(sharedData, role) :
new PowerBoxWSClient(sharedData, role, address, port)
:
isAddressAndPortDefaultValue ?
new PowerBoxWSClient(sharedData, role, operation) :
new PowerBoxWSClient(sharedData, role, operation, address, port);
}
}
}

View File

@ -0,0 +1,288 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket;
import com.r3944realms.dg_lab.api.operation.ServerOperation;
import com.r3944realms.dg_lab.websocket.handler.server.DefaultServerOperation;
import com.r3944realms.dg_lab.websocket.handler.server.ServerDLPBHandler;
import com.r3944realms.dg_lab.websocket.handler.server.ServerDLPBHandlerContextWrapper;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketServerRole;
import com.r3944realms.dg_lab.websocket.sharedData.ServerPowerBoxSharedData;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.util.Timer;
import java.util.concurrent.CompletableFuture;
/**
* 处理PowerBox的Websocket消息的服务器实现
*/
public class PowerBoxWSServer extends AbstractWebSocketServer {
/**
* The Shared data.
*/
protected final ServerPowerBoxSharedData SharedData;
/**
* The Role.
*/
protected final WebSocketServerRole role;
/**
* The Operation.
*/
protected final ServerOperation operation;
private final ServerDLPBHandlerContextWrapper contextWrapper;
/**
* Instantiates a new Power box ws server.
*
* @param sharedData the shared data
* @param role the role
* @param operation the operation
*/
public PowerBoxWSServer(ServerPowerBoxSharedData sharedData, WebSocketServerRole role, ServerOperation operation) {
this.SharedData = sharedData;
this.role = role;
this.operation = operation;
contextWrapper = new ServerDLPBHandlerContextWrapper(
sharedData,
role,
operation
);
}
/**
* Instantiates a new Power box ws server.
*
* @param sharedData the shared data
* @param role the role
* @param operation the operation
* @param port the port
*/
public PowerBoxWSServer(ServerPowerBoxSharedData sharedData, WebSocketServerRole role, ServerOperation operation, int port) {
super(port);
this.SharedData = sharedData;
this.role = role;
this.operation = operation;
contextWrapper = new ServerDLPBHandlerContextWrapper(
sharedData,
role,
operation
);
}
/**
* Instantiates a new Power box ws server.
*
* @param sharedData the shared data
* @param role the role
*/
public PowerBoxWSServer(ServerPowerBoxSharedData sharedData, WebSocketServerRole role) {
this(sharedData, role, new DefaultServerOperation());
}
/**
* Instantiates a new Power box ws server.
*
* @param sharedData the shared data
* @param role the role
* @param port the port
*/
public PowerBoxWSServer(ServerPowerBoxSharedData sharedData, WebSocketServerRole role, int port) {
this(sharedData, role, new DefaultServerOperation(), port);
}
@Override
protected void MessagePipeLineHandler(ChannelPipeline pipeline) {
pipeline.addLast(new ServerDLPBHandler(contextWrapper));
}
@Override
protected void starting() {
operation.ServerStartingHandler();
}
@Override
protected void startingError() {
operation.ServerStartingErrorHandler();
}
@Override
protected void started() {
operation.ServerStartedHandler();
}
@Override
protected void stopping() {
operation.ServerStoppingHandler();
}
@Override
protected void stoppingError() {
operation.ServerStoppingErrorHandler();
}
@Override
protected void stopped() {
operation.ServerStoppedHandler();
}
@Override
public void send(String connectId, Message message) {
if(message instanceof PowerBoxMessage PBMessage) {
ChannelHandlerContext context = SharedData.connections.get(connectId);//获取连接连接的Context
if(context != null) {
String targetId = SharedData.relations.get(connectId);
if(targetId.isEmpty()) { //该上下文为客户端
context.writeAndFlush(new TextWebSocketFrame(PBMessage.getMsgJson()));//发送给客户端
ChannelHandlerContext appContext = SharedData.connections.get(targetId);
appContext.writeAndFlush(new TextWebSocketFrame(PBMessage.getDataJson()));//发送给APP端
} else if(SharedData.relations.containsValue(targetId)){ // 绑定状态的APP端
//仅对APP发信息
context.writeAndFlush(new TextWebSocketFrame(PBMessage.getDataJson()));
} else { // 未绑定状态不可发送消息
logger.error("Can't send Msg to no relationship obj.");
}
} else {
logger.error("Find that Target is invalid.");
}
} else {
logger.error("Message is not a PowerBoxMessage");
}
}
/**
* Gets shared data.
*
* @return the shared data
*/
public ServerPowerBoxSharedData getSharedData() {
return SharedData;
}
@Override
protected CompletableFuture<Void> stop0() {
return CompletableFuture.runAsync(
() -> {
Timer heartTimer = getSharedData().heartTimer;
if (heartTimer != null) {
heartTimer.cancel();
}
}
);
}
/**
* 必要要设置{@link ServerPowerBoxSharedData sharedData}{@link WebSocketServerRole role}正确值
*/
public static final class Builder {
/**
* The Shared data.
*/
ServerPowerBoxSharedData sharedData;
/**
* The Role.
*/
WebSocketServerRole role;
/**
* The Port.
*/
int port;
/**
* The Operation.
*/
ServerOperation operation;
private Builder() {
role = null;
sharedData = null;
operation = null;
port = -1;
}
/**
* Gets builder.
*
* @return the builder
*/
public static Builder getBuilder() {
return new Builder();
}
/**
* Shared data builder.
*
* @param sharedData the shared data
* @return the builder
*/
public Builder sharedData(ServerPowerBoxSharedData sharedData) {
this.sharedData = sharedData;
return this;
}
/**
* Port builder.
*
* @param port the port
* @return the builder
*/
public Builder port(int port) {
this.port = port;
return this;
}
/**
* Operation builder.
*
* @param operation the operation
* @return the builder
*/
public Builder operation(ServerOperation operation) {
this.operation = operation;
return this;
}
/**
* Role builder.
*
* @param role the role
* @return the builder
*/
public Builder role(WebSocketServerRole role) {
this.role = role;
return this;
}
/**
* Build power box ws server.
*
* @return the power box ws server
*/
public PowerBoxWSServer build() {
if (sharedData == null || role == null)
throw new UnsupportedOperationException("Lost key value, failed to build");
return (operation == null) ? (port == -1) ?
new PowerBoxWSServer(sharedData, role) :
new PowerBoxWSServer(sharedData, role, port)
: (port == -1) ?
new PowerBoxWSServer(sharedData, role, operation) :
new PowerBoxWSServer(sharedData, role, operation, port);
}
}
}

View File

@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
/**
* The type Abstract dg lab power box handler.
*/
public abstract class AbstractDgLabPowerBoxHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
/**
* The Context wrapper.
*/
protected final AbstractDgLabPowerBoxHandlerContextWrapper contextWrapper;
/**
* Instantiates a new Abstract dg lab power box handler.
*
* @param contextWrapper the context wrapper
*/
public AbstractDgLabPowerBoxHandler(AbstractDgLabPowerBoxHandlerContextWrapper contextWrapper) {
this.contextWrapper = contextWrapper;
}
@Override
public void handlerAdded(ChannelHandlerContext session) throws Exception {
super.handlerAdded(session);
contextWrapper.SessionBuildInHandle(session);
}
@Override
public void channelActive(ChannelHandlerContext session) throws Exception {
super.channelActive(session);
contextWrapper.ActiveSessionHandle(session);
}
@Override
public void handlerRemoved(ChannelHandlerContext session) throws Exception {
super.handlerRemoved(session);
contextWrapper.SessionCloseHandle(session);
}
@Override
public void exceptionCaught(ChannelHandlerContext session, Throwable cause) {
contextWrapper.ErrorHandler(session, cause);
}
@Override
protected void channelRead0(ChannelHandlerContext session, TextWebSocketFrame msg) {
contextWrapper.ReadMsgHandle(session, msg);
}
}

View File

@ -0,0 +1,130 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler;
import com.r3944realms.dg_lab.api.operation.IOperation;
import com.r3944realms.dg_lab.api.websocket.message.role.Role;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The type Abstract dg lab power box handler context wrapper.
*/
public abstract class AbstractDgLabPowerBoxHandlerContextWrapper {
/**
* The constant logger.
*/
public static final Logger logger = LoggerFactory.getLogger(AbstractDgLabPowerBoxHandlerContextWrapper.class);
/**
* The Shared data.
*/
protected final ISharedData sharedData;
/**
* The Operation.
*/
protected final IOperation operation;
/**
* The Role.
*/
protected final Role role;
/**
* Gets role.
*
* @return the role
*/
public Role getRole() {
return role;
}
/**
* Gets shared data.
*
* @return the shared data
*/
public ISharedData getSharedData() {
return sharedData.clone();
}
/**
* Gets operation.
*
* @return the operation
*/
public IOperation getOperation() {
return operation;
}
/**
* Instantiates a new Abstract dg lab power box handler context wrapper.
*
* @param sharedData the shared data
* @param role the role
* @param operation the operation
*/
public AbstractDgLabPowerBoxHandlerContextWrapper(
ISharedData sharedData,
Role role,
IOperation operation
) {
this.sharedData = sharedData;
this.operation = operation;
this.role = role;
}
/**
* 连接建立时触发
*
* @param session {@link ChannelHandlerContext Context}
*/
public abstract void SessionBuildInHandle(ChannelHandlerContext session);
/**
* 连接存活时触发
*
* @param session {@link ChannelHandlerContext Context}
*/
public abstract void ActiveSessionHandle(ChannelHandlerContext session);
/**
* 连接关闭时触发
*
* @param session {@link ChannelHandlerContext Context}
*/
public abstract void SessionCloseHandle(ChannelHandlerContext session);
/**
* 连接获取信息时触发
*
* @param session {@link ChannelHandlerContext Context}
* @param frame {@link TextWebSocketFrame frame}
*/
public abstract void ReadMsgHandle(ChannelHandlerContext session, TextWebSocketFrame frame);
/**
* 连接出现错误时触发
*
* @param session {@link ChannelHandlerContext Context}
* @param cause {@link Throwable cause}
*/
public abstract void ErrorHandler(ChannelHandlerContext session, Throwable cause);
}

View File

@ -0,0 +1,31 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
/**
* 携带共享数据的接口
*/
public interface IAttachSharedData {
/**
* Gets shared data.
*
* @return the shared data
*/
ISharedData getSharedData();
}

View File

@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler.client;
import com.r3944realms.dg_lab.websocket.handler.AbstractDgLabPowerBoxHandler;
import com.r3944realms.dg_lab.websocket.handler.IAttachSharedData;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
import io.netty.channel.ChannelHandler;
/**
* The type Client dlpb handler.
*/
@ChannelHandler.Sharable
public class ClientDLPBHandler extends AbstractDgLabPowerBoxHandler implements IAttachSharedData {
/**
* Instantiates a new Client dlpb handler.
*
* @param contextWrapper the context wrapper
*/
public ClientDLPBHandler(ClientDLPBHandlerContextWrapper contextWrapper) {
super(contextWrapper);
}
@Override
public ISharedData getSharedData() {
return contextWrapper.getSharedData();
}
}

View File

@ -0,0 +1,282 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler.client;
import com.r3944realms.dg_lab.api.operation.ClientOperation;
import com.r3944realms.dg_lab.api.operation.IOperation;
import com.r3944realms.dg_lab.websocket.handler.AbstractDgLabPowerBoxHandlerContextWrapper;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import com.r3944realms.dg_lab.api.websocket.message.role.Role;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketClientRole;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketServerRole;
import com.r3944realms.dg_lab.api.websocket.message.role.type.RoleType;
import com.r3944realms.dg_lab.websocket.sharedData.ClientPowerBoxSharedData;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.net.SocketException;
import java.util.Objects;
import java.util.Timer;
import java.util.function.Consumer;
/**
* The type Client dlpb handler context wrapper.
*/
public class ClientDLPBHandlerContextWrapper extends AbstractDgLabPowerBoxHandlerContextWrapper {
/**
* The Sr msg.
*/
public final boolean SRMsg;
/**
* Instantiates a new Client dlpb handler.
*
* @param SharedData the shared data
* @param role the role
* @param Co the co
*/
public ClientDLPBHandlerContextWrapper(ClientPowerBoxSharedData SharedData, WebSocketClientRole role, ClientOperation Co) {
this(SharedData, role, Co, false);
}
/**
* Instantiates a new Client dlpb handler.
*
* @param SharedData the shared data
* @param role the role
* @param clientOperation the client operation
* @param SRMsg the sr msg
*/
public ClientDLPBHandlerContextWrapper(ClientPowerBoxSharedData SharedData, WebSocketClientRole role, ClientOperation clientOperation, boolean SRMsg) {
super(SharedData, role, clientOperation);
this.SRMsg = SRMsg;
}
/**
* Instantiates a new Client dlpb handler context wrapper.
*
* @param sharedData the shared data
* @param role the role
* @param operation the operation
*/
public ClientDLPBHandlerContextWrapper(ISharedData sharedData, Role role, IOperation operation) {
super(sharedData, role, operation);
this.SRMsg = false;
}
@Override
public ClientPowerBoxSharedData getSharedData() {
return (ClientPowerBoxSharedData) sharedData;
}
@Override
public void SessionBuildInHandle(ChannelHandlerContext session) {
logger.info("连接已建立");
}
@Override
public void ActiveSessionHandle(ChannelHandlerContext session) {
//NOOP
}
@Override
public void SessionCloseHandle(ChannelHandlerContext session) {
PowerBoxMessage breakMsg = PowerBoxMessage.createPowerBoxMessage("break", ConnectionId(), TargetWSId(), "200", new WebSocketClientRole("Cl" + ConnectionId()), new WebSocketServerRole("WebSocketServer"));
if(session != null && session.channel().isActive() && session.channel().isOpen()) {
sendMsgOrData(session, breakMsg);
}
logger.info("连接已断开");
}
@Override
public void ReadMsgHandle(ChannelHandlerContext session, TextWebSocketFrame msg) {
PowerBoxMessage dataMsg;
PowerBoxData data;
String json = msg.text();
if(SRMsg && !TargetWSId().isEmpty()) {
//通过构造开启且如果有目标即连上了APP端则开启Message对象校验只有通过校验才能进读取data()
dataMsg = PowerBoxMessage.getNullMessage().getMessage(json);
//发送对象必须是服务器类型 接收者本客户端为占位对象类型 本客户端对象名字类型相同这里取反只有满足条件才能进入 else读取 data
if(
dataMsg.direction.sender().type != RoleType.T_SERVER
&&
!(
dataMsg.direction.receiver().type == RoleType.PLACEHOLDER
||
(dataMsg.direction.receiver().type == RoleType.T_CLIENT && Objects.equals(dataMsg.direction.receiver().name, role.name))
)
) {
logger.info("消息验证者错误:{}", dataMsg.direction.sender().name);
data = PowerBoxMessage.getNullMessage().getPayload(dataMsg.getInvalidMessageJson());
} else {
data = PowerBoxMessage.getNullMessage().getPayload(dataMsg.getDataJson());
}
} else {
if (SRMsg) {
dataMsg = PowerBoxMessage.getNullMessage().getMessage(json);
data = dataMsg == null ? PowerBoxMessage.getNullMessage().getPayload(json) : PowerBoxMessage.getNullMessage().getPayload(dataMsg.getDataJson());
} else {
data = PowerBoxMessage.getNullMessage().getPayload(json);
}
}
assert data != null;
switch (data.getCommandType()) {
case _NC_BIND_ -> {
if(data.getTargetId().isEmpty()) {
//初次连接客户端获取服务器指定id
((ClientPowerBoxSharedData)sharedData).connectionId = data.getClientId();
logger.info("收到clientId: {}", ConnectionId());
String qrCodeContext = "https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#" + ((ClientPowerBoxSharedData)sharedData).getUrl() + ConnectionId();
TryCatch(i -> ((ClientOperation)operation).QrCodeUrlHandler(qrCodeContext));
TryCatch(i -> ((ClientOperation)operation).ShowQrCodeHandler());
logger.debug("重新生成QrCodeContext: {}",qrCodeContext);
} else {
if(!Objects.equals(data.getClientId(), ConnectionId())) {
logger.debug("接收到不正确的target消息,消息中目标Id{}",data.getClientId());
return;
}
((ClientPowerBoxSharedData)sharedData).targetWSId = data.getTargetId();
logger.info("已建立绑定连接TargetId:{}, msg:{}",TargetWSId(),data.getMessage());
TryCatch(
i -> ((ClientOperation)operation).ConnectSuccessfulNoticeHandler()
);
}
}
case _NC_BREAK_ -> {
//对方断开
if (!Objects.equals(data.getTargetId(), TargetWSId())) {
return;
}
TryCatch(
i -> ((ClientOperation)operation).DisconnectHandler(data)
);
logger.info("对方已断开targetId:{}, msg:{}", TargetWSId(), data.getMessage());
}
case _NC_ERROR_ -> {
if (!Objects.equals(data.getTargetId(), TargetWSId())) {
return;
}
TryCatch(
i -> ((ClientOperation)operation).ErrorHandler(data)
);
logger.info("接收到错误码:{}",data.getMessage());
}
case _NC_HEARTBEAT_ -> {
logger.info("收到心跳");
TryCatch(
i -> ((ClientOperation)operation).HeartBeatHandler(data)
);
((ClientPowerBoxSharedData)sharedData).connectionId = data.getClientId();
role.UpdateName("Cl" + ((ClientPowerBoxSharedData)sharedData).connectionId);
}
default -> {
logger.info("收到其它信息{}",data.getMessage());
TryCatch(
i -> ((ClientOperation)operation).OtherMessageHandler(data)
);
}
}
}
private void TryCatch(Consumer<Void> event) {
try {
event.accept(null);
} catch (Exception e) {
logger.error("DataTypeHandler Error:{}", e.getMessage());
}
}
@Override
public void ErrorHandler(ChannelHandlerContext session, Throwable cause) {
logger.error("连接出现错误:{}: {}",cause.getMessage(), cause.getStackTrace());
//错误处理逻辑待加入
if(cause instanceof SocketException) {
logger.info("远程服务器关闭");
}
}
/**
* Send msg or data.
*
* @param target the target
* @param dataMsg the data msg
*/
protected void sendMsgOrData(ChannelHandlerContext target, PowerBoxMessage dataMsg) {
String json = SRMsg ? dataMsg.getMsgJson() : dataMsg.getDataJson();
target.channel().writeAndFlush(new TextWebSocketFrame(json));
}
/**
* Connection id string.
*
* @return the string
*/
public String ConnectionId() {
return ((ClientPowerBoxSharedData)sharedData).connectionId;
}
/**
* Target ws id string.
*
* @return the string
*/
public String TargetWSId() {
return ((ClientPowerBoxSharedData)sharedData).targetWSId;
}
/**
* Delay int.
*
* @return the int
*/
public int Delay() {
return ((ClientPowerBoxSharedData)sharedData).delay;
}
/**
* Delay timer timer.
*
* @return the timer
*/
public Timer DelayTimer() {
return ((ClientPowerBoxSharedData)sharedData).delayTimer;
}
/**
* Follow a strength boolean.
*
* @return the boolean
*/
public boolean FollowAStrength() {
return ((ClientPowerBoxSharedData)sharedData).followAStrength;
}
/**
* Follow b strength boolean.
*
* @return the boolean
*/
public boolean FollowBStrength() {
return ((ClientPowerBoxSharedData)sharedData).followBStrength;
}
}

View File

@ -0,0 +1,91 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler.client;
import com.r3944realms.dg_lab.api.operation.ClientOperation;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
/**
* The type Default client operation.
*/
public class DefaultClientOperation implements ClientOperation {
@Override
public void ClientStartingHandler() {
//NOOP
}
@Override
public void ClientStartedHandler() {
//NOOP
}
@Override
public void ClientStartingErrorHandler() {
//NOOP
}
@Override
public void ClientStoppingHandler() {
//NOOP
}
@Override
public void ClientStoppingErrorHandler() {
//NOOP
}
@Override
public void ClientStoppedHandler() {
//NOOP
}
@Override
public void QrCodeUrlHandler(String qrCodeUrl) {
//NOOP
System.out.println(qrCodeUrl);
}
@Override
public void ShowQrCodeHandler() {
//NOOP
}
@Override
public void ConnectSuccessfulNoticeHandler() {
//NOOP
}
@Override
public void DisconnectHandler(final PowerBoxData data) {
//NOOP
}
@Override
public void ErrorHandler(PowerBoxData data) {
//NOOP
}
@Override
public void HeartBeatHandler(PowerBoxData data) {
//NOOP
}
@Override
public void OtherMessageHandler(PowerBoxData data) {
//NOOP
}
}

View File

@ -0,0 +1,120 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler.server;
import com.r3944realms.dg_lab.api.operation.ServerOperation;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
/**
* The type Default server operation.
*/
public class DefaultServerOperation implements ServerOperation {
@Override
public void HeartbeatMessageHandler(PowerBoxMessage message) {
//NOOP
}
@Override
public void ClearMessageHandler(PowerBoxMessage message) {
//NOOP
}
@Override
public void FeedbackMessageHandler(PowerBoxMessage message) {
//NOOP
}
@Override
public void StrengthMessageNoticeMessage(PowerBoxMessage message) {
//NOOP
}
@Override
public void StrengthMessageChangeHandler(PowerBoxMessage message) {
//NOOP
}
@Override
public void PulseClientMessageHandler(PowerBoxMessage clearMessage, PowerBoxMessage pulseMessage) {
//NOOP
}
@Override
public void PulseClientMessageHandler(PowerBoxMessage clearMessage, int delayTime, PowerBoxMessage pulseMessage) {
//NOOP
}
@Override
public void BreakConnectMessageHandler(PowerBoxMessage message) {
//NOOP
}
@Override
public void OtherMessageHandler(PowerBoxMessage message) {
//NOOP
}
@Override
public void ServerStartingHandler() {
//NOOP
}
@Override
public void ServerStartingErrorHandler() {
}
@Override
public void ServerStartedHandler() {
//NOOP
}
@Override
public void ServerStoppingHandler() {
//NOOP
}
@Override
public void ServerStoppingErrorHandler() {
}
@Override
public void ServerStoppedHandler() {
//NOOP
}
@Override
public void InactiveConnectionRemoveHandler(String clientId) {
//NOOP
}
@Override
public void ErrorMessageHandler(PowerBoxMessage message) {
//NOOP
}
@Override
public void BindSuccessMessageHandler(PowerBoxMessage message) {
//NOOP
}
@Override
public void BindFailureMessageHandler(PowerBoxMessage message) {
//NOOP
}
}

View File

@ -0,0 +1,82 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler.server;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The type Http request handler.
*/
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final Logger logger = LoggerFactory.getLogger(HttpRequestHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
// 读取原始请求的 URI
String requestUri = request.uri();
// 修改 URI去掉路径部分只保留 "/"
String modifiedUri = "/";
// 检查是否是 WebSocket 升级请求
String upgradeHeader = request.headers().get(HttpHeaderNames.UPGRADE);
String webSocketVersion = request.headers().get(HttpHeaderNames.SEC_WEBSOCKET_VERSION);
if ("WebSocket".equalsIgnoreCase(upgradeHeader) &&
"13".equals(webSocketVersion)) { // 注意WebSocket 版本通常为 "13"
// 创建一个新的 FullHttpRequest 实例修改 URI
FullHttpRequest modifiedRequest = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1,
request.method(),
modifiedUri,
request.content().retain(), // 保留内容以避免意外释放
request.headers().copy(),
request.trailingHeaders().copy()
);
// 将修改后的 URI 设置到上下文中
ctx.channel().attr(AttributeKey.valueOf("ws-uri")).set(modifiedUri);
// 传递修改后的请求到下一个处理器
ctx.fireChannelRead(modifiedRequest);
} else {
// 如果不是 WebSocket 升级请求返回拒绝
sendForbiddenResponse(ctx, request);
}
}
private void sendForbiddenResponse(ChannelHandlerContext ctx, FullHttpRequest request) {
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
response.content().writeBytes("Access denied".getBytes());
// Write the response and close the connection
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error(cause.getMessage(), cause);
ctx.close();
}
}

View File

@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler.server;
import com.r3944realms.dg_lab.websocket.handler.AbstractDgLabPowerBoxHandler;
import com.r3944realms.dg_lab.websocket.handler.AbstractDgLabPowerBoxHandlerContextWrapper;
import com.r3944realms.dg_lab.websocket.handler.IAttachSharedData;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
import io.netty.channel.ChannelHandler;
/**
* The type Server dlpb handler.
*/
@ChannelHandler.Sharable
public class ServerDLPBHandler extends AbstractDgLabPowerBoxHandler implements IAttachSharedData {
/**
* Instantiates a new Server dlpb handler.
*
* @param contextWrapper the context wrapper
*/
public ServerDLPBHandler(AbstractDgLabPowerBoxHandlerContextWrapper contextWrapper) {
super(contextWrapper);
}
@Override
public ISharedData getSharedData() {
return contextWrapper.getSharedData();
}
}

View File

@ -0,0 +1,645 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.handler.server;
import com.google.gson.JsonSyntaxException;
import com.r3944realms.dg_lab.api.operation.ServerOperation;
import com.r3944realms.dg_lab.api.websocket.message.role.*;
import com.r3944realms.dg_lab.utils.stringUtils.StringHandlerUtil;
import com.r3944realms.dg_lab.utils.timeTask.DgLabTimerTask;
import com.r3944realms.dg_lab.websocket.handler.AbstractDgLabPowerBoxHandlerContextWrapper;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxDataWithSingleAttachment;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxStatusCode;
import com.r3944realms.dg_lab.api.websocket.message.role.type.RoleType;
import com.r3944realms.dg_lab.websocket.sharedData.ServerPowerBoxSharedData;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* The type Server dlpb handler context wrapper.
*/
public class ServerDLPBHandlerContextWrapper extends AbstractDgLabPowerBoxHandlerContextWrapper {
/**
* Instantiates a new Server dlpb handler.
*
* @param serverPowerBoxSharedData the server power box shared data
* @param role the role
* @param serverOperation the server operation
*/
public ServerDLPBHandlerContextWrapper(ServerPowerBoxSharedData serverPowerBoxSharedData, WebSocketServerRole role, ServerOperation serverOperation) {
super(serverPowerBoxSharedData, role, serverOperation);
}
public ServerPowerBoxSharedData getSharedData() {
return (ServerPowerBoxSharedData) sharedData;
}
@Override
public void SessionBuildInHandle(ChannelHandlerContext session) {
String clientId;
do {
clientId = UUID.randomUUID().toString();
} while (ChannelIdMap().containsKey(clientId));
ChannelIdMap().put(session.channel().id().asLongText(), clientId);
logger.debug("channel added: clientId={}", clientId);
Channel().add(session.channel());
Connections().put(clientId, session);
logger.info("新的 webSocket 连接已建立, 标识符为{}", clientId);
}
@Override
public void ActiveSessionHandle(ChannelHandlerContext session) {
String clientId = ChannelIdMap().get(session.channel().id().asLongText());
PowerBoxMessage bindMsg = PowerBoxMessage.createPowerBoxMessage("bind", clientId, "", "targetId",
role, new PlaceholderRole("Pl" + clientId)
);
// 延迟发送消息
session.executor().schedule(() -> {
if (session.channel().isActive() && session.channel().isOpen()) {
session.channel().writeAndFlush(new TextWebSocketFrame(bindMsg.getDataJson())).addListener(sendFuture -> {
if (sendFuture.isSuccess()) {
logger.info("#1 Message sent successfully to clientId={}", clientId);
} else {
logger.error("#1 Failed to send message to clientId={}", clientId, sendFuture.cause());
}
});
} else {
logger.debug("Channel is not active, message not sent to clientId={}", clientId);
}
}, 200, TimeUnit.MILLISECONDS); // 延迟200毫秒发送消息
if(HeartTimer() != null) return;
synchronized (ServerDLPBHandler.class) {
// 启动心跳定时器如果尚未启动
if (HeartTimer() == null) {
((ServerPowerBoxSharedData)sharedData).heartTimer = new Timer();
HeartTimer().schedule(new TimerTask() {
@Override
public void run() {
if (!Connections().isEmpty()) {
logger.debug("关系池大小:{}连接池大小:{},发送心跳消息", Relations().size(), Connections().size());
}
for (String clientId : Connections().keySet()) {
ChannelHandlerContext client = Connections().get(clientId);
if (client != null && client.channel().isActive() && client.channel().isOpen()) {
String targetId = Relations().get(clientId);
if (ObjectUtils.isEmpty(targetId)) {
targetId = "";
}
Role receiverRole = getRole(clientId);
PowerBoxMessage message =
PowerBoxMessage.createPowerBoxMessage("heartbeat", clientId, targetId, PowerBoxStatusCode.SUCCESSFUL,
role, receiverRole);
TryCatch(n -> ((ServerOperation)operation).HeartbeatMessageHandler(message));
sendMessageOrData(ServerDLPBHandlerContextWrapper.this, clientId, message);
} else {
logger.debug("Channel is not active, skipping heartbeat for clientId={}", clientId);
TryCatch(n -> ((ServerOperation)operation).InactiveConnectionRemoveHandler(clientId));
Connections().remove(clientId);//不活跃移除对应连接
}
}
}
}, 500, 60000/*ms = 1min*/);
}
}
}
private @NotNull Role getRole(String clientId) {
RoleType roleType = getRoleType(this.Relations(), clientId);
Role receiverRole;
switch (roleType) {
case T_CLIENT -> receiverRole = new WebSocketClientRole("Cl" + clientId);
case APPLICATION -> receiverRole = new WebSocketApplicationRole("Ap" + clientId);
default -> receiverRole = new PlaceholderRole("Pl" + clientId);
}
return receiverRole;
}
@Override
public void SessionCloseHandle(ChannelHandlerContext session) {
logger.debug("Websocket 连接已关闭");
String channelId = session.channel().id().asLongText();
String disconnectId = ChannelIdMap().remove(channelId);
logger.debug("channel removed: channelId={}", channelId);
logger.info("断开的client Id:{}", disconnectId);
Channel().remove(session.channel());
for (String _clientId_ : Relations().keySet()) {
String _targetId_ = Relations().get(_clientId_);
if (_clientId_.equals(disconnectId)) {
//Client断开 通知app
ChannelHandlerContext appClient = Connections().get(_targetId_);
PowerBoxMessage message =
PowerBoxMessage.createPowerBoxMessage("break", disconnectId, _targetId_, PowerBoxStatusCode.OPPOSITE_CLIENT_DISCONNECTED, role, new WebSocketApplicationRole("Ap" + _targetId_));
TryCatch(n -> ((ServerOperation)operation).BreakConnectMessageHandler(message));
sendMessageData(this, appClient, message);
appClient.close(); //关闭当前的 websocket 连接
Relations().remove(_clientId_); // 清除关系
PowerBoxDataMap().remove(_targetId_);
logger.debug("Close Application Connecting{}", _targetId_);
Connections().remove(_targetId_);//并移除targetId
} else if (_targetId_.equals(disconnectId)) {
//app断开,通知Client
ChannelHandlerContext client = Connections().get(_clientId_);
PowerBoxMessage message =
PowerBoxMessage.createPowerBoxMessage("break", _clientId_, _targetId_, PowerBoxStatusCode.OPPOSITE_CLIENT_DISCONNECTED, role, new WebSocketClientRole("Cl" + _clientId_));
TryCatch(n -> ((ServerOperation)operation).BreakConnectMessageHandler(message));
sendMessageText(this, client, message);
client.close();//关闭当前的 websocket 连接
Relations().remove(_clientId_);// 清除关系
PowerBoxDataMap().remove(_targetId_);
logger.debug("Close Client Connecting{}", _targetId_);
Connections().remove(_clientId_);//并移除clientId
}
Connections().remove(disconnectId);//移除断开连接的对方
Channel().remove(session.channel());
logger.debug("channel removed: channelId={}[Current Size:{}]", channelId, Connections().size());
}
}
@Override
public void ReadMsgHandle(ChannelHandlerContext session, TextWebSocketFrame msg) {
logger.debug("收到消息:{}", msg);
PowerBoxMessage powerBoxMessage, errorMsg;
PowerBoxData data;
String text = msg.text();
try {
powerBoxMessage = PowerBoxMessage.getNullMessage().getMessage(text);
data = PowerBoxMessage.getNullMessage().getPayload(powerBoxMessage.getDataJson());
} catch (JsonSyntaxException e) {
errorMsg = PowerBoxMessage.createPowerBoxMessage("error", "", "", PowerBoxStatusCode.NOT_STANDARD_JSON,
role, new WebSocketClientRole("ErrorReceiver"));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(errorMsg));
sendMessageText(this, session, errorMsg);
return;
} catch (Exception e) { //非JSON消息
data = PowerBoxMessage.getNullMessage().getPayload(text);
}
if (!Connections().containsKey(data.getClientId()) && !Connections().containsKey(data.getTargetId())) { //非法信息来源拒绝
errorMsg = PowerBoxMessage.createPowerBoxMessage("error", "", "", PowerBoxStatusCode.NOT_FOUND_BECAUSE_OF_OFFLINE,
role, new WebSocketClientRole("ErrorReceiver"));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(errorMsg));
sendMessageText(this, session, errorMsg);
return;
}
if (!ObjectUtils.isEmpty(data.getType()) && !ObjectUtils.isEmpty(data.getClientId()) && !ObjectUtils.isEmpty(data.getTargetId()) && !ObjectUtils.isEmpty(data.getMessage())) {
String type = data.getType();
String clientId = data.getClientId();
String targetId = data.getTargetId();
String message = data.getMessage();
PowerBoxDataType dataType = PowerBoxDataType.getType(type, message);
DataTypeHandler(session, dataType, clientId, targetId, message, data);
}
}
private void DataTypeHandler(ChannelHandlerContext session, PowerBoxDataType dataType, String clientId, String targetId, String message, PowerBoxData data) {
switch (dataType) {
case _NC_BIND_ -> {
//服务器下发绑定关系
if (Connections().containsKey(clientId) && Connections().containsKey(targetId)){
//relations的双方都不存在这对id 信息为DGLAB
if (!(Relations().containsKey(clientId) || Relations().containsKey(targetId) || Relations().containsValue(clientId) || Relations().containsValue(targetId)) && message.equals("DGLAB")) {
Relations().put(clientId, targetId);
ChannelHandlerContext client = Connections().get(clientId);
PowerBoxMessage bindMessage = PowerBoxMessage.createPowerBoxMessage("bind", clientId, targetId, PowerBoxStatusCode.SUCCESSFUL, role, new PlaceholderRole("Both: " + clientId + " ^ " + targetId));
TryCatch(n -> ((ServerOperation)operation).BindSuccessMessageHandler(bindMessage));
sendMessageData(this, session, bindMessage);
sendMessageText(this, client, bindMessage);
} else {
PowerBoxMessage failure = PowerBoxMessage.createPowerBoxMessage("bind", clientId, targetId, PowerBoxStatusCode.TRYING_BINDING_ALREADY_BOUND_ID, role, new WebSocketApplicationRole("Ap" + targetId));
TryCatch(n -> ((ServerOperation)operation).BindFailureMessageHandler(failure));
sendMessageData(this, session, failure);
}
} else {
PowerBoxMessage failure = PowerBoxMessage.createPowerBoxMessage("bind", clientId, targetId, PowerBoxStatusCode.TARGET_CLIENT_NOT_EXIST, role, new WebSocketApplicationRole("Ap" + targetId));
TryCatch(n -> ((ServerOperation)operation).BindFailureMessageHandler(failure));
sendMessageData(this, session, failure);
}
}
case STRENGTH,PULSE,CLEAR,FEEDBACK -> {
if (Relations().containsKey(clientId) && !Relations().get(clientId).equals(targetId)) {//没有绑定关系
PowerBoxMessage error = PowerBoxMessage.createPowerBoxMessage("bind", clientId, targetId, PowerBoxStatusCode.NOT_BINDING_RELATIONSHIP, role, new WebSocketApplicationRole("Ap" + targetId));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(error));
sendMessageData(this, session, error);
return;
}
Object[] argsArray = data.getArgsArray();
if (argsArray == null) {
PowerBoxMessage error = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, PowerBoxStatusCode.INTERNAL_ERROR, role, new WebSocketApplicationRole("Ap" + targetId));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(error));
sendMessageData(this, session, error);
return;
}
switch (dataType) {
case STRENGTH -> {
if(Connections().containsKey(targetId)) {
ChannelHandlerContext app = Connections().get(targetId);
ChannelHandlerContext client = Connections().get(clientId);
if (argsArray.length == 3){
int channel = ((Integer[])argsArray)[0];
int policyChange = ((Integer[])argsArray)[1];
int strengthChangeValue = ((Integer[])argsArray)[2];
String messageCommand = "strength-" + channel + "+" + policyChange + "+" + strengthChangeValue;
PowerBoxMessage strengthUpdate = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, messageCommand, role, new WebSocketApplicationRole("Ap" + targetId));
TryCatch(n -> ((ServerOperation)operation).StrengthMessageChangeHandler(strengthUpdate));
sendMessageData(this, app, strengthUpdate);
} else if(argsArray.length == 4){
int AStrength = ((Integer[])argsArray)[0];
int BStrength = ((Integer[])argsArray)[1];
int ALimit = ((Integer[])argsArray)[2];
int BLimit = ((Integer[])argsArray)[3];
putDataInMap(targetId, data);
String currentStrengthMsg = "strength-" + AStrength + "+" + BStrength + "+" + ALimit + "+" + BLimit;
PowerBoxMessage clientMsg = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, currentStrengthMsg, role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).StrengthMessageNoticeMessage(clientMsg));
sendMessageText(this, client, clientMsg);
}
}
}
case PULSE -> {
//Thrown unsupportedMsg
PowerBoxMessage unsupportedMsg = PowerBoxMessage.createPowerBoxMessage("error", clientId, targetId, PowerBoxStatusCode.UNSUPPORTED_OPERATION, role, new WebSocketClientRole("Cl" + clientId));
//"unsupported-reason:(please use type named clientMsg to adjust wave in order to get the timers of AB)"
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(unsupportedMsg));
sendMessageText(this , session, unsupportedMsg);//发送给客户端
}
case CLEAR -> {
if (Connections().containsKey(targetId)) {
ChannelHandlerContext client = Connections().get(targetId);
String Channel = ((String[])argsArray)[0];
String messageCommand = "clear-" + Channel;
PowerBoxMessage strengthUpdate = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, messageCommand, role, new WebSocketApplicationRole("Ap" + targetId));
TryCatch(n -> ((ServerOperation)operation).ClearMessageHandler(strengthUpdate));
sendMessageData(this, client, strengthUpdate);
}
}
case FEEDBACK -> {
if (Connections().containsKey(targetId)) {
int index = ((Integer[])argsArray)[0];
String feedBack = "feedback-" + index;
PowerBoxMessage feedBackMsg = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, feedBack, role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).FeedbackMessageHandler(feedBackMsg));
sendMessageOrData(this, clientId, feedBackMsg);
}
}
}
}
case CLIENT_MESSAGE -> {//接收到客户端消息(客户端的作用是附带Timer)
if (!Relations().get(clientId).equals(targetId)) {
PowerBoxMessage error = PowerBoxMessage.createPowerBoxMessage("bind", clientId, targetId, PowerBoxStatusCode.NOT_BINDING_RELATIONSHIP, role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(error));
sendMessageText(this, session, error);
} else if (ObjectUtils.isEmpty(message)){
PowerBoxMessage error = PowerBoxMessage.createPowerBoxMessage("error", clientId, targetId, PowerBoxStatusCode.INVALID_REQUEST, role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(error));
sendMessageText(this, session, error);
} else if (Relations().containsValue(targetId)) {
String[] args = (String[]) data.getArgsArrayByPointing(PowerBoxDataType.PULSE);
char channel = StringHandlerUtil.getCharForString(args[0],0);
ChannelHandlerContext app = Connections().get(targetId);
String[] waveDataList = new String[args.length - 1];
System.arraycopy(args, 1, waveDataList, 0, args.length - 1);
String waveDataListString = StringHandlerUtil.reformWaveDataList(waveDataList);
String messageCommand = "pulse-"+ channel + ":" + waveDataListString;
PowerBoxMessage pulseMsg = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, messageCommand, role, new WebSocketApplicationRole("Ap" + targetId));
if (data instanceof PowerBoxDataWithSingleAttachment dataWithSingleAttachment) {
Integer timer = dataWithSingleAttachment.getTimer();
int sendTime = dataWithSingleAttachment.isValid() ? (timer != null ? timer : PunishmentTime()) : PunishmentTime();
Integer totalSends = PunishmentTime() * sendTime;
Integer timesSpace = 1000 / PunishmentTime();
logger.debug("频道{}消息发送中,频道消息数:{},持续时间:{}",channel, totalSends, sendTime);
if (ClientTimers().containsKey(clientId)) {
//接收消息未工作完毕清除旧计时器并发送清除App队列消息
PowerBoxMessage clearAndUpdateMsg = PowerBoxMessage.createPowerBoxMessage("clientMsg", clientId, targetId, "over-previous-value", role, new WebSocketClientRole("Cl" + clientId));
sendMessageOrData(this, clientId, clearAndUpdateMsg);
Timer timerId = ClientTimers().get(clientId + "-" + channel);
clearInterval(clientId, timerId, channel);
ClientTimers().remove(clientId + "-" + channel);
//发送 App波形队列清除指令
String commandMsg = "clear-" + (channel == 'A' ? "1": "2") ;//非A即B
PowerBoxMessage clear = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, commandMsg, role, new WebSocketApplicationRole("Ap" + targetId));
TryCatch(n -> ((ServerOperation)operation).PulseClientMessageHandler(clear, totalSends, pulseMsg));
sendMessageData(this, app, clear);
Promise<Void> promise = session.newPromise();
session.executor().schedule(() -> {
promise.setSuccess(null);
}, 150, TimeUnit.MILLISECONDS);
promise.addListener((FutureListener<Void>) future -> {
if (future.isSuccess()) {
delaySendMsg(clientId, session, app, pulseMsg, totalSends, timesSpace, channel);
} else logger.error("WTF?(╯‵□′)╯︵┻━┻");
});
} else {
TryCatch(n -> ((ServerOperation)operation).PulseClientMessageHandler(null, totalSends, pulseMsg));
delaySendMsg(clientId, session, app, pulseMsg, totalSends, timesSpace, channel);
}//如果不存在未发送玩的消息 直接发送
} else {
PowerBoxMessage unsupportedMsg = PowerBoxMessage.createPowerBoxMessage("error", clientId, targetId, PowerBoxStatusCode.UNSUPPORTED_OPERATION, role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(unsupportedMsg));
sendMessageText(this , session, unsupportedMsg);//发送给客户端
}
} else {
logger.debug("未找到匹配的客户端,clientId={}", clientId);
PowerBoxMessage notFound = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, PowerBoxStatusCode.NOT_FOUND_BECAUSE_OF_OFFLINE, role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(notFound));
sendMessageText(this, session, notFound);
}
}
default -> {
if (!Relations().get(clientId).equals(targetId)) {
PowerBoxMessage error = PowerBoxMessage.createPowerBoxMessage("bind", clientId, targetId, PowerBoxStatusCode.NOT_BINDING_RELATIONSHIP, role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(error));
sendMessageText(this, session, error);
} else if (Connections().containsKey(clientId)) {
ChannelHandlerContext client = Connections().get(clientId);
PowerBoxMessage defaultMsg = PowerBoxMessage.createPowerBoxMessage(data.getType(), clientId, targetId, data.getMessage(), role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).OtherMessageHandler(defaultMsg));
sendMessageText(this, client, defaultMsg);
} else {
PowerBoxMessage notFound = PowerBoxMessage.createPowerBoxMessage("msg", clientId, targetId, PowerBoxStatusCode.NOT_FOUND_BECAUSE_OF_OFFLINE, role, new WebSocketClientRole("Cl" + clientId));
TryCatch(n -> ((ServerOperation)operation).ErrorMessageHandler(notFound));
sendMessageText(this, session, notFound);
}
}
}
}
private void TryCatch(Consumer<Void> event) {
try {
event.accept(null);
} catch (Exception e) {
logger.error("DataTypeHandler Error:{}", e.getMessage());
}
}
@Override
public void ErrorHandler(ChannelHandlerContext session, Throwable cause) {
// java.io.IOException: 远程主机强迫关闭了一个现有的连接 这个错误是由于连接直接被关闭 无需处理
if (cause instanceof IOException) {
return;
}
logger.error("WebSocket 异常{}:{}", cause.getClass(),cause.getMessage());
//在此通知用户异常提过Websocket 发送消息给对方
String _errorSideId_ = ChannelIdMap().get(session.channel().id().asLongText());
if(ObjectUtils.isEmpty(_errorSideId_)) {
logger.debug("Can't found the client");
return;
}
//构造错误信息
String errorMessage = "Websocket异常 " + cause.getMessage();
for (String _clientId_ : Relations().keySet()) {
String _targetId_ = Relations().get(_clientId_);
if (_clientId_.equals(_errorSideId_)) {//clientId = value
//通知app
ChannelHandlerContext appClient = Connections().get(_targetId_);
PowerBoxMessage error = PowerBoxMessage.createPowerBoxMessage("error", _clientId_, _targetId_, PowerBoxStatusCode.INTERNAL_ERROR, role, new WebSocketApplicationRole("Ap" + _targetId_));
sendMessageData(this, appClient, error);
} else if (_targetId_.equals(_errorSideId_)) {
//通知client
ChannelHandlerContext client = Connections().get(_clientId_);
PowerBoxMessage error = PowerBoxMessage.createPowerBoxMessage("error", _clientId_ , _targetId_, errorMessage, role, new WebSocketClientRole("Cl" + _targetId_) );
sendMessageData(this, client, error);
}
}
}
private void putDataInMap(String targetId, PowerBoxData data) {
if (PowerBoxDataMap().containsKey(targetId))PowerBoxDataMap().replace(targetId, data);
else PowerBoxDataMap().put(targetId, data);
}
/**
* 根据UUID对应对象的类型来决定发送的数据类型JSON
* @param uuid 能处理Message对象的目标UUID
* @param msg Message{@link Message}
*/
private static void sendMessageOrData(ServerDLPBHandlerContextWrapper contextWrapper, String uuid, Message msg) {
switch (getRoleType(contextWrapper.Relations(), uuid)) {
case T_CLIENT -> sendMessageText(contextWrapper, contextWrapper.Connections().get(uuid), msg);
case PLACEHOLDER,APPLICATION -> sendMessageData(contextWrapper, contextWrapper.Connections().get(uuid), msg);
}
}
/**
*
* @param target 能处理Message对象的目标
* @param msg Message{@link Message}
*/
private static void sendMessageText(ServerDLPBHandlerContextWrapper contextWrapper, ChannelHandlerContext target, Message msg) {
if (target == null) throw new NullPointerException("target is null");
if (msg == null) throw new NullPointerException("message is null");
try {
String json = msg.getMsgJson();
writeAndFlushAndFlush(contextWrapper, target, json);
} catch (Exception e) {
logger.error("Error sending message{}", e.getMessage());
}
}
/**
* 发送Message的Data数据
*
* @param contextWrapper ServerDLPBHandlerContextWrapper
* @param target 能处理Message对象的目标
* @param msg 待转化为Message{@link Message}
*/
protected static void sendMessageData(ServerDLPBHandlerContextWrapper contextWrapper, ChannelHandlerContext target, Message msg) {
if (target == null) throw new NullPointerException("target is null");
if (msg == null) throw new NullPointerException("message is null");
try {
String json = msg.getDataJson();
writeAndFlushAndFlush(contextWrapper, target, json);
} catch (Exception e){
logger.error("Error sending message{}", e.getMessage());
}
}
private static void writeAndFlushAndFlush(ServerDLPBHandlerContextWrapper contextWrapper, ChannelHandlerContext target, String json) {
target.channel().writeAndFlush(new TextWebSocketFrame(json)).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
logger.debug("消息已成功发送到客户端 UUID={},消息内容={}", contextWrapper.ChannelIdMap().get(target.channel().id().asLongText()), json);
} else {
logger.error("消息发送失败,客户端 UUID={},消息内容={}", contextWrapper.ChannelIdMap().get(target.channel().id().asLongText()) , json, future.cause());
}
});
}
/**
* Delay send msg.
*
* @param clientId the client id
* @param client the client
* @param target the target
* @param message the message
* @param totalSends the total sends
* @param timeSpace the time space
* @param channel the channel
*/
public void delaySendMsg(String clientId, ChannelHandlerContext client, ChannelHandlerContext target, Message message, Integer totalSends, Integer timeSpace, char channel) {
sendMessageData(this, target, message);
totalSends--;
if (totalSends> 0 ) {
Timer timer = new Timer();
DgLabTimerTask task = new DgLabTimerTask(client, target, message, totalSends, k -> clearInterval(clientId, timer, channel), channel);
timer.scheduleAtFixedRate(task, 0, timeSpace.longValue());//周期触发
ClientTimers().put(clientId + "-" + channel, timer);//存储对应的·clientId与频道
}
}
/**
* Clear interval.
*
* @param clientId the client id
* @param timer the timer
* @param channel the channel
*/
public void clearInterval(String clientId, Timer timer, char channel) {
timer.cancel();
ClientTimers().remove(clientId + "-" + channel); // 删除对应的定时器
}
/**
* Gets data.
*
* @param contextWrapper ServerDLPBHandlerContextWrapper
* @param clientId 客户端uuid
* @return PowerData 客户端Data消息存储的一般是对应的pulse数据
* @throws NullPointerException 如果对于clientId未存入对应数据
*/
public static PowerBoxData getData(ServerDLPBHandlerContextWrapper contextWrapper, String clientId) throws NullPointerException {
return contextWrapper.PowerBoxDataMap().get(clientId);
}
/**
* Channel channel group.
*
* @return the channel group
*/
public ChannelGroup Channel() {
return ((ServerPowerBoxSharedData)sharedData).channels;
}
/**
* Channel id map map.
*
* @return the map
*/
public Map<String, String> ChannelIdMap() {
return ((ServerPowerBoxSharedData)sharedData).channelIdMap;
}
/**
* Connections map.
*
* @return the map
*/
public Map<String, ChannelHandlerContext> Connections() {
return ((ServerPowerBoxSharedData)sharedData).connections;
}
/**
* Power box data map map.
*
* @return the map
*/
public Map<String, PowerBoxData> PowerBoxDataMap() {
return ((ServerPowerBoxSharedData)sharedData).powerBoxDataMap;
}
/**
* Relations map.
*
* @return the map
*/
public Map<String, String> Relations() {
return ((ServerPowerBoxSharedData)sharedData).relations;
}
/**
* Client timers map.
*
* @return the map
*/
public Map<String, Timer> ClientTimers() {
return ((ServerPowerBoxSharedData)sharedData).clientTimers;
}
/**
* Punishment duration int.
*
* @return the int
*/
public int PunishmentDuration() {
return ((ServerPowerBoxSharedData)sharedData).punishmentDuration;
}
/**
* Punishment time int.
*
* @return the int
*/
public int PunishmentTime() {
return ((ServerPowerBoxSharedData)sharedData).punishmentTime;
}
/**
* Heart timer timer.
*
* @return the timer
*/
public Timer HeartTimer() {
return ((ServerPowerBoxSharedData)sharedData).heartTimer;
}
private static RoleType getRoleType(Map<String, String> relations, String uuid) {
if (relations.containsKey(uuid)) return RoleType.T_CLIENT;
else if (relations.containsValue(uuid)) return RoleType.APPLICATION;
else return RoleType.PLACEHOLDER;
}
/**
* Gets role type.
*
* @param sharedData the shared data
* @param uuid the uuid
* @return the role type
*/
public static RoleType getRoleType(ServerPowerBoxSharedData sharedData, String uuid) {
return getRoleType(sharedData.relations, uuid);
}
}

View File

@ -0,0 +1,98 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.sharedData;
import com.r3944realms.dg_lab.utils.stringUtils.StringHandlerUtil;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
import java.util.Timer;
/**
* 客户端PowerBox共享数据
*/
public class ClientPowerBoxSharedData implements ISharedData {
@SuppressWarnings("MethodDoesntCallSuperMethod")
@Override
public ClientPowerBoxSharedData clone() {
ClientPowerBoxSharedData clone = new ClientPowerBoxSharedData();
clone.connectionId = connectionId;
clone.targetWSId = targetWSId;
clone.delay = delay;
clone.address = address;
clone.port = port;
return clone;
}
/**
* The Connection id.
*/
public String connectionId = "",
/**
* The Target ws id.
*/
targetWSId = "";
/**
* The Delay.
*/
public int delay = 500; //防抖
/**
* The Delay timer.
*/
public Timer delayTimer;
/**
* The Address.
*/
public String address;
/**
* The Port.
*/
public int port;
/**
* The Follow a strength.
*/
//跟随AB的软上限
public final boolean followAStrength = false;
/**
* The Follow b strength.
*/
public final boolean followBStrength = false;
/**
* Instantiates a new Client power box shared data.
*
* @param delay the delay
*/
public ClientPowerBoxSharedData(int delay) {
this.delay = delay > 0 ? delay : 500;
}
/**
* Instantiates a new Client power box shared data.
*/
public ClientPowerBoxSharedData() {}
/**
* Gets url.
*
* @return the url
*/
public String getUrl() {
return StringHandlerUtil.buildWebSocketURL(address, port, false);
}
}

View File

@ -0,0 +1,112 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.websocket.sharedData;
import com.google.common.collect.Maps;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.util.Map;
import java.util.Timer;
/**
* 服务器PowerBox共享数据
*/
public class ServerPowerBoxSharedData implements ISharedData,Cloneable {
@SuppressWarnings("MethodDoesntCallSuperMethod")
@Override
public ServerPowerBoxSharedData clone() {
ServerPowerBoxSharedData clone = new ServerPowerBoxSharedData(punishmentDuration, punishmentTime);
clone.channels.addAll(channels);
clone.channelIdMap.putAll(channelIdMap);
clone.powerBoxDataMap.putAll(powerBoxDataMap);
clone.connections.putAll(connections);
clone.relations.putAll(relations);
clone.clientTimers.putAll(clientTimers);
//不会给计时器(因为毫无意义)
return clone;
}
/**
* The Channels.
*/
public final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* The Channel id map.
*/
// 映射表
public final Map<String, String> channelIdMap = Maps.newConcurrentMap();
/**
* The Power box data map.
*/
// targetId -> PowerData
public final Map<String, PowerBoxData> powerBoxDataMap = Maps.newConcurrentMap();
/**
* The Connections.
*/
// 储存已连接的用户及其标识
public final Map<String, ChannelHandlerContext> connections = Maps.newConcurrentMap();
/**
* The Relations.
*/
// 存储消息关系
public final Map<String, String> relations = Maps.newConcurrentMap();
/**
* The Client timers.
*/
// 存储定时器
public final Map<String, Timer> clientTimers = Maps.newConcurrentMap();
/**
* The Punishment duration.
*/
//默认发送时间1秒
public final int punishmentDuration;
/**
* The Punishment time.
*/
// 默认一秒发送1次
public final int punishmentTime;
/**
* The Heart timer.
*/
// 心跳定时器该为线程安全的类
public Timer heartTimer = null;
/**
* Instantiates a new Server power box shared data.
*/
public ServerPowerBoxSharedData() {
this(5, 1);
}
/**
* Instantiates a new Server power box shared data.
*
* @param punishmentDuration the punishment duration
* @param punishmentTime the punishment time
*/
public ServerPowerBoxSharedData(int punishmentDuration, int punishmentTime) {
this.punishmentDuration = punishmentDuration > 0 ? punishmentDuration : 5;
this.punishmentTime = punishmentTime> 0 ? punishmentTime : 1;
}
}

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="10" status="WARN">
<Properties>
<Property name="APP_NAME">dg_lab</Property>
<Property name="LOG_HOME">${env:log.dir:-logs}/${APP_NAME}</Property>
<Property name="ENCODER_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{80} - %msg%n</Property>
</Properties>
<Appenders>
<!-- 控制台输出 -->
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="${ENCODER_PATTERN}" />
</Console>
<!-- 普通日志输出 -->
<RollingFile name="FILE" fileName="${LOG_HOME}/output.log"
filePattern="${LOG_HOME}/output.%d{yyyy-MM-dd}.log">
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
<DefaultRolloverStrategy max="7" />
</RollingFile>
<!-- 错误日志输出WARN及以上 -->
<RollingFile name="ERROR_FILE" fileName="${LOG_HOME}/error.log"
filePattern="${LOG_HOME}/error.%d{yyyy-MM-dd}.log">
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Filters>
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
<DefaultRolloverStrategy max="7" />
</RollingFile>
<!-- sync日志 -->
<RollingFile name="SYNC_FILE" fileName="${LOG_HOME}/sync.log"
filePattern="${LOG_HOME}/sync.%d{yyyy-MM-dd}.log">
<PatternLayout pattern="${ENCODER_PATTERN}" />
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
<DefaultRolloverStrategy max="7" />
</RollingFile>
</Appenders>
<Loggers>
<!-- 指定 logger -->
<Logger name="log.sync" level="debug" additivity="true">
<AppenderRef ref="SYNC_FILE" />
</Logger>
<!-- 根日志记录器 -->
<Root level="debug">
<AppenderRef ref="STDOUT" />
<AppenderRef ref="FILE" />
<AppenderRef ref="ERROR_FILE" />
</Root>
</Loggers>
</Configuration>

86
CommonApi/build.gradle Normal file
View File

@ -0,0 +1,86 @@
plugins {
id 'java'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
group = api_project_group
version = project_version
base {
archivesName.set("${project_name}-${api_suffix}")
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
resources {
srcDirs = ['src/main/resources']
}
}
test {
java {
srcDirs = ['src/test/java']
}
resources {
srcDirs = ['src/test/resources']
}
}
}
dependencies {
implementation 'org.apache.logging.log4j:log4j-core:2.23.1'
implementation 'org.apache.logging.log4j:log4j-api:2.23.1'
implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.23.1'
implementation group: 'org.realityforge.org.jetbrains.annotations', name: 'org.jetbrains.annotations', version: '1.7.0'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.17.0'
implementation group: 'com.google.guava', name: 'guava', version: '33.3.0-jre'
implementation group: 'io.netty', name: 'netty-all', version: '4.1.109.Final'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.16'
}
tasks.register('sourcesJar', Jar) {
archiveClassifier.set('sources')
from sourceSets.main.allSource
}
tasks.register('javadocJar', Jar) {
dependsOn(tasks.named("javadoc"))
archiveClassifier.set('javadoc')
from(tasks.named("javadoc").get().destinationDir)
}
// jar
tasks.jar {
archiveVersion.set(project.version.toString())
archiveClassifier.set("")
}
tasks.named('sourcesJar', Jar) {
archiveClassifier.set('sources')
}
tasks.named('javadocJar', Jar) {
archiveVersion.set(project.version.toString())
archiveClassifier.set('javadoc')
}
javadoc {
options.encoding = 'UTF-8'
options.charSet = 'UTF-8'
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifact(tasks.named("sourcesJar"))
artifact(tasks.named("javadocJar"))
}
}
}

View File

@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.dataType;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType;
/**
* The type Power box commands.
*/
public final class PowerBoxCommands {
/**
* The constant STRENGTH.
*/
public static final String STRENGTH = "strength";
/**
* The constant PULSE.
*/
public static final String PULSE = "pulse";
/**
* The constant CLEAR.
*/
public static final String CLEAR = "clear";
/**
* The constant FEEDBACK.
*/
public static final String FEEDBACK = "feedback";
/**
* Gets command type.
*
* @param commandPrefix the command prefix
* @return the command type
*/
static String getCommandType(PowerBoxDataType commandPrefix) {
return switch (commandPrefix) {
case STRENGTH -> STRENGTH;
case PULSE -> PULSE;
case CLEAR -> CLEAR;
case FEEDBACK -> FEEDBACK;
default -> PowerBoxMsgType.UNKNOWN;
};
}
}

View File

@ -0,0 +1,82 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.dataType;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType;
/**
* The type Power box msg type.
*/
public final class PowerBoxMsgType {
/**
* The constant HEARTBEAT.
*/
public static final String HEARTBEAT = "heartbeat";
/**
* The constant BIND.
*/
public static final String BIND = "bind";
/**
* The constant BREAK.
*/
public static final String BREAK = "break";
/**
* The constant MSG_COMMAND.
*/
public static final String MSG_COMMAND = "msg";
/**
* The constant ERROR.
*/
public static final String ERROR = "error";
/**
* The constant CLIENT_MSG.
*/
public static final String CLIENT_MSG = "clientMsg";
/**
* The constant UNKNOWN.
*/
public static final String UNKNOWN = "unknown";
/**
* Gets msg type.
*
* @param type the type
* @return the msg type
*/
public static String getMsgType(PowerBoxDataType type) {
return getMsgType(type, false);
}
/**
* Gets msg type.
*
* @param type the type
* @param showMsgCommand the show msg command
* @return the msg type
*/
public static String getMsgType(PowerBoxDataType type, boolean showMsgCommand) {
return switch (type) {
case _NC_HEARTBEAT_ -> HEARTBEAT;
case _NC_BIND_ -> BIND;
case _NC_BREAK_ -> BREAK;
case _NC_ERROR_ -> ERROR;
case CLIENT_MESSAGE -> CLIENT_MSG;
case STRENGTH, CLEAR, PULSE, FEEDBACK -> showMsgCommand ? PowerBoxCommands.getCommandType(type) : MSG_COMMAND;
default -> UNKNOWN;
};
}
}

View File

@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.exception;
/**
* The type No match data type exception.
*/
public class NoMatchDataTypeException extends Exception {
/**
* Instantiates a new No match data type exception.
*
* @param message the message
*/
public NoMatchDataTypeException(String message) {
super(message);
}
/**
* Instantiates a new No match data type exception.
*/
public NoMatchDataTypeException() {
super();
}
}

View File

@ -0,0 +1,56 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.manager;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
/**
* DGLab管理接口
*/
public interface IDGLabManager {
/**
* Start.
*/
void start();
/**
* Stop.
*/
void stop();
/**
* Gets shared data.
*
* @return the shared data
*/
ISharedData getSharedData();
/**
* 获取当前单例实例运行
*
* @return 运行状态 status
*/
Status getStatus();
/**
* 设置当前单例实例运行
*
* @param status 运行状态
*/
void setStatus(Status status);
}

View File

@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.manager;
/**
* The enum Status.
*/
public enum Status {
/**
* 等待初始化
*/
WAITING_FOR_INIT,
/**
* Starting status.
*/
STARTING,
/**
* Running status.
*/
RUNNING,
/**
* Stopping status.
*/
STOPPING,
/**
* Stopped status.
*/
STOPPED,
/**
* Error status.
*/
ERROR
}

View File

@ -0,0 +1,271 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.message;
import com.r3944realms.dg_lab.api.dataType.PowerBoxCommands;
import com.r3944realms.dg_lab.api.dataType.PowerBoxMsgType;
import com.r3944realms.dg_lab.api.exception.NoMatchDataTypeException;
import com.r3944realms.dg_lab.api.message.argType.ChangePolicy;
import com.r3944realms.dg_lab.api.message.argType.Channel;
import com.r3944realms.dg_lab.api.message.data.PulseWaveList;
import com.r3944realms.dg_lab.api.message.data.PulseWaveListGenerator;
import com.r3944realms.dg_lab.api.websocket.message.MessageDirection;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxDataWithSingleAttachment;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType;
import com.r3944realms.dg_lab.api.websocket.message.role.Role;
import java.util.Arrays;
/**
* The interface Power box msg.
*/
public interface IPowerBoxMsg {
/**
* To power box data power box data.
*
* @param clientUUID the client uuid
* @param targetUUID the target uuid
* @return the power box data
*/
PowerBoxData toPowerBoxData(String clientUUID, String targetUUID);
/**
* To power box message power box message.
*
* @param clientUUID the client uuid
* @param targetUUID the target uuid
* @param sender the sender
* @param receiver the receiver
* @return the power box message
*/
default PowerBoxMessage toPowerBoxMessage(String clientUUID, String targetUUID, Role sender, Role receiver) {
return new PowerBoxMessage(toPowerBoxData(clientUUID, targetUUID), new MessageDirection<>(sender, receiver));
}
/**
* To power box message power box message.
*
* @param clientUUID the client uuid
* @param targetUUID the target uuid
* @param direction the direction
* @return the power box message
*/
default PowerBoxMessage toPowerBoxMessage(String clientUUID, String targetUUID, MessageDirection<?, ?> direction) {
return new PowerBoxMessage(toPowerBoxData(clientUUID, targetUUID), direction);
}
/**
* To power box message power box message.
*
* @param clientUUID the client uuid
* @param targetUUID the target uuid
* @param type the type
* @return the power box message
*/
default PowerBoxMessage toPowerBoxMessage(String clientUUID, String targetUUID, MessageDirection.DirectType type) {
return new PowerBoxMessage(toPowerBoxData(clientUUID, targetUUID), MessageDirection.of(type, clientUUID, targetUUID));
}
/**
* To power box message power box message.
*
* @param clientUUID the client uuid
* @param targetUUID the target uuid
* @param receiverName the receiver name
* @param type the type
* @return the power box message
*/
default PowerBoxMessage toPowerBoxMessage(String clientUUID, String targetUUID, String receiverName, MessageDirection.DirectType type) {
return new PowerBoxMessage(toPowerBoxData(clientUUID, targetUUID), MessageDirection.of(type, clientUUID, receiverName));
}
/**
* To power box message power box message.
*
* @param clientUUID the client uuid
* @param targetUUID the target uuid
* @param senderName the sender name
* @param receiverName the receiver name
* @param type the type
* @return the power box message
*/
default PowerBoxMessage toPowerBoxMessage(String clientUUID, String targetUUID,String senderName, String receiverName, MessageDirection.DirectType type) {
return new PowerBoxMessage(toPowerBoxData(clientUUID, targetUUID), MessageDirection.of(type, senderName, receiverName));
}
/**
* The type Strength.
*/
record StrengthChange(Channel channel, ChangePolicy policy, int value) implements IPowerBoxMsg {
@Override
public PowerBoxData toPowerBoxData(String clientUUID, String targetUUID) {
String msg = PowerBoxCommands.STRENGTH + "-" + channel.index_int + "+" + policy.index + "+" + value;
return new PowerBoxData(PowerBoxMsgType.MSG_COMMAND, clientUUID, targetUUID, msg);
}
/**
* Read strength change.
*
* @param msg the msg
* @return the strength change
* @throws NoMatchDataTypeException the no match data type exception
*/
public static StrengthChange read(PowerBoxMessage msg) throws NoMatchDataTypeException {
if (msg.commandType != PowerBoxDataType.STRENGTH) throw new NoMatchDataTypeException();
PowerBoxData payload = msg.getPayload();
Object[] argsArrayByPointing = payload.getArgsArrayByPointing(msg.commandType);
if (argsArrayByPointing.length != 3) throw new NoMatchDataTypeException();
return new StrengthChange(Channel.getChannel((Integer) argsArrayByPointing[0]), ChangePolicy.getChangePolicy((Integer) argsArrayByPointing[1]), (Integer) argsArrayByPointing[2]);
}
}
/**
* The type Strength info.
*/
record StrengthInfo(int aValue, int bValue, int aMax, int bMax) implements IPowerBoxMsg {
@Override
public PowerBoxData toPowerBoxData(String clientUUID, String targetUUID) {
String msg = PowerBoxCommands.STRENGTH + "-" + aValue + "+" + bValue + "+" + aMax + "+" + bMax;
return new PowerBoxData(PowerBoxMsgType.MSG_COMMAND, clientUUID, targetUUID, msg);
}
/**
* Read strength info.
*
* @param msg the msg
* @return the strength info
* @throws NoMatchDataTypeException the no match data type exception
*/
public static StrengthInfo read(PowerBoxMessage msg) throws NoMatchDataTypeException {
if (msg.commandType != PowerBoxDataType.STRENGTH) throw new NoMatchDataTypeException();
PowerBoxData payload = msg.getPayload();
Object[] argsArrayByPointing = payload.getArgsArrayByPointing(msg.commandType);
if (argsArrayByPointing.length != 4) throw new NoMatchDataTypeException();
return new StrengthInfo((Integer) argsArrayByPointing[0], (Integer) argsArrayByPointing[1], (Integer) argsArrayByPointing[2], (Integer) argsArrayByPointing[3]);
}
}
/**
* The type Clear.
*/
record Clear(Channel channel) implements IPowerBoxMsg {
@Override
public PowerBoxData toPowerBoxData(String clientUUID, String targetUUID) {
String msg = PowerBoxCommands.CLEAR + "-" + channel.index_int;
return new PowerBoxData(PowerBoxMsgType.MSG_COMMAND, clientUUID, targetUUID, msg);
}
/**
* Read clear.
*
* @param msg the msg
* @return the clear
* @throws NoMatchDataTypeException the no match data type exception
*/
public static Clear read(PowerBoxMessage msg) throws NoMatchDataTypeException {
if (msg.commandType != PowerBoxDataType.CLEAR) throw new NoMatchDataTypeException();
PowerBoxData payload = msg.getPayload();
Object[] argsArrayByPointing = payload.getArgsArrayByPointing(msg.commandType);
return new Clear(Channel.getChannel((Integer) argsArrayByPointing[0]));
}
}
/**
* The type Feedback.
*/
record Feedback(int feedback) implements IPowerBoxMsg {
@Override
public PowerBoxData toPowerBoxData(String clientUUID, String targetUUID) {
String msg = "feedback" + "-" + feedback;
return new PowerBoxData(PowerBoxMsgType.MSG_COMMAND, clientUUID, targetUUID, msg);
}
/**
* Read feedback.
*
* @param msg the msg
* @return the feedback
* @throws NoMatchDataTypeException the no match data type exception
*/
public static Feedback read(PowerBoxMessage msg) throws NoMatchDataTypeException {
if (msg.commandType != PowerBoxDataType.FEEDBACK) throw new NoMatchDataTypeException();
PowerBoxData payload = msg.getPayload();
Object[] argsArrayByPointing = payload.getArgsArrayByPointing(msg.commandType);
return new Feedback((Integer) argsArrayByPointing[0]);
}
}
/**
* The type Pulse.
*/
record Pulse(Channel channel, PulseWaveList pulseWaveList, Integer timer) implements IPowerBoxMsg {
/**
* Instantiates a new Pulse.
*
* @param channel the channel
* @param waveData the wave data
* @param timer the timer
*/
public Pulse(Channel channel, String[] waveData, Integer timer) {
this(channel, PulseWaveListGenerator.toPulseWaveListFromStringArray(waveData), timer);
}
/**
* Instantiates a new Pulse.
*
* @param channel the channel
* @param waveData the wave data
*/
public Pulse(Channel channel, String[] waveData) {
this(channel, waveData, 0);
}
/**
* Instantiates a new Pulse.
*
* @param channel the channel
* @param waveData the wave data
*/
public Pulse(Channel channel, PulseWaveList waveData) {
this(channel, waveData, 0);
}
@Override
public PowerBoxDataWithSingleAttachment toPowerBoxData(String clientUUID, String targetUUID) {
String msg = "pulse-" + channel.index_char + ":" + pulseWaveList.toListString();
return new PowerBoxData(PowerBoxMsgType.CLIENT_MSG, clientUUID, targetUUID, msg).withSingleAttachment(timer);
}
/**
* Read pulse.
*
* @param msg the msg
* @return the pulse
* @throws NoMatchDataTypeException the no match data type exception
*/
public static Pulse read(PowerBoxMessage msg) throws NoMatchDataTypeException {
PowerBoxData payload = msg.getPayload();
PowerBoxDataType commandType = payload.getCommandType(true);
if (commandType != PowerBoxDataType.PULSE) throw new NoMatchDataTypeException();
Object[] argsArrayByPointing = payload.getArgsArrayByPointing(commandType);//10 0 1 ~ 9
return new Pulse(Channel.getChannel(((String)argsArrayByPointing[0]).charAt(0)), Arrays.copyOfRange((String[])argsArrayByPointing, 1, argsArrayByPointing.length - 1));
}
}
}

View File

@ -0,0 +1,94 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.message.adapter;
import com.google.gson.*;
import com.r3944realms.dg_lab.api.message.data.PulseWave;
import java.lang.reflect.Type;
/**
* 处理 PulseWave JSON 格式
* - 格式1: "A1B2C3D4E5F6G7H8" (16进制字符串)
* - 格式2: {"frequencies":[20,30,40,50], "strengths":[80,90,70,60]}
*/
public class PulseWaveAdapter implements JsonSerializer<PulseWave>, JsonDeserializer<PulseWave> {
@Override
public PulseWave deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
if (json.isJsonPrimitive()) {
String hex = json.getAsString();
return parseHexString(hex);
} else if (json.isJsonObject()) {
return parseObject(json.getAsJsonObject());
} else
throw new JsonParseException("Invalid PulseWave format: expected String or Object");
} catch (Exception e) {
throw new JsonParseException("PulseWave validation failed: " + e.getMessage(), e);
}
}
@Override
public JsonElement serialize(PulseWave src, Type typeOfSrc, JsonSerializationContext context) {
// 默认序列化为16进制字符串 "0A1E1422805A4632"
return new JsonPrimitive(src.toHexString());
}
/**
* 解析16进制字符串格式16字符 "0A1E1422805A4632"
*/
private PulseWave parseHexString(String hex) {
hex = hex.toUpperCase().trim();
if (!hex.matches("^[0-9A-F]{16}$")) {
throw new JsonParseException("Invalid hex format: must be 16 uppercase hex characters");
}
return PulseWave.fromHex(hex);
}
/**
* 解析对象格式{"frequencies":[...], "strengths":[...]}
*/
private PulseWave parseObject(JsonObject obj) {
// 解析频率数组
int[] frequencies = parseJsonArray(obj.get("frequencies"), "frequencies");
// 解析强度数组
int[] strengths = parseJsonArray(obj.get("strengths"), "strengths");
return PulseWave.fromArrays(frequencies, strengths);
}
/**
* 解析JSON数组并校验长度和类型
*/
private int[] parseJsonArray(JsonElement element, String fieldName) {
if (element == null || !element.isJsonArray()) {
throw new JsonParseException("Missing required field: " + fieldName);
}
JsonArray array = element.getAsJsonArray();
if (array.size() != 4) {
throw new JsonParseException(fieldName + " must have exactly 4 elements");
}
int[] values = new int[4];
for (int i = 0; i < 4; i++) {
try {
values[i] = array.get(i).getAsInt();
} catch (NumberFormatException e) {
throw new JsonParseException("Invalid number in " + fieldName + " at index " + i, e);
}
}
return values;
}
}

View File

@ -0,0 +1,104 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.message.adapter;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.r3944realms.dg_lab.api.message.data.PulseWave;
import com.r3944realms.dg_lab.api.message.data.PulseWaveList;
import com.r3944realms.dg_lab.api.message.data.PulseWaveListGenerator;
import java.io.IOException;
import java.util.List;
/**
* The type Pulse wave list adapter.
*/
public class PulseWaveListAdapter extends TypeAdapter<PulseWaveList> {
@Override
public void write(JsonWriter out, PulseWaveList value) throws IOException {
out.beginObject();
out.name("name").value(value.getName());
out.name("list");
writeList(out, value.getList());
out.endObject();
}
@Override
public PulseWaveList read(JsonReader in) throws IOException {
PulseWaveList waveList = new PulseWaveList();
boolean hasName = false;
boolean hasList = false;
in.beginObject();
while (in.hasNext()) {
String field = in.nextName();
switch (field) {
case "name":
waveList.setName(in.nextString());
hasName = true;
break;
case "list":
readList(in, waveList);
hasList = true;
break;
default:
// 严格模式下可抛出异常
// throw new JsonParseException("未知字段: " + field);
in.skipValue(); // 宽松模式忽略未知字段
}
}
in.endObject();
if (!hasName) {
throw new JsonParseException("Missing required field: name");
}
if (!hasList) {
throw new JsonParseException("Missing required field: list");
}
return waveList;
}
private void writeList(JsonWriter out, List<PulseWave> list) throws IOException {
out.beginArray();
for (PulseWave wave : list) {
PulseWaveListGenerator.gson.toJson(wave, PulseWave.class, out);
}
out.endArray();
}
private void readList(JsonReader in, PulseWaveList waveList) throws IOException {
in.beginArray();
while (in.hasNext()) {
if (in.peek() == JsonToken.STRING) {
// Parse string to PulseWave (adjust based on toHexString() format)
waveList.add(parseWaveFromString(in.nextString()));
} else {
// Existing logic for object parsing
waveList.add(PulseWaveListGenerator.gson.getAdapter(PulseWave.class).read(in));
}
}
in.endArray();
}
private PulseWave parseWaveFromString(String waveString) {
// Implement parsing logic based on PulseWave's toHexString() format
// Example: If toHexString() returns JSON, use:
return PulseWave.fromString(waveString);
}
}

View File

@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.message.argType;
/**
* 修改策略
*/
public enum ChangePolicy {
/**
* 增加
*/
INCREASE(0),
/**
* 减少
*/
DECREASE(1),
/**
* 转变
*/
GOTO(2);
/**
* The Index.
*/
public final int index;
ChangePolicy(int index) {
this.index = index;
}
/**
* Gets change policy.
*
* @param index the index
* @return the change policy
*/
public static ChangePolicy getChangePolicy(int index) {
return switch (index) {
case 0 -> INCREASE;
case 1 -> DECREASE;
case 2 -> GOTO;
default -> throw new IllegalStateException("Unexpected value: " + index);
};
}
}

View File

@ -0,0 +1,64 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.message.argType;
/**
* 通道
*/
public enum Channel {
/**
* A频道
*/
A(1, 'A'),
/**
* B频道
*/
B(2, 'B');
/**
* 通道int值
*/
public final int index_int;
/**
* 通道char值
*/
public final char index_char;
Channel(int index_int, char index_char) {
this.index_int = index_int;
this.index_char = index_char;
}
/**
* Gets channel.
*
* @param index_int the index int
* @return the channel
*/
public static Channel getChannel(int index_int) {
return index_int == 1 ? A : B;
}
/**
* Gets channel.
*
* @param index_char the index char
* @return the channel
*/
public static Channel getChannel(char index_char) {
return index_char == 'A' ? A : B;
}
}

View File

@ -0,0 +1,161 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.message.data;
import java.security.InvalidParameterException;
/**
* 定义了100ms内部的4端波形数据<br/>
* <code>frequency</code> 范围在10~240里<br/>
* <code>strength</code> 范围在0~100里
*
* @param f1 第一个25ms的波频
* @param f2 第二个25ms的波频
* @param f3 第三个25ms的波频
* @param f4 第四个25ms的波频
* @param s1 第一个25ms的强度
* @param s2 第二个25ms的强度
* @param s3 第三个25ms的强度
* @param s4 第四个25ms的强度
*/
public record PulseWave(int f1, int f2, int f3, int f4, int s1, int s2, int s3, int s4) {
/**
* Instantiates a new Pulse wave.
*
* @param f1 the f 1
* @param f2 the f 2
* @param f3 the f 3
* @param f4 the f 4
* @param s1 the s 1
* @param s2 the s 2
* @param s3 the s 3
* @param s4 the s 4
*/
public PulseWave(int f1, int f2, int f3, int f4, int s1, int s2, int s3, int s4) {
this.f1 = validateFrequency(f1);
this.f2 = validateFrequency(f2);
this.f3 = validateFrequency(f3);
this.f4 = validateFrequency(f4);
this.s1 = validateStrength(s1);
this.s2 = validateStrength(s2);
this.s3 = validateStrength(s3);
this.s4 = validateStrength(s4);
}
/**
* Instantiates a new Pulse wave.
*
* @param frequencies the frequencies
* @param strengths the strengths
*/
PulseWave(int[] frequencies, int[] strengths) {
this(frequencies[0], frequencies[1], frequencies[2], frequencies[3], strengths[0], strengths[1], strengths[2], strengths[3]);
}
/**
* From default pulse wave.
*
* @param f1 the f 1
* @param f2 the f 2
* @param f3 the f 3
* @param f4 the f 4
* @param s1 the s 1
* @param s2 the s 2
* @param s3 the s 3
* @param s4 the s 4
* @return the pulse wave
*/
public static PulseWave fromDefault(int f1, int f2, int f3, int f4, int s1, int s2, int s3, int s4) {
return new PulseWave(f1, f2, f3, f4, s1, s2, s3, s4);
}
/**
* From arrays pulse wave.
*
* @param frequencies the frequencies
* @param strengths the strengths
* @return the pulse wave
*/
public static PulseWave fromArrays(int[] frequencies, int[] strengths) {
return new PulseWave(frequencies, strengths);
}
/**
* 根据字符串还原记录
*
* @param hex 哈希16进制字符串
* @return PulseWave pulse wave
*/
public static PulseWave fromHex(String hex) {
if (hex == null || !hex.toUpperCase().matches("^([0-9A-F]{2}){8}$")) {
throw new IllegalArgumentException("Invalid hex string");
}
// 解析前8字节为频率f1-f4
int[] frequencies = parseHexChunk(hex.substring(0, 8), "frequency");
int[] strengths = parseHexChunk(hex.substring(8, 16), "strength");
return PulseWave.fromArrays(frequencies, strengths);
}
@Override
public String toString() {
return PulseWaveListGenerator.gson.toJson(this); // Serialize to JSON string
}
/**
* From string pulse wave.
*
* @param str the str
* @return the pulse wave
*/
public static PulseWave fromString(String str) {
return PulseWaveListGenerator.gson.fromJson(str, PulseWave.class);
}
/**
* To hex string string.
*
* @return the string
*/
public String toHexString() {
return String.format("%02X%02X%02X%02X%02X%02X%02X%02X", f1, f2, f3, f4, s1, s2, s3, s4);
}
private static int[] parseHexChunk(String hex, String type) {
int[] values = new int[4];
for (int i = 0; i < 4; i++) {
String byteStr = hex.substring(i * 2, (i + 1) * 2);
int value = Integer.parseInt(byteStr, 16);
if ("frequency".equals(type)) validateFrequency(value);
else if ("strength".equals(type)) validateStrength(value);
values[i] = value;
}
return values;
}
private static int validateFrequency(int frequency) throws InvalidParameterException{
if (frequency < 10 || frequency > 240)
throw new IllegalArgumentException("Frequency must be between 10 and 240");
return frequency;
}
private static int validateStrength(int strength) throws InvalidParameterException{
if (strength < 0 || strength > 100)
throw new IllegalArgumentException("Strength must be between 0 and 100");
return strength;
}
}

View File

@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.message.data;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
/**
* 波形列表
*/
public class PulseWaveList {
private String name;
private final List<PulseWave> list;
/**
* Instantiates a new Pulse wave list.
*/
public PulseWaveList() {
this.name = "";
list = new ArrayList<>();
}
/**
* Sets name.
*
* @param name the name
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets name.
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Add.
*
* @param wave the wave
*/
public void add(PulseWave wave) {
list.add(wave);
}
/**
* Clear.
*/
public void clear() {
list.clear();
}
/**
* Gets list.
*
* @return the list
*/
public List<PulseWave> getList() {
return ImmutableList.copyOf(list);
}
/**
* To list string.
*
* @return the string
*/
public String toListString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i <= list.size() -1; i++) {
sb.append("\"").append(list.get(i).toHexString()).append("\"");
if(i != list.size() - 1) sb.append(",");
}
sb.append("]");
return sb.toString();
}
@Override
public String toString() {
return "PulseWaveList{name='" + name + "', list=" + list + "}";
}
}

View File

@ -0,0 +1,185 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.message.data;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.r3944realms.dg_lab.api.message.adapter.PulseWaveAdapter;
import com.r3944realms.dg_lab.api.message.adapter.PulseWaveListAdapter;
import java.util.stream.IntStream;
/**
* The type Pulse wave list generator.
*/
public class PulseWaveListGenerator {
/**
* The constant gson.
*/
public static final Gson gson = new GsonBuilder()
.registerTypeAdapter(PulseWaveList.class, new PulseWaveListAdapter())
.registerTypeAdapter(PulseWave.class, new PulseWaveAdapter())
.setPrettyPrinting()
.create();
private PulseWaveListGenerator() {}
/**
* To pulse wave list from string array pulse wave list.
*
* @param waveStringList the wave string list
* @return the pulse wave list
*/
public static PulseWaveList toPulseWaveListFromStringArray(String[] waveStringList) {
PulseWaveList ret = new PulseWaveList();
for (String s : waveStringList) {
int v1, v2, v3, v4, v5, v6, v7, v8;
int i = 0;
v1 = Integer.parseInt(s.substring(i, 2 + i++), 16);
v2 = Integer.parseInt(s.substring(++i, 2 + i++), 16);
v3 = Integer.parseInt(s.substring(++i, 2 + i++), 16);
v4 = Integer.parseInt(s.substring(++i, 2 + i++), 16);
v5 = Integer.parseInt(s.substring(++i, 2 + i++), 16);
v6 = Integer.parseInt(s.substring(++i, 2 + i++), 16);
v7 = Integer.parseInt(s.substring(++i, 2 + i++), 16);
v8 = Integer.parseInt(s.substring(++i, 2 + i), 16);
ret.add(new PulseWave(v1, v2, v3, v4, v5, v6, v7, v8));
}
return ret;
}
/**
* 输入频率和强度数组输出波形列表 <br/>
* 数组长度必须一致且为4的倍数
*
* @param frequencies 频率数组
* @param strengths 强度数组
* @return 波形列表 pulse wave list
* @throws IllegalArgumentException 不符合条件的输入
*/
public static PulseWaveList pulseWave(int[] frequencies, int[] strengths) throws IllegalArgumentException {
if (frequencies.length != strengths.length)
throw new IllegalArgumentException("frequencies and strengths must be the same length");
if (frequencies.length % 4 != 0)
throw new IllegalArgumentException("frequencies must be a multiple of 4");
PulseWaveList ret = new PulseWaveList();
for (int i = 0; i < frequencies.length; i += 4) {
ret.add(
new PulseWave(
frequencies[i], frequencies[i + 1], frequencies[i + 2], frequencies[i + 3],
strengths[i], strengths[i + 1], strengths[i + 2], strengths[i + 3]
)
);
}
return ret;
}
/**
* 输入频率和强度混合数组输出波形列表 <br/>
*
* @param args 频率和强度混合数组
* @return 波形列表 pulse wave list
* @throws IllegalArgumentException 不符合条件的输入
*/
public static PulseWaveList pulseWave(int[] args) throws IllegalArgumentException {
if (args.length % 2 != 0) {
throw new IllegalArgumentException("the number of arguments must be a multiple of 2");
}
int[] frequencies = new int[args.length / 2];
int[] strengths = new int[args.length / 2];
for (int i = 0; i < args.length; i++) {
if (i % 2 == 0)
frequencies[i] = args[i];
else
strengths[i] = args[i];
}
return pulseWave(frequencies, strengths);
}
/**
* 生成正弦波
*
* @param frequency 波的频率
* @param minStrength 波的最小强度
* @param maxStrength 波的最大强度
* @param duration 波的时长d (d 是8的倍数实际时长 = d * 25ms)
* @return 生成的正弦的波 pulse wave list
* @throws IllegalArgumentException 不符合条件的输入
*/
public static PulseWaveList sinPulse(
int frequency, int minStrength, int maxStrength, int duration) throws IllegalArgumentException {
if (duration <= 0)
throw new IllegalArgumentException("duration must be greater than 0");
int validDataNumber = duration - duration % 4;
int[] strengths = new int[validDataNumber];
// 振幅
double amplitude = maxStrength - minStrength;
// 角度增量, 只需要 0 - π 的范围
double angleStep = Math.PI / (duration - 1);
for (int i = 0; i < validDataNumber; i++) {
// 当前角度
double angle = i * angleStep;
// 计算正弦值并且平移到给定的最大最小值
double sinValue = Math.sin(angle) * amplitude + minStrength;
strengths[i] = ((int) Math.round(sinValue));
}
return pulseWave(IntStream.generate(() -> frequency).limit(validDataNumber).toArray(), strengths);
}
/**
* 生成梯度的波
*
* @param frequency 波的频率
* @param startStrength 起始强度
* @param endStrength 最终强度
* @param duration 波的时长d (d 是8的倍数实际时长 = d * 25ms)
* @return 生成的梯度的波 pulse wave list
* @throws IllegalArgumentException 不符合条件的输入
*/
public static PulseWaveList gradientPulse(
int frequency, int startStrength, int endStrength, int duration) throws IllegalArgumentException {
if (duration <= 0)
throw new IllegalArgumentException("duration must be greater than 0");
duration = duration - duration % 4;
int[] strengths = new int[duration];
double step = (endStrength - startStrength) / (duration - 1.0);
for (int i = 0; i < duration; i++) {
strengths[i] = ((int) Math.round(startStrength + step * i));
}
return pulseWave(IntStream.generate(() -> frequency).limit(duration).toArray(), strengths);
}
/**
* 生成平滑的波
*
* @param frequency 波的频率
* @param strength 波的强度
* @param duration 波的时长d (d 是8的倍数实际时长 = d * 25ms)
* @return 生成的平滑的波 pulse wave list
* @throws IllegalArgumentException the illegal argument exception
*/
public static PulseWaveList smoothPulse(int frequency, int strength, int duration) throws IllegalArgumentException {
if (duration <= 0) {
throw new IllegalArgumentException("duration must be greater than 0");
}
duration = duration - duration % 4;
return pulseWave(
IntStream.generate(() -> frequency).limit(duration).toArray(),
IntStream.generate(() -> strength).limit(duration).toArray()
);
}
}

View File

@ -0,0 +1,99 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.operation;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
/**
* The interface Client operation.
*/
public interface ClientOperation extends IOperation {
/**
* 客户端线程开启中处理
*/
void ClientStartingHandler();
/**
* 客户端线程完全开启后处理
*/
void ClientStartedHandler();
/**
* 客户端启动遇到错误后处理
*/
void ClientStartingErrorHandler();
/**
* 客户端线程关闭中处理
*/
void ClientStoppingHandler();
/**
* 客户端线程关闭中遇到错误后处理
*/
void ClientStoppingErrorHandler();
/**
* 客户端线程完全关闭后处理
*/
void ClientStoppedHandler();
/**
* 接收一个参数实现二维码生成
*
* @param qrCodeUrl 二维码URL
*/
void QrCodeUrlHandler(final String qrCodeUrl);
/**
* 将二维码展现出来
*/
void ShowQrCodeHandler();
/**
* 通过二维码连接成功后的通知
*/
void ConnectSuccessfulNoticeHandler();
/**
* 断开连接信息触发处理
*
* @param data 数据
*/
void DisconnectHandler(final PowerBoxData data);
/**
* 错误信息触发处理
*
* @param data 数据
*/
void ErrorHandler(final PowerBoxData data);
/**
* 心跳信息触发处理
*
* @param data 数据
*/
void HeartBeatHandler(final PowerBoxData data);
/**
* 其它信息触发处理
*
* @param data 数据
*/
void OtherMessageHandler(final PowerBoxData data);
}

View File

@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.operation;
/**
* The interface Operation.
*/
public interface IOperation {
}

View File

@ -0,0 +1,152 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.operation;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import org.jetbrains.annotations.Nullable;
/**
* The interface Server operation.
*/
public interface ServerOperation extends IOperation {
/**
* 服务器线程开启中通知
*/
void ServerStartingHandler();
/**
* 服务器线程开启中遇到错误后处理
*/
void ServerStartingErrorHandler();
/**
* 服务器线程开启后处理
*/
void ServerStartedHandler();
/**
* 服务器线程关闭中处理后通知
*/
void ServerStoppingHandler();
/**
* 服务器线程关闭中遇到错误后处理
*/
void ServerStoppingErrorHandler();
/**
* 服务器线程完全关闭后处理
*/
void ServerStoppedHandler();
/**
* 在定时器里即将被移除的UUID处理
*
* @param clientId 不活跃的UUID
*/
void InactiveConnectionRemoveHandler(final String clientId);
/**
* 读取信息错误走这里处理
*
* @param message 异常消息
*/
void ErrorMessageHandler(final PowerBoxMessage message);
/**
* 绑定成功处理
*
* @param message 绑定成功消息
*/
void BindSuccessMessageHandler(final PowerBoxMessage message);
/**
* 绑定失败处理
*
* @param message 绑定失败消息
*/
void BindFailureMessageHandler(final PowerBoxMessage message);
/**
* 心跳定时器触发处理
*
* @param message 心跳消息
*/
void HeartbeatMessageHandler(final PowerBoxMessage message);
/**
* 清理频道消息处理
*
* @param message 清理信息
*/
void ClearMessageHandler(final PowerBoxMessage message);
/**
* 反馈消息处理
*
* @param message 反馈消息
*/
void FeedbackMessageHandler(final PowerBoxMessage message);
/**
* 强度通知消息处理
*
* @param message 强度通知消息
*/
void StrengthMessageNoticeMessage(final PowerBoxMessage message);
/**
* 强度改变消息处理
*
* @param message 强度改变消息
*/
void StrengthMessageChangeHandler(final PowerBoxMessage message);
/**
* 波形信息由客户端下发处理
*
* @param clearMessage 清理信息
* @param pulseMessage 波形消息
*/
default void PulseClientMessageHandler(final PowerBoxMessage clearMessage, final PowerBoxMessage pulseMessage) {
PulseClientMessageHandler(clearMessage, 500, pulseMessage);
}
/**
* 波形信息由客户端下发处理
*
* @param clearMessage 清理信息
* @param delayTime 延迟时间
* @param pulseMessage 波形消息
*/
void PulseClientMessageHandler(@Nullable final PowerBoxMessage clearMessage, final int delayTime, final PowerBoxMessage pulseMessage);
/**
* 连接断开消息处理
*
* @param message 断开消息
*/
void BreakConnectMessageHandler(final PowerBoxMessage message);
/**
* 其它类型合法消息转发处理
*
* @param message 其它合法消息
*/
void OtherMessageHandler(final PowerBoxMessage message);
}

View File

@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.handler;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
/**
* 携带共享数据的接口
*/
public interface IAttachSharedData {
/**
* Gets shared data.
*
* @return the shared data
*/
ISharedData getSharedData();
}

View File

@ -0,0 +1,164 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
import com.r3944realms.dg_lab.api.websocket.message.data.IData;
import com.r3944realms.dg_lab.api.websocket.message.data.adapter.IDataTypeAdapterFactory;
import com.r3944realms.dg_lab.api.websocket.message.role.Role;
import com.r3944realms.dg_lab.api.websocket.message.role.RoleDeserializer;
import java.io.Serial;
import java.io.Serializable;
/**
* 消息带有方向和有效负载消息
*/
public abstract class Message implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* The constant gson.
*/
@Expose(deserialize = false, serialize = false)
final static Gson gson;
/**
* The Direction.
*/
final public MessageDirection<?,?> direction;
/**
* The Payload.
*/
final IData payload;
static {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Role.class, new RoleDeserializer());
gsonBuilder.registerTypeAdapter(MessageDirection.class, new MessageDirectionDeserializer());
gsonBuilder.registerTypeAdapterFactory(new IDataTypeAdapterFactory());
gson = gsonBuilder.create();
}
/**
* 额外的信息,
* <ul>
* <li>消息发送者UUID</li>
* <li>消息创建时间</li>
* <li>信息校对值</li>
* <li>...</li>
* </ul>
*
* @return 信息 string
*/
public abstract String AdditionalInformation();
/**
* Instantiates a new Message.
*
* @param payload the payload
* @param direction the direction
*/
Message(IData payload, MessageDirection<?, ?> direction) {
this.payload = payload;
this.direction = direction;
}
/**
* Gets data json.
*
* @return the data json
*/
public String getDataJson() {
return getDataJson(false);
}
/**
* 无效信息返回
*
* @return Json invalid message json
*/
public abstract String getInvalidMessageJson();
/**
* Gets data json.
*
* @param isFix the is fix
* @return the data json
*/
public String getDataJson(boolean isFix) {
if(payload == null) return getInvalidMessageJson();
return payload.isValid() ?
(isFix ? gson.toJson(payload).replace("\\","") : gson.toJson(payload))
: getInvalidMessageJson();
}
/**
* Gets msg json.
*
* @return the msg json
*/
public String getMsgJson() {
return getMsgJson(false);
}
/**
* Gets msg json.
*
* @param isFix the is fix
* @return the msg json
*/
public String getMsgJson(boolean isFix) {
if(payload == null && direction == null) return getInvalidMessageJson();
return isFix ? gson.toJson(this).replace("\\","") : gson.toJson(this);
}
/**
* Gets payload.
*
* @return the payload
*/
public IData getPayload() {
return this.payload;
}
/**
* Read json return message message.
*
* @param dataJson the data json
* @param messageDirection the message direction
* @return the message
*/
public abstract Message readJsonReturnMessage(String dataJson, MessageDirection<?, ?> messageDirection);
/**
* Gets payload.
*
* @param json the json
* @return the payload
*/
public abstract IData getPayload(String json);
/**
* Gets message.
*
* @param json the json
* @return the message
*/
public abstract Message getMessage(String json);
}

View File

@ -0,0 +1,136 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message;
import com.r3944realms.dg_lab.api.websocket.message.role.*;
import java.io.Serializable;
/**
* {@link Message}的参数之一
*
* @param <T> 接收者类型
* @param <K> 发送者类型
*/
public record MessageDirection<T extends Role, K extends Role>(T sender, K receiver) implements Serializable {
@Override
public String toString() {
return
"MessageDirection:[ " + sender.type + " -> " + receiver.type + " ] {" + sender.name + " -> " + receiver.name + "}";
}
/**
* Of message direction.
*
* @param type the type
* @param SenderUUID the sender uuid
* @param ReceiverUUID the receiver uuid
* @return the message direction
*/
public static MessageDirection<? extends Role, ? extends Role> of(final DirectType type, final String SenderUUID, final String ReceiverUUID) {
return switch (type) {
case PLACEHOLDER_TO_PLACEHOLDER -> new MessageDirection<>(new PlaceholderRole(SenderUUID), new PlaceholderRole(ReceiverUUID));
case PLACEHOLDER_TO_CLIENT -> new MessageDirection<>(new PlaceholderRole(SenderUUID), new WebSocketClientRole(ReceiverUUID));
case PLACEHOLDER_TO_SERVER -> new MessageDirection<>(new PlaceholderRole(SenderUUID), new WebSocketServerRole(ReceiverUUID));
case PLACEHOLDER_TO_APPLICATION -> new MessageDirection<>(new PlaceholderRole(SenderUUID), new WebSocketApplicationRole(ReceiverUUID));
case CLIENT_TO_PLACEHOLDER -> new MessageDirection<>(new WebSocketClientRole(SenderUUID), new PlaceholderRole(ReceiverUUID));
case CLIENT_TO_CLIENT -> new MessageDirection<>(new WebSocketClientRole(SenderUUID), new WebSocketClientRole(ReceiverUUID));
case CLIENT_TO_SERVER -> new MessageDirection<>(new WebSocketClientRole(SenderUUID), new WebSocketServerRole(ReceiverUUID));
case CLIENT_TO_APPLICATION -> new MessageDirection<>(new WebSocketClientRole(SenderUUID), new WebSocketApplicationRole(ReceiverUUID));
case SERVER_TO_PLACEHOLDER -> new MessageDirection<>(new WebSocketServerRole(SenderUUID), new PlaceholderRole(ReceiverUUID));
case SERVER_TO_CLIENT -> new MessageDirection<>(new WebSocketServerRole(SenderUUID), new WebSocketClientRole(ReceiverUUID));
case SERVER_TO_APPLICATION -> new MessageDirection<>(new WebSocketServerRole(SenderUUID), new WebSocketApplicationRole(ReceiverUUID));
case SERVER_TO_SERVER -> new MessageDirection<>(new WebSocketServerRole(SenderUUID), new WebSocketServerRole(ReceiverUUID));
case APPLICATION_TO_PLACEHOLDER -> new MessageDirection<>(new WebSocketApplicationRole(SenderUUID), new PlaceholderRole(ReceiverUUID));
case APPLICATION_TO_CLIENT -> new MessageDirection<>(new WebSocketApplicationRole(SenderUUID), new WebSocketClientRole(ReceiverUUID));
case APPLICATION_TO_APPLICATION -> new MessageDirection<>(new WebSocketApplicationRole(SenderUUID), new WebSocketApplicationRole(ReceiverUUID));
case APPLICATION_TO_SERVER -> new MessageDirection<>(new WebSocketApplicationRole(SenderUUID), new WebSocketServerRole(ReceiverUUID));
};
}
/**
* The enum Direct type.
*/
public enum DirectType {
/**
* Placeholder to placeholder direct type.
*/
PLACEHOLDER_TO_PLACEHOLDER,
/**
* Placeholder to client direct type.
*/
PLACEHOLDER_TO_CLIENT,
/**
* Placeholder to server direct type.
*/
PLACEHOLDER_TO_SERVER,
/**
* Placeholder to application direct type.
*/
PLACEHOLDER_TO_APPLICATION,
/**
* Client to placeholder direct type.
*/
CLIENT_TO_PLACEHOLDER,
/**
* Client to client direct type.
*/
CLIENT_TO_CLIENT,
/**
* Client to server direct type.
*/
CLIENT_TO_SERVER,
/**
* Client to application direct type.
*/
CLIENT_TO_APPLICATION,
/**
* Server to placeholder direct type.
*/
SERVER_TO_PLACEHOLDER,
/**
* Server to client direct type.
*/
SERVER_TO_CLIENT,
/**
* Server to application direct type.
*/
SERVER_TO_APPLICATION,
/**
* Server to server direct type.
*/
SERVER_TO_SERVER,
/**
* Application to placeholder direct type.
*/
APPLICATION_TO_PLACEHOLDER,
/**
* Application to client direct type.
*/
APPLICATION_TO_CLIENT,
/**
* Application to application direct type.
*/
APPLICATION_TO_APPLICATION,
/**
* Application to server direct type.
*/
APPLICATION_TO_SERVER;
}
}

View File

@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message;
import com.google.gson.*;
import com.r3944realms.dg_lab.api.websocket.message.role.Role;
import java.lang.reflect.Type;
/**
* The type Message direction deserializer.
*/
public class MessageDirectionDeserializer implements JsonDeserializer<MessageDirection<?, ?>> {
@Override
public MessageDirection<?, ?> deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject obj = jsonElement.getAsJsonObject();
JsonElement senderJson = obj.get("sender");
JsonElement receiverJson = obj.get("receiver");
Role sender = jsonDeserializationContext.deserialize(senderJson, Role.class);
Role receiver = jsonDeserializationContext.deserialize(receiverJson, Role.class);
return new MessageDirection<>(sender, receiver);
}
}

View File

@ -0,0 +1,229 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message;
import com.google.gson.JsonSyntaxException;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxDataWithAttachment;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxDataWithSingleAttachment;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxStatusCode;
import com.r3944realms.dg_lab.api.websocket.message.role.PlaceholderRole;
import com.r3944realms.dg_lab.api.websocket.message.role.Role;
/**
* The type Power box message.
*/
public class PowerBoxMessage extends Message {
/**
* The Command type.
*/
public final PowerBoxDataType commandType;
private static final PowerBoxMessage Null = PowerBoxMessage.createPowerBoxMessage("null","null","null","null", new PlaceholderRole("null"), new PlaceholderRole("null"));
/**
* The Invalid message json.
*/
static final String INVALID_MESSAGE_JSON = gson.toJson(PowerBoxData.createPowerBoxData("error","","",""));
/**
* Instantiates a new Power box message.
*
* @param payload the payload
* @param direction the direction
*/
public PowerBoxMessage(PowerBoxData payload, MessageDirection<?, ?> direction) {
super(payload ,direction);
commandType = payload.getCommandType();
}
/**
* Create power box message power box message.
*
* @param type the type
* @param clientId the client id
* @param targetId the target id
* @param message the message
* @param timer the timer
* @param sender the sender
* @param receiver the receiver
* @return the power box message
*/
public static PowerBoxMessage createPowerBoxMessage(
String type, String clientId, String targetId, String message, Integer timer,
Role sender, Role receiver
) {
PowerBoxDataWithSingleAttachment data = new PowerBoxDataWithSingleAttachment(PowerBoxData.createPowerBoxData(type, clientId, targetId, message), timer);
MessageDirection<?,?> direction = new MessageDirection<>(
sender,
receiver
);
return new PowerBoxMessage(data, direction);
}
/**
* Create power box message power box message.
*
* @param type the type
* @param clientId the client id
* @param targetId the target id
* @param message the message
* @param timerA the timer a
* @param timerB the timer b
* @param sender the sender
* @param receiver the receiver
* @return the power box message
*/
@SuppressWarnings("deprecation")
public static PowerBoxMessage createPowerBoxMessage(
String type, String clientId, String targetId, String message, Integer timerA, Integer timerB,
Role sender, Role receiver
) {
PowerBoxDataWithAttachment data = new PowerBoxDataWithAttachment(PowerBoxData.createPowerBoxData(type, clientId, targetId, message), timerA, timerB);
MessageDirection<?,?> direction = new MessageDirection<>(
sender,
receiver
);
return new PowerBoxMessage(data, direction);
}
/**
* Create power box message power box message.
*
* @param payload the payload
* @param direction the direction
* @return the power box message
*/
public static PowerBoxMessage createPowerBoxMessage(PowerBoxData payload, MessageDirection<?, ?> direction ) {
return new PowerBoxMessage(payload, direction);
}
/**
* Create power box message power box message.
*
* @param type the type
* @param clientId the client id
* @param targetId the target id
* @param message the message
* @param sender the sender
* @param receiver the receiver
* @return the power box message
*/
public static PowerBoxMessage createPowerBoxMessage(
String type, String clientId, String targetId, String message,
Role sender, Role receiver
) {
PowerBoxData data = PowerBoxData.createPowerBoxData(type, clientId, targetId, message);
MessageDirection<?,?> direction = new MessageDirection<>(
sender,
receiver
);
return new PowerBoxMessage(data, direction);
}
/**
* Create power box message power box message.
*
* @param type the type
* @param clientId the client id
* @param targetId the target id
* @param statusCode the status code
* @param sender the sender
* @param receiver the receiver
* @return the power box message
*/
public static PowerBoxMessage createPowerBoxMessage(
String type, String clientId, String targetId, PowerBoxStatusCode statusCode,
Role sender, Role receiver
) {
return createPowerBoxMessage(type, clientId, targetId, statusCode.getCode(), sender, receiver);
}
/**
* 仅供转化用不可使用与传递
*
* @return Null_Message null message
*/
public static PowerBoxMessage getNullMessage() {
return Null;
}
@Override
public String AdditionalInformation() {
return "IPowerBoxMessage : " + direction.toString();
}
@Override
public String getInvalidMessageJson() {
return INVALID_MESSAGE_JSON;
}
@Override
public PowerBoxMessage readJsonReturnMessage(String dataJson, MessageDirection<?, ?> messageDirection) throws JsonSyntaxException {
return new PowerBoxMessage(getPayload(dataJson), messageDirection);
}
@Override
public PowerBoxData getPayload() {
return (PowerBoxData)payload;
}
/**
*
* @param json PowerBoxDataJSON
* @return PowerBoxData 如果Data字段存在空则会返回null值
*/
@Override
public PowerBoxData getPayload(String json) throws JsonSyntaxException {
PowerBoxData powerBoxData = gson.fromJson(json, PowerBoxData.class);
return (powerBoxData.Type() != null && powerBoxData.getClientId() != null && powerBoxData.getTargetId() != null && powerBoxData.getMessage() != null) ? powerBoxData : null;
}
/**
*
* @param json PowerBoxMessageJSON
* @return PowerBoxMessage 如果message字段存在空则会返回null值
*/
@Override
public PowerBoxMessage getMessage(String json) {
PowerBoxMessage message = gson.fromJson(json, PowerBoxMessage.class);
return (message.direction != null && message.payload != null && message.commandType != null) ? message : null;
}
/**
* Gets payload with attachment.
*
* @param json the json
* @return the payload with attachment
* @throws JsonSyntaxException the json syntax exception
*/
@SuppressWarnings("deprecation")
public PowerBoxDataWithAttachment getPayloadWithAttachment(String json) throws JsonSyntaxException {
return gson.fromJson(json, PowerBoxDataWithAttachment.class);
}
/**
* Gets payload with single attachment.
*
* @param json the json
* @return the payload with single attachment
* @throws JsonSyntaxException the json syntax exception
*/
public PowerBoxDataWithSingleAttachment getPayloadWithSingleAttachment(String json) throws JsonSyntaxException {
return gson.fromJson(json, PowerBoxDataWithSingleAttachment.class);
}
}

View File

@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data;
public enum DataType {
/**
* Power box att data type.
*/
POWER_BOX_ATT,
/**
* Default data type.
*/
DEFAULT,
/**
* Power box data type.
*/
POWER_BOX;
/**
* Gets type from string.
*
* @param type the type
* @return the type from string
*/
public static DataType getTypeFromString(String type) {
try {
return valueOf(type.toUpperCase());
} catch (IllegalArgumentException e) {
return DEFAULT;
}
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data;
public interface IData {
/**
* 获取 无效原因
*
* @return inValidReason 无效原因
*/
default String getInvalidReason() {
return "Invalid arguments [Default Reason]";
}
/**
* 当前数据是否有效
*
* @return isValid 有效与否
*/
boolean isValid();
/**
* 获取当前数据类型
*
* @return DateType 数据类型
*/
DataType Type();
}

View File

@ -0,0 +1,379 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxDataType;
import com.r3944realms.dg_lab.api.websocket.message.data.type.PowerBoxStatusCode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* PowerBox 负载消息数据
*/
@SuppressWarnings("FieldCanBeLocal")
public class PowerBoxData implements IData {
private final String type;
private final String clientId;
private final String targetId;
private final String message;
AtomicReference<String> inValidReason = new AtomicReference<>(getInvalidReason());
/**
* Instantiates a new Power box data.
*
* @param type the type
* @param clientId the client id
* @param targetId the target id
* @param message the message
*/
public PowerBoxData(String type, String clientId, String targetId, String message) {
this.type = type;
this.clientId = clientId;
this.targetId = targetId;
this.message = message;
}
/**
* Create power box data power box data.
*
* @param type the type
* @param clientId the client id
* @param targetId the target id
* @param message the message
* @return the power box data
*/
public static PowerBoxData createPowerBoxData(String type, String clientId, String targetId, String message) {
return new PowerBoxData(type, clientId, targetId, message);
}
/**
* Gets type.
*
* @return the type
*/
public String getType() {
return type;
}
/**
* Gets client id.
*
* @return the client id
*/
public String getClientId() {
return clientId;
}
/**
* Gets target id.
*
* @return the target id
*/
public String getTargetId() {
return targetId;
}
/**
* Gets message.
*
* @return the message
*/
public String getMessage() {
return message;
}
/**
* With single attachment power box data with single attachment.
*
* @param timer the timer
* @return the power box data with single attachment
*/
public PowerBoxDataWithSingleAttachment withSingleAttachment(Integer timer) {
return new PowerBoxDataWithSingleAttachment(this, timer);
}
/**
* With attachment power box data with attachment.
*
* @param timerA the timer a
* @param timerB the timer b
* @return the power box data with attachment
*/
@SuppressWarnings("deprecation")
public PowerBoxDataWithAttachment withAttachment(Integer timerA, Integer timerB) {
return new PowerBoxDataWithAttachment(this, timerA, timerB);
}
@Override
public boolean isValid() {
if(type == null || type.isEmpty() || clientId == null || targetId == null || message == null) {
inValidReason.set("Invalid PowerBox Data");
return false;
}
final boolean commonValidCheck = !clientId.isEmpty() && !targetId.isEmpty() && !message.isEmpty();
return switch (type) {
case "heartbeat" -> !clientId.isEmpty() && PowerBoxStatusCode.isValidStatusCode(message);
case "bind" -> Objects.equals(message, "targetId") ? (targetId.isEmpty() && !clientId.isEmpty()) : commonValidCheck;
case "msg" -> !clientId.isEmpty() && !targetId.isEmpty() && isCommandValid(message);
case "break","clientMsg" -> commonValidCheck;
case "error" -> !message.isEmpty();
default -> false;
};
}
@Override
public DataType Type() {
return DataType.POWER_BOX;
}
/**
* Gets command type.
*
* @return the command type
*/
public PowerBoxDataType getCommandType() {
return PowerBoxDataType.getType(type, message);
}
/**
* Gets command type.
*
* @param mayPulse the may pulse
* @return the command type
*/
public PowerBoxDataType getCommandType(boolean mayPulse) {
return PowerBoxDataType.getType(type, message, mayPulse);
}
/**
* Get args array by pointing object [ ].
*
* @param dataType the data type
* @return the object [ ]
*/
@SuppressWarnings("DuplicatedCode")
public Object[] getArgsArrayByPointing(PowerBoxDataType dataType) {
if(message == null || message.isEmpty()) {
return null;
}
String[] args = message.split("-");
switch(dataType) {
case STRENGTH: {
String[] arguments = args[1].split("\\+");
int argumentsLength = arguments.length;
switch (argumentsLength) {
case 3:{
int channel = Integer.parseInt(arguments[0]);
int strengthChangePolicy = Integer.parseInt(arguments[1]);
int value = Integer.parseInt(arguments[2]);
return new Integer[]{channel, strengthChangePolicy, value};
}
case 4:{
int AStrength = Integer.parseInt(arguments[0]);
int BStrength = Integer.parseInt(arguments[1]);
int ALimit = Integer.parseInt(arguments[2]);
int BLimit = Integer.parseInt(arguments[3]);
return new Integer[]{AStrength, BStrength, ALimit, BLimit};
}
}
}
case PULSE: {
String channel = args[1].substring(0,1);
String[] DataList = getWaveformDataList(args[1]);
String[] dataList = new String[DataList.length + 1];
int i = 0;
dataList[i] = channel;
for(String str : DataList) {
i++;
dataList[i] = str;
}
return dataList;
}
case CLEAR: {
return new Integer[]{ Integer.parseInt(args[1]) };
}
case FEEDBACK:{
int arg = Integer.parseInt(args[1]);
return new Integer[]{ arg };
}
default: return null;
}
}
/**
* Get args array object [ ].
*
* @return the object [ ]
*/
@SuppressWarnings("DuplicatedCode")
public Object[] getArgsArray() {
if(message == null || message.isEmpty()) {
return null;
}
String[] args = message.split("-");
switch(args[0]) {
/* 强度 */
case "strength": {
String[] arguments = args[1].split("\\+");
int argumentsLength = arguments.length;
switch (argumentsLength) {
/* 这个是客户端->指令中转服务器->App->PowerBox主机 */
case 3:{
int channel = Integer.parseInt(arguments[0]);
int strengthChangePolicy = Integer.parseInt(arguments[1]);
int value = Integer.parseInt(arguments[2]);
/*=== 通道{1->A, 2->B} + 策略模式{0-减小, 1-增加 ,2-指定} + 数值 ===*/
return new Integer[]{channel, strengthChangePolicy, value};
}
/* 这个是PowerBox主机->App->指令中转服务器->客户端 */
case 4:{
int AStrength = Integer.parseInt(arguments[0]);
int BStrength = Integer.parseInt(arguments[1]);
int ALimit = Integer.parseInt(arguments[2]);
int BLimit = Integer.parseInt(arguments[3]);
/*=== A通道目前强度 + B通道目前强度 + A通道强度上限 + B通道强度上限 ===*/
return new Integer[]{AStrength, BStrength, ALimit, BLimit};
}
}
}
/* 波形 */
case "pulse": {
String channel = args[1].substring(0,1);
String[] DataList = getWaveformDataList(args[1]);
String[] dataList = new String[DataList.length + 1];
int i = 0;
/* 频道 {A->A, B->B} */
dataList[i] = channel;
for(String str : DataList) {
i++;
/* 一段波形数据如 1122334455667788 */
/* 解释为
* 第0~25ms频率,第25~50ms频率, 第50~75ms频率, 第75~100ms频率: 0x11, 0x22, 0x33 0x44
* 第0~25ms强度,第25~50ms强度, 第50~75ms强度, 第75~100ms强度: 0x55, 0x66, 0x77 0x88
* */
dataList[i] = str;
}
return dataList;
}
/* 清空波形 */
case "clear": {
/* 通道{1->A, 2->B} */
return new Integer[]{ Integer.parseInt(args[1]) };
}
/* 反馈 */
case "feedback":{
/* 拟定不同形状图标代表的感受状态 */
return new Integer[]{ Integer.parseInt(args[1]) };
}
default: return null;
}
}
/**
* Is command valid boolean.
*
* @param command the command
* @return the boolean
*/
public boolean isCommandValid(String command) {
if(command == null || command.isEmpty()) {
return false;
}
String[] args = command.split("-");
try {
switch(args[0]) {
case "strength": {
String[] arguments = args[1].split("\\+");
int argumentsLength = arguments.length;
switch (argumentsLength) {
case 3:{
int channel = Integer.parseInt(arguments[0]);
if(channel != 1 && channel != 2) throw new IllegalArgumentException("Channel must be 1 or 2");
int strengthChangePolicy = Integer.parseInt(arguments[1]);
if(2 < strengthChangePolicy || strengthChangePolicy < 0) throw new IllegalArgumentException("Strength change policy must in the range of [0,2]");
int value = Integer.parseInt(arguments[2]);
if (value < 0 || value > 200) throw new IllegalArgumentException("Value must be between 0 and 200");
return true;
}
case 4:{
return true;//App发来的数据应该不会有问题如果有也不是我的锅
}
default: throw new IllegalArgumentException("Invalid number of arguments");
}
}
case "pulse": {
String channel = args[1].substring(0,1);
if(!(channel.equals("A") || channel.equals("B"))) throw new IllegalArgumentException("Channel is incorrect or lacked.");
String[] DataList = getWaveformDataList(args[1]);
Pattern pattern = Pattern.compile("^[a-zA-Z0-9]{16}$");//检查是否为16进制数字大小写都可以
if (DataList.length > 100) throw new IllegalArgumentException("The list of Waveform data is too long.");
for(String str : DataList) {
if(str.length() != 16) {
throw new IllegalArgumentException("Find list has a the invalid length of waveform data.");
}
Matcher matcher = pattern.matcher(str);
if (!matcher.matches()) {
throw new NumberFormatException("Find list has a incorrect syntax of waveform data.");
}
}
return true;
}
case "clear": {
String arg = args[1];
if(args.length != 2) throw new IllegalArgumentException("Invalid number of arguments");
if(!Objects.equals(arg, "1") && !Objects.equals(arg, "2")) throw new IllegalArgumentException("The argument must be 1 or 2");
return true;
}
case "feedback":{
int arg = Integer.parseInt(args[1]);
if(args.length != 2) throw new IllegalArgumentException("Invalid number of arguments");
if(0 > arg || arg > 10) throw new IllegalArgumentException("args must be between 0 and 10");
return true;
}
default: throw new IllegalArgumentException("Invalid command");
}
} catch (Exception e) {
inValidReason.set(e.getMessage());
return false;//指令不正确直接否
}
}
/**
* Get waveform data list string [ ].
*
* @param msg the msg
* @return the string [ ]
*/
String[] getWaveformDataList(String msg) {
String dataList = msg.substring(msg.indexOf('[') + 1, msg.indexOf(']'));
String[] rawStringList = dataList.split(",");
ArrayList<String> list = new ArrayList<>();
Arrays.stream(rawStringList).forEach(rawString -> {
list.add(rawString.replaceAll("\"", ""));
});
String[] result = new String[list.size()];
list.toArray(result);
return result;
}
}

View File

@ -0,0 +1,89 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data;
import org.jetbrains.annotations.Nullable;
/**
* PowerBox双附加的额外数据
*
* @deprecated 建议使用单附加的
*/
@Deprecated
public class PowerBoxDataWithAttachment extends PowerBoxData {
@Nullable
private final Integer timer_A;
@Nullable
private final Integer timer_B;
/**
* Instantiates a new Power box data with attachment.
*
* @param parent the parent
* @param timer_A the timer a
* @param timer_B the timer b
*/
public PowerBoxDataWithAttachment(PowerBoxData parent,@Nullable Integer timer_A,@Nullable Integer timer_B) {
super(parent.getType(), parent.getClientId(), parent.getTargetId(), parent.getMessage());
this.timer_A = timer_A;
this.timer_B = timer_B;
}
/**
* Attach power box data with attachment.
*
* @param parent the parent
* @param timer_A the timer a
* @param timer_B the timer b
* @return the power box data with attachment
*/
public static PowerBoxDataWithAttachment attach(PowerBoxData parent, Integer timer_A, Integer timer_B) {
if(parent == null)
throw new NullPointerException("parent is null");
return new PowerBoxDataWithAttachment(parent, timer_A, timer_B);
}
/**
* Gets timer a.
*
* @return the timer a
*/
@Nullable
public Integer getTimerA() {
return timer_A;
}
/**
* Gets timer b.
*
* @return the timer b
*/
@Nullable
public Integer getTimerB() {
return timer_B;
}
@Override
public boolean isValid() {
return super.isValid();
}
@Override
public DataType Type() {
return DataType.POWER_BOX_ATT;
}
}

View File

@ -0,0 +1,69 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data;
import org.jetbrains.annotations.Nullable;
/**
* PowerBox单附加的额外数据
*/
public class PowerBoxDataWithSingleAttachment extends PowerBoxData {
@Nullable
private final Integer timer;
/**
* Instantiates a new Power box data with single attachment.
*
* @param parent the parent
* @param timer the timer
*/
public PowerBoxDataWithSingleAttachment(PowerBoxData parent,@Nullable Integer timer) {
super(parent.getType(), parent.getClientId(), parent.getTargetId(), parent.getMessage());
this.timer = timer;
}
/**
* Gets timer.
*
* @return the timer
*/
@Nullable
public Integer getTimer() {
return timer;
}
/**
* Attach power box data with single attachment.
*
* @param parent the parent
* @param timer the timer
* @return the power box data with single attachment
*/
public static PowerBoxDataWithSingleAttachment attach(PowerBoxData parent, Integer timer){
if(parent == null)
throw new NullPointerException("parent is null");
return new PowerBoxDataWithSingleAttachment(parent, timer);
}
@Override
public boolean isValid() {
return super.isValid();
}
@Override
public DataType Type() {
return DataType.DEFAULT;
}
}

View File

@ -0,0 +1,84 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data.adapter;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.r3944realms.dg_lab.api.websocket.message.data.IData;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxDataWithAttachment;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxDataWithSingleAttachment;
import java.io.IOException;
/**
* The type Data type adapter factory.
*/
@SuppressWarnings("deprecation")
public class IDataTypeAdapterFactory implements TypeAdapterFactory {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if(!IData.class.isAssignableFrom(typeToken.getRawType())) {
return null;
}
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
final TypeAdapter<PowerBoxData> powerBoxDataAdapter = gson.getDelegateAdapter(this, TypeToken.get(PowerBoxData.class));
final TypeAdapter<PowerBoxDataWithAttachment> powerBoxDataWithAttachmentAdapter = gson.getDelegateAdapter(this, TypeToken.get(PowerBoxDataWithAttachment.class));
final TypeAdapter<PowerBoxDataWithSingleAttachment> powerBoxDataWithSingleAttachmentTypeAdapter = gson.getDelegateAdapter(this, TypeToken.get(PowerBoxDataWithSingleAttachment.class));
return (TypeAdapter<T>) new TypeAdapter<IData>() {
@Override
public void write(JsonWriter jsonWriter, IData iData) throws IOException {
JsonElement jsonElement = switch (iData.getClass().getSimpleName()) {
case "PowerBoxDataWithAttachment" ->
powerBoxDataWithAttachmentAdapter.toJsonTree((PowerBoxDataWithAttachment) iData);
case "PowerBoxDataWithSingleAttachment" ->
powerBoxDataWithSingleAttachmentTypeAdapter.toJsonTree((PowerBoxDataWithSingleAttachment) iData);
case "PowerBoxData" -> powerBoxDataAdapter.toJsonTree((PowerBoxData) iData);
case "null" ->
throw new NullPointerException("IDataTypeAdapterFactory#create(Gson gson, TypeToken iData): null");
default -> throw new JsonSyntaxException("Unsupported data type: " + iData.getClass().getName());
};
elementAdapter.write(jsonWriter, jsonElement);
}
@Override
public IData read(JsonReader jsonReader) throws IOException {
JsonElement element = elementAdapter.read(jsonReader);
JsonObject jsonObject = element.getAsJsonObject();
JsonElement type = jsonObject.get("type");
String Eigenvalues_A = type.getAsString();
switch (Eigenvalues_A) {
case "heartbeat", "error", "msg", "break", "bind" -> {
return powerBoxDataAdapter.fromJsonTree(element);
}
case "clientMsg" -> {
return powerBoxDataWithSingleAttachmentTypeAdapter.fromJsonTree(element);
}
default -> throw new JsonParseException("Unknown type");
}
}
};
}
}

View File

@ -0,0 +1,75 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data.adapter;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import java.io.IOException;
/**
* The type Power box data adapter.
*/
public class PowerBoxDataAdapter extends TypeAdapter<PowerBoxData> {
@Override
public void write(JsonWriter out, PowerBoxData value) throws IOException {
out.beginObject();
out.name("type").value(value.getType());
out.name("clientId").value(value.getClientId());
out.name("targetId").value(value.getTargetId());
out.name("message").value(value.getMessage());
out.endObject();
}
@Override
public PowerBoxData read(JsonReader in) throws IOException {
String type = "";
String clientId = "";
String targetId = "";
String message = "";
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "type":
type = in.nextString();
break;
case "clientId":
clientId = in.nextString();
break;
case "targetId":
targetId = in.nextString();
break;
case "message":
message = in.nextString();
break;
}
}
in.endObject();
if ("POWER_BOX".equals(type)) {
return new PowerBoxData(type, clientId, targetId, message);
}
// Handle other types
throw new JsonParseException("Unknown type: " + type);
}
}

View File

@ -0,0 +1,73 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data.adapter;
import com.google.gson.JsonParseException;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxDataWithAttachment;
import java.io.IOException;
/**
* The type Power box data wa adapter.
*/
@Deprecated
public class PowerBoxDataWAAdapter extends PowerBoxDataAdapter {
@Override
public void write(JsonWriter out, PowerBoxData value) throws IOException {
out.beginObject();
PowerBoxDataWithAttachment newValue = (PowerBoxDataWithAttachment) value;
out.name("type").value(newValue.getType());
out.name("clientId").value(newValue.getClientId());
out.name("targetId").value(newValue.getTargetId());
out.name("message").value(newValue.getMessage());
out.name("timer_A").value(newValue.getTimerA());
out.name("timer_B").value(newValue.getTimerB());
out.endObject();
}
@Override
public PowerBoxData read(JsonReader in) throws IOException {
String type = "";
String clientId = "";
String targetId = "";
String message = "";
Integer timer_A = null;
Integer timer_B = null;
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "type" -> type = in.nextString();
case "clientId" -> clientId = in.nextString();
case "targetId" -> targetId = in.nextString();
case "message" -> message = in.nextString();
case "timerA" -> timer_A = in.nextInt();
case "timerB" -> timer_B = in.nextInt();
}
}
in.endObject();
if ("POWER_BOX".equals(type)) {
return new PowerBoxDataWithAttachment(new PowerBoxData(type, clientId, targetId, message), timer_A, timer_B);
}
// Handle other types
throw new JsonParseException("Unknown type: " + type);
}
}

View File

@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data.adapter;
import com.google.gson.JsonParseException;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxData;
import com.r3944realms.dg_lab.api.websocket.message.data.PowerBoxDataWithSingleAttachment;
import java.io.IOException;
/**
* The type Power box data wsa adapter.
*/
public class PowerBoxDataWSAAdapter extends PowerBoxDataAdapter {
@Override
public void write(JsonWriter out, PowerBoxData value) throws IOException {
out.beginObject();
PowerBoxDataWithSingleAttachment newValue = (PowerBoxDataWithSingleAttachment) value;
out.name("type").value(newValue.getType());
out.name("clientId").value(newValue.getClientId());
out.name("targetId").value(newValue.getTargetId());
out.name("message").value(newValue.getMessage());
out.name("timer").value(newValue.getTimer());
out.endObject();
}
@Override
public PowerBoxData read(JsonReader in) throws IOException {
String type = "";
String clientId = "";
String targetId = "";
String message = "";
Integer timer = null;
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "type" -> type = in.nextString();
case "clientId" -> clientId = in.nextString();
case "targetId" -> targetId = in.nextString();
case "message" -> message = in.nextString();
case "timer" -> timer = in.nextInt();
}
}
in.endObject();
if ("POWER_BOX".equals(type)) {
return new PowerBoxDataWithSingleAttachment(new PowerBoxData(type, clientId, targetId, message), timer);
}
// Handle other types
throw new JsonParseException("Unknown type: " + type);
}
}

View File

@ -0,0 +1,124 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data.type;
/**
* PowerBox数据类型
*/
public enum PowerBoxDataType {
/**
* 心跳类型(无参数)
*/
_NC_HEARTBEAT_,
/**
* 绑定类型(无参数)
*/
_NC_BIND_,
/**
* 断开类型(无参数)
*/
_NC_BREAK_,
/**
* 错误类型(无参数)
*/
_NC_ERROR_,
/**
* 强度类型 (参数数量3~4)
*/
STRENGTH(3, 4),
/**
* 波形类型 (参数数量1~101)
*/
PULSE(1, 101),
/**
* 清空类型 (参数数量1)
*/
CLEAR(1),
/**
* 反馈类型 (参数数量1)
*/
FEEDBACK(1),
/**
* 客户端消息类型 (无参数)
*/
CLIENT_MESSAGE,
/**
* 未知类型 (无参数)
*/
UNKNOWN;
/**
* The Nop.
*/
public final int NOP;
/**
* The Max nop.
*/
public final int MaxNOP;
PowerBoxDataType() {
this(0, 0);
}
PowerBoxDataType(int NumberOfParameters) {
this(NumberOfParameters,-1);
}
PowerBoxDataType(int minNumberOfParameters, int maxNumberOfParameters) {
this.NOP = minNumberOfParameters;
this.MaxNOP = maxNumberOfParameters;
}
private static PowerBoxDataType getCommandType(String commandPrefix) {
return switch (commandPrefix) {
case "strength" -> STRENGTH;
case "pulse" -> PULSE;
case "clear" -> CLEAR;
case "feedback" -> FEEDBACK;
default -> UNKNOWN;
};
}
/**
* Gets type.
*
* @param type the type
* @param msg the msg
* @return the type
*/
public static PowerBoxDataType getType(String type, String msg) {
return getType(type, msg, false);
}
/**
* Gets type.
*
* @param type the type
* @param msg the msg
* @param mayPulse the may pulse
* @return the type
*/
public static PowerBoxDataType getType(String type, String msg, boolean mayPulse) {
return switch (type) {
case "heartbeat" -> _NC_HEARTBEAT_;
case "bind" -> _NC_BIND_;
case "msg" -> getCommandType(msg.split("-")[0]);
case "break" -> _NC_BREAK_;
case "error" -> _NC_ERROR_;
case "clientMsg" -> {
PowerBoxDataType commandType = getCommandType(msg.split("-")[0]);
yield mayPulse && commandType == PULSE ? PULSE : CLIENT_MESSAGE;
}
default -> UNKNOWN;
};
}
}

View File

@ -0,0 +1,135 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.data.type;
/**
* 状态码
*/
public enum PowerBoxStatusCode {
/**
* 200 - 成功
*/
SUCCESSFUL("200"),
/**
* 209 - 对方客户端已断开
*/
OPPOSITE_CLIENT_DISCONNECTED("209"),
/**
* 210 - 二维码中没有有效的clientID
*/
QR_CODE_HAS_A_INVALID_CLIENT_ID("210"),
/**
* 211 - socket连接上了但服务器迟迟不下发app端的id来绑定
*/
WAITING_FOR_SERVER_BINDING_MESSAGE_TOO_LONG("211"),
/**
* 400 - 此id已被其他客户端绑定关系
*/
TRYING_BINDING_ALREADY_BOUND_ID("400"),
/**
* 401 - 要绑定的目标客户端不存在
*/
TARGET_CLIENT_NOT_EXIST("401"),
/**
* 402 - 收信方和寄信方不是绑定关系
*/
NOT_BINDING_RELATIONSHIP("402"),
/**
* 403 - 发送的内容不是标准json对象
*/
NOT_STANDARD_JSON("403"),
/**
* 404 - 未找到收信人离线
*/
NOT_FOUND_BECAUSE_OF_OFFLINE("404"),
/**
* 405 - 下发的message长度大于1950
*/
MESSAGE_TOO_LONG("405"),
/**
* 406 - 未指定通道
*/
NO_CHOOSE_CHANNEL("406"),
/**
* 500 - 服务器内部异常
*/
INTERNAL_ERROR("500"),
/**
* 501 - 客户端发送了无效信息Message为无效内容给服务器
*/
INVALID_REQUEST("501"),
/**
* 502 - 不支持的操作
*/
UNSUPPORTED_OPERATION("502"),
/**
* -1 - 无效状态码
*/
INVALID_STATUS_CODE("-1");
/**
* The Code.
*/
final String code;
PowerBoxStatusCode(String code) {
this.code = code;
}
/**
* Gets status code.
*
* @param code the code
* @return the status code
*/
public static PowerBoxStatusCode getStatusCode(String code) {
return switch(code) {
case "200" -> PowerBoxStatusCode.SUCCESSFUL;
case "209" -> PowerBoxStatusCode.OPPOSITE_CLIENT_DISCONNECTED;
case "210" -> PowerBoxStatusCode.QR_CODE_HAS_A_INVALID_CLIENT_ID;
case "211" -> PowerBoxStatusCode.WAITING_FOR_SERVER_BINDING_MESSAGE_TOO_LONG;
case "400" -> PowerBoxStatusCode.TRYING_BINDING_ALREADY_BOUND_ID;
case "401" -> PowerBoxStatusCode.TARGET_CLIENT_NOT_EXIST;
case "402" -> PowerBoxStatusCode.NOT_BINDING_RELATIONSHIP;
case "403" -> PowerBoxStatusCode.NOT_STANDARD_JSON;
case "404" -> PowerBoxStatusCode.NOT_FOUND_BECAUSE_OF_OFFLINE;
case "405" -> PowerBoxStatusCode.MESSAGE_TOO_LONG;
case "406" -> PowerBoxStatusCode.NO_CHOOSE_CHANNEL;
case "500" -> PowerBoxStatusCode.INTERNAL_ERROR;
case "501" -> PowerBoxStatusCode.INVALID_REQUEST;
case "502" -> PowerBoxStatusCode.UNSUPPORTED_OPERATION;
default -> PowerBoxStatusCode.INVALID_STATUS_CODE;
};
}
/**
* Is valid status code boolean.
*
* @param code the code
* @return the boolean
*/
public static boolean isValidStatusCode(String code) {
return getStatusCode(code) != INVALID_STATUS_CODE;
}
/**
* Gets code.
*
* @return the code
*/
public String getCode() {
return code;
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.role;
import com.r3944realms.dg_lab.api.websocket.message.role.type.RoleType;
/**
* WS占位角色
*/
public final class PlaceholderRole extends Role {
/**
* Instantiates a new Placeholder role.
*
* @param name the name
*/
public PlaceholderRole(String name) {
super(name, RoleType.PLACEHOLDER);
}
/**
* Of placeholder role.
*
* @param name the name
* @return the placeholder role
*/
public static PlaceholderRole of(String name) {
return new PlaceholderRole("Pl" + name);
}
}

View File

@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.role;
import com.r3944realms.dg_lab.api.websocket.message.MessageDirection;
import com.r3944realms.dg_lab.api.websocket.message.role.type.RoleType;
import java.io.Serializable;
/**
* 角色{@link MessageDirection messageDirection}组成部分
*/
public sealed abstract class Role implements Serializable
permits PlaceholderRole, WebSocketApplicationRole, WebSocketClientRole, WebSocketServerRole {
/**
* The Name.
*/
public String name;
/**
* The Type.
*/
public final RoleType type;
/**
* Instantiates a new Role.
*
* @param name the name
* @param type the type
*/
Role(String name, final RoleType type) {
this.name = name;
this.type = type;
}
/**
* 更新角色名字主要在每次心跳时更新
*
* @param name 角色
*/
public void UpdateName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.role;
import com.google.gson.*;
import com.r3944realms.dg_lab.api.websocket.message.role.type.RoleType;
import java.lang.reflect.Type;
/**
* Role Json反序列化适配器
*/
public class RoleDeserializer implements JsonDeserializer<Role> {
@Override
public Role deserialize(JsonElement json, Type typeOFT, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String name = jsonObject.get("name").getAsString();
RoleType type = RoleType.getTypeFromString(jsonObject.get("type").getAsString());
if (type != null) {
return switch (type){
case T_CLIENT -> new WebSocketClientRole(name);
case T_SERVER -> new WebSocketServerRole(name);
case APPLICATION -> new WebSocketApplicationRole(name);
case PLACEHOLDER -> new PlaceholderRole(name);
};
}
return null;
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.role;
import com.r3944realms.dg_lab.api.websocket.message.role.type.RoleType;
/**
* WS应用角色
*/
public final class WebSocketApplicationRole extends Role{
/**
* Instantiates a new Web socket application role.
*
* @param name the name
*/
public WebSocketApplicationRole(String name) {
super(name, RoleType.APPLICATION);
}
/**
* Of web socket application role.
*
* @param uuid the uuid
* @return the web socket application role
*/
public static WebSocketApplicationRole of(String uuid) {
return new WebSocketApplicationRole("Ap" + uuid);
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.role;
import com.r3944realms.dg_lab.api.websocket.message.role.type.RoleType;
/**
* WS客户端角色
*/
public final class WebSocketClientRole extends Role {
/**
* Instantiates a new Web socket client role.
*
* @param name the name
*/
public WebSocketClientRole(String name) {
super(name, RoleType.T_CLIENT);
}
/**
* Of web socket client role.
*
* @param uuid the uuid
* @return the web socket client role
*/
public static WebSocketClientRole of(String uuid) {
return new WebSocketClientRole("Cl" + uuid);
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.role;
import com.r3944realms.dg_lab.api.websocket.message.role.type.RoleType;
/**
* WS服务器角色
*/
public final class WebSocketServerRole extends Role {
/**
* Instantiates a new Web socket server role.
*
* @param name the name
*/
public WebSocketServerRole(String name) {
super(name, RoleType.T_SERVER);
}
/**
* Of web socket server role.
*
* @param uuid the uuid
* @return the web socket server role
*/
public static WebSocketServerRole of(String uuid) {
return new WebSocketServerRole("Sr" + uuid);
}
}

View File

@ -0,0 +1,55 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.message.role.type;
/**
* 角色枚举类型
*/
public enum RoleType {
/**
* Tradition Server 传统意义上的服务器
*/
T_SERVER,
/**
* Tradition Client 传统意义上的客户端
*/
T_CLIENT,
/**
* App 应用
*/
APPLICATION,
/**
* 占位符 即任意端
*/
PLACEHOLDER;
/**
* Gets type from string.
*
* @param string the string
* @return the type from string
*/
public static RoleType getTypeFromString(String string) {
return switch (string) {
case "T_SERVER" -> T_SERVER;
case "T_CLIENT" -> T_CLIENT;
case "APPLICATION" -> APPLICATION;
case "PLACEHOLDER" -> PLACEHOLDER;
default -> null;
};
}
}

View File

@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2024-2025 R3944Realms. All rights reserved.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.r3944realms.dg_lab.api.websocket.sharedData;
/**
* C/S s Shared Data that exist in handler
*/
public interface ISharedData extends Cloneable{
ISharedData clone();
}

201
LICENSE.txt Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1
README.md Normal file
View File

@ -0,0 +1 @@
# DG_LAB 郊狼开发库

35
build.gradle Normal file
View File

@ -0,0 +1,35 @@
plugins {
id("idea")
id("eclipse")
id("maven-publish")
}
group = "top.r3944realms.superleadrope"
version = "1.0-SNAPSHOT"
allprojects {
repositories {
mavenCentral()
}
}
subprojects {
apply {
plugin('java')
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
}
tasks.test {
useJUnitPlatform()
}
}

18
gradle.properties Normal file
View File

@ -0,0 +1,18 @@
# Gradle settings
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn
# ROOT
project_name=DgLab
project_version=4.2.8.16
project_group=top.r3944realms.dg_lab
# API
api_project_group=top.r3944realms.dg_lab.api
api_suffix=api
# COMMON
common_suffix=common

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Sun Sep 22 17:25:46 CST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.12-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

16
node_modules/.bin/commitizen generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../commitizen/bin/commitizen" "$@"
else
exec node "$basedir/../commitizen/bin/commitizen" "$@"
fi

17
node_modules/.bin/commitizen.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\commitizen\bin\commitizen" %*

28
node_modules/.bin/commitizen.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../commitizen/bin/commitizen" $args
} else {
& "$basedir/node$exe" "$basedir/../commitizen/bin/commitizen" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../commitizen/bin/commitizen" $args
} else {
& "node$exe" "$basedir/../commitizen/bin/commitizen" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/cz generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../commitizen/bin/git-cz" "$@"
else
exec node "$basedir/../commitizen/bin/git-cz" "$@"
fi

17
node_modules/.bin/cz.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\commitizen\bin\git-cz" %*

28
node_modules/.bin/cz.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../commitizen/bin/git-cz" $args
} else {
& "$basedir/node$exe" "$basedir/../commitizen/bin/git-cz" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../commitizen/bin/git-cz" $args
} else {
& "node$exe" "$basedir/../commitizen/bin/git-cz" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/git-cz generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../commitizen/bin/git-cz" "$@"
else
exec node "$basedir/../commitizen/bin/git-cz" "$@"
fi

17
node_modules/.bin/git-cz.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\commitizen\bin\git-cz" %*

28
node_modules/.bin/git-cz.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../commitizen/bin/git-cz" $args
} else {
& "$basedir/node$exe" "$basedir/../commitizen/bin/git-cz" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../commitizen/bin/git-cz" $args
} else {
& "node$exe" "$basedir/../commitizen/bin/git-cz" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/jiti generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../jiti/lib/jiti-cli.mjs" "$@"
else
exec node "$basedir/../jiti/lib/jiti-cli.mjs" "$@"
fi

17
node_modules/.bin/jiti.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jiti\lib\jiti-cli.mjs" %*

28
node_modules/.bin/jiti.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../jiti/lib/jiti-cli.mjs" $args
} else {
& "$basedir/node$exe" "$basedir/../jiti/lib/jiti-cli.mjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../jiti/lib/jiti-cli.mjs" $args
} else {
& "node$exe" "$basedir/../jiti/lib/jiti-cli.mjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/js-yaml generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../js-yaml/bin/js-yaml.js" "$@"
else
exec node "$basedir/../js-yaml/bin/js-yaml.js" "$@"
fi

17
node_modules/.bin/js-yaml.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\js-yaml\bin\js-yaml.js" %*

28
node_modules/.bin/js-yaml.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
} else {
& "$basedir/node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
} else {
& "node$exe" "$basedir/../js-yaml/bin/js-yaml.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/tsc generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
else
exec node "$basedir/../typescript/bin/tsc" "$@"
fi

17
node_modules/.bin/tsc.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %*

28
node_modules/.bin/tsc.ps1 generated vendored Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
} else {
& "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../typescript/bin/tsc" $args
} else {
& "node$exe" "$basedir/../typescript/bin/tsc" $args
}
$ret=$LASTEXITCODE
}
exit $ret

16
node_modules/.bin/tsserver generated vendored Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
else
exec node "$basedir/../typescript/bin/tsserver" "$@"
fi

17
node_modules/.bin/tsserver.cmd generated vendored Normal file
View File

@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsserver" %*

Some files were not shown because too many files have changed in this diff Show More