diff --git a/README.md b/README.md index cce44098d..c7eaf1712 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ ioGame 是轻量级的网络编程框架,**不依赖任何第三方**中间件 com.iohao.game run-one-netty - 21.5 + 21.6 ``` diff --git a/common/common-core/pom.xml b/common/common-core/pom.xml index 0acafdd61..a5e042f59 100644 --- a/common/common-core/pom.xml +++ b/common/common-core/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/IoGameVersion.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/IoGameVersion.java index 23fd05e41..ee8aba6b4 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/IoGameVersion.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/IoGameVersion.java @@ -29,7 +29,7 @@ public final class IoGameVersion { public static String a; static { - String internalVersion = "21.5"; + String internalVersion = "21.6"; VERSION = internalVersion .replace("", "") diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/InternalAboutFlowContext.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/InternalAboutFlowContext.java index 6609cc23b..133362d56 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/InternalAboutFlowContext.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/InternalAboutFlowContext.java @@ -32,8 +32,8 @@ import com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage; import com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalMessage; import com.iohao.game.common.kit.concurrent.executor.ExecutorRegion; -import com.iohao.game.common.kit.trace.TraceKit; import com.iohao.game.common.kit.concurrent.executor.ThreadExecutor; +import com.iohao.game.common.kit.trace.TraceKit; import lombok.experimental.UtilityClass; import org.slf4j.MDC; @@ -827,6 +827,7 @@ private RequestCollectExternalMessage createRequestCollectExternalMessage(int bi return new RequestCollectExternalMessage() // 根据业务码,调用游戏对外服与业务码对应的业务实现类 .setBizCode(bizCode) + .setUserId(headMetadata.getUserId()) // 业务数据 .setData(data) // 强制指定需要访问的游戏对外服;当指定 id 后,将不会访问所有的游戏对外服 diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/RangeBroadcast.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/RangeBroadcast.java new file mode 100644 index 000000000..d9029f2e9 --- /dev/null +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/kit/RangeBroadcast.java @@ -0,0 +1,221 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.action.skeleton.kit; + +import com.iohao.game.action.skeleton.core.BarMessageKit; +import com.iohao.game.action.skeleton.core.CmdInfo; +import com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext; +import com.iohao.game.action.skeleton.core.flow.FlowContext; +import com.iohao.game.action.skeleton.core.flow.attr.FlowAttr; +import com.iohao.game.action.skeleton.protocol.ResponseMessage; +import com.iohao.game.common.kit.CollKit; +import lombok.AccessLevel; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import org.jctools.maps.NonBlockingHashSet; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; + +/** + * 范围内的广播,这个范围指的是,指定某些用户进行广播。 + *
+ *     在执行广播前,开发者可以自定义业务逻辑,如
+ *     - 添加一些需要广播的用户
+ *     - 删除一些不需要接收广播的用户
+ *     - 可通过重写 logic、trick 方法来做一些额外扩展
+ * 
+ * for example + *
{@code
+ *         // example - 1
+ *         new RangeBroadcast(flowContext)
+ *                 // 需要广播的数据
+ *                 .setResponseMessage(responseMessage)
+ *                 // 添加需要接收广播的用户
+ *                 .addUserId(1)
+ *                 .addUserId(2)
+ *                 .addUserId(List.of(3L, 4L, 5L))
+ *                 // 排除一些用户,被排除的用户将不会接收到广播
+ *                 .removeUserId(1)
+ *                 // 执行广播
+ *                 .execute();
+ *
+ *         // example - 2
+ *         new RangeBroadcast(flowContext)
+ *                 // 需要广播的数据
+ *                 .setResponseMessage(cmdInfo, playerReady)
+ *                 // // 添加需要接收广播的用户
+ *                 .addUserId(1)
+ *                 // 执行广播
+ *                 .execute();
+ * }
+ * 
+ * + * @author 渔民小镇 + * @date 2024-04-23 + */ +@Accessors(chain = true) +@FieldDefaults(level = AccessLevel.PRIVATE) +public class RangeBroadcast { + final CommunicationAggregationContext aggregationContext; + /** 需要推送的 userId 列表 */ + final Set userIds = new NonBlockingHashSet<>(); + /** 响应数据 */ + ResponseMessage responseMessage; + /** 是否执行发送领域事件操作: true 执行推送操作 */ + boolean doSend = true; + + public RangeBroadcast(CommunicationAggregationContext aggregationContext) { + Objects.requireNonNull(aggregationContext); + this.aggregationContext = aggregationContext; + } + + public RangeBroadcast(FlowContext flowContext) { + this(flowContext.option(FlowAttr.aggregationContext)); + } + + /** + * 响应消息到远程, 此方法是同步推送 + *
+     *     模板方法模式:
+     *         在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。
+     *         模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
+     *
+     *         要点:
+     *         - “模板方法”定义了算法的步骤,把这些步骤的实现延迟到子类。
+     *         - 模板方法模式为我们提供了一种代码复用的重要技巧。
+     *         - 模板方法的抽象类可以定义具体方法、抽象方法和钩子。
+     *         - 抽象方法由子类实现。
+     *         - 钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。
+     *         - 为了防止子类改变模板方法中的算法,可以将模板方法声明为final。
+     *         - 好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用低层模块。
+     *         - 你将在真实世界代码中看到模板方法模式的许多变体,不要期待它们全都是一眼就可以被你一眼认出的。
+     *         - 策略模式和模板方法模式都封装算法,一个用组合,一个用继承。
+     *         - 工厂方法是模板方法的一种特殊版本。
+     * 
+ */ + public final void execute() { + // 子类可根据需要来构建响应内容 + this.logic(); + + // 钩子方法,see disableSend() + if (!this.doSend) { + return; + } + + // 在将数据推送前调用的钩子方法 + this.trick(); + + Objects.requireNonNull(this.responseMessage); + + if (CollKit.isEmpty(this.userIds)) { + throw new RuntimeException("没有添加消息推送人 " + this.getClass()); + } + + // 推送响应 (广播消息)给指定的用户列表 + this.aggregationContext.broadcast(this.responseMessage, this.userIds); + } + + /** + * 在将数据推送到调用方之前,触发的方法 + *
+     *     可以做一些逻辑,在逻辑中可以决定是否执行推送
+     *     {@code
+     *         // 不执行推送数据的操作
+     *         this.disableSend()
+     *     }
+     * 
+ */ + protected void logic() { + } + + /** + * 小把戏 (钩子方法). 子类可以做些其他的事情 + *
+     *     在将数据推送到调用方之前,触发的方法
+     * 
+ */ + protected void trick() { + } + + public RangeBroadcast setResponseMessage(ResponseMessage responseMessage) { + this.responseMessage = responseMessage; + return this; + } + + public RangeBroadcast setResponseMessage(CmdInfo cmdInfo, Object bizData) { + var responseMessage = BarMessageKit.createResponseMessage(cmdInfo, bizData); + return this.setResponseMessage(responseMessage); + } + + /** + * 接收广播的用户 + * + * @param userIds userIds + * @return me + */ + public RangeBroadcast addUserId(Collection userIds) { + this.userIds.addAll(userIds); + return this; + } + + /** + * 接收广播的用户 + * + * @param userId userId + * @return me + */ + public RangeBroadcast addUserId(long userId) { + this.userIds.add(userId); + return this; + } + + /** + * 添加接收广播的用户,顺带排除一个不需要接收广播的用户 + * + * @param userIds 接收广播的 userIds + * @param excludeUserId 需要排除的 userId + * @return me + */ + public RangeBroadcast addUserId(Collection userIds, long excludeUserId) { + return this.addUserId(userIds).removeUserId(excludeUserId); + } + + /** + * 排除 userId + * + * @param excludeUserId 需要排除的 userId + * @return me + */ + public RangeBroadcast removeUserId(long excludeUserId) { + if (excludeUserId > 0) { + this.userIds.remove(excludeUserId); + } + + return this; + } + + /** + * 不执行推送数据的操作 + */ + protected void disableSend() { + this.doSend = false; + } +} diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/RequestCollectExternalMessage.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/RequestCollectExternalMessage.java index e5381dbb5..8c537f21a 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/RequestCollectExternalMessage.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/RequestCollectExternalMessage.java @@ -59,6 +59,7 @@ public final class RequestCollectExternalMessage implements Serializable { * */ int bizCode; + long userId; /** 请求业务数据 */ Serializable data; diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/ResponseCollectExternalItemMessage.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/ResponseCollectExternalItemMessage.java index 217e58d05..fc0b2c0c9 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/ResponseCollectExternalItemMessage.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/external/ResponseCollectExternalItemMessage.java @@ -63,4 +63,9 @@ public void setError(MsgException msgException) { this.responseStatus = msgException.getMsgCode(); this.errorMsg = msgException.getMessage(); } + + @SuppressWarnings("unchecked") + public T getData() { + return (T) data; + } } diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/BeeAction.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/BeeAction.java index 8cb94389c..e9bda5d1a 100644 --- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/BeeAction.java +++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/BeeAction.java @@ -64,16 +64,16 @@ public void thatVoid(BeeApple beeApple) { @ActionMethod(ExampleActionCmd.BeeActionCmd.jsr380) public void jsr380(DogValid dogValid) { - log.info("dogValid : {}", dogValid); + log.info("{}", dogValid); } @ActionMethod(ExampleActionCmd.BeeActionCmd.validated_group_update) public void validateUpdate(@ValidatedGroup(value = Update.class) BirdValid birdValid) { - log.info("dogValid : {}", birdValid); + log.info("{}", birdValid); } @ActionMethod(ExampleActionCmd.BeeActionCmd.validated_group_create) public void validateCreate(@ValidatedGroup(value = Create.class) BirdValid birdValid) { - log.info("dogValid : {}", birdValid); + log.info("{}", birdValid); } } diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/list/TheListTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/list/TheListTest.java deleted file mode 100644 index 45fc1eabc..000000000 --- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/list/TheListTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * ioGame - * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. - * # iohao.com . 渔民小镇 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.iohao.game.action.skeleton.list; - -import com.baidu.bjf.remoting.protobuf.Codec; -import com.baidu.bjf.remoting.protobuf.ProtobufProxy; -import com.github.javafaker.Faker; -import com.github.javafaker.Name; -import com.iohao.game.action.skeleton.core.action.pojo.Snake; -import com.iohao.game.action.skeleton.protocol.RequestMessage; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -/** - * @author 渔民小镇 - * @date 2023-02-08 - */ -@Slf4j -public class TheListTest { - - - @Test - public void test() throws Exception { - List list = getSnakes(); - - - RequestMessage requestMessage= new RequestMessage(); - - requestMessage.setData(list); - - log.info("list : {}", list); - - Codec snakeCodec = ProtobufProxy.create(Snake.class); - snakeCodec.encode(null); - - - - - } - - private List getSnakes() { - // cn name - Faker faker = new Faker(Locale.CHINA); - Name name = faker.name(); - - int loop = 3; - List list = new ArrayList<>(loop); - - for (int i = 0; i < loop; i++) { - Snake snake = new Snake(); - snake.id = i + 1; - snake.name = name.lastName() + name.firstName(); - list.add(snake); - } - - return list; - } - - -} diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKitTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKitTest.java index 37dcc929a..6e44bbb08 100644 --- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKitTest.java +++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKitTest.java @@ -15,7 +15,7 @@ public void of() { IntValue of = WrapperKit.of(1); log.info("of : {}", of); - Integer intV = 1; + int intV = 1; Object of1 = WrapperKit.of(intV); log.info("of1 : {}", of1); } diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/toy/ToyTableTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/toy/ToyTableTest.java index fe37d5944..b461f95c4 100644 --- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/toy/ToyTableTest.java +++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/toy/ToyTableTest.java @@ -24,5 +24,4 @@ public void test() { table.render(); } - } \ No newline at end of file diff --git a/common/common-kit/pom.xml b/common/common-kit/pom.xml index aad381d27..6e5fd8635 100644 --- a/common/common-kit/pom.xml +++ b/common/common-kit/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/common/common-micro-kit/pom.xml b/common/common-micro-kit/pom.xml index a94197bd3..6aa6098e6 100644 --- a/common/common-micro-kit/pom.xml +++ b/common/common-micro-kit/pom.xml @@ -6,7 +6,7 @@ com.iohao.game ioGame - 21.5 + 21.6 ../../pom.xml diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/AbstractPropertyValueObservable.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/AbstractPropertyValueObservable.java new file mode 100644 index 000000000..5cf6d6e5e --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/AbstractPropertyValueObservable.java @@ -0,0 +1,66 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +import java.util.Objects; + +/** + * PropertyValueObservable adapter + * + * @author 渔民小镇 + * @date 2024-04-17 + * @see IntegerProperty + * @see LongProperty + * @see StringProperty + * @see BooleanProperty + * @see ObjectProperty + */ +abstract class AbstractPropertyValueObservable implements PropertyValueObservable { + protected boolean valid = true; + ChangeHelperList helperList; + + @Override + public void addListener(PropertyChangeListener listener) { + if (Objects.isNull(this.helperList)) { + this.helperList = new ChangeHelperList<>(); + } + + this.helperList.addListener(this, listener); + } + + @Override + public void removeListener(PropertyChangeListener listener) { + if (Objects.nonNull(this.helperList)) { + this.helperList.removeListener(listener); + } + } + + protected void markInvalid() { + if (this.valid) { + this.valid = false; + this.fireValueChangedEvent(); + } + } + + private void fireValueChangedEvent() { + if (Objects.nonNull(this.helperList)) { + this.helperList.fireValueChangedEvent(); + } + } +} \ No newline at end of file diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/BooleanProperty.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/BooleanProperty.java new file mode 100644 index 000000000..0beea3fa2 --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/BooleanProperty.java @@ -0,0 +1,88 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +import lombok.ToString; + +/** + * bool - 属性具备监听特性。当值发生变更时,会触发监听事件。 + *
{@code
+ *         var property = new BooleanProperty();
+ *         // add listener monitor property object
+ *         property.addListener((observable, oldValue, newValue) -> {
+ *             log.info("oldValue:{}, newValue:{}", oldValue, newValue);
+ *         });
+ *
+ *         property.get(); // value is false
+ *         property.set(true); // When the value changes,listeners are triggered
+ *         property.get(); // value is true
+ * }
+ * 
+ * + * @author 渔民小镇 + * @date 2024-04-17 + */ +@ToString +public final class BooleanProperty extends AbstractPropertyValueObservable { + boolean value; + + public BooleanProperty() { + this(false); + } + + public BooleanProperty(boolean value) { + this.value = value; + } + + @Override + public Boolean getValue() { + return get(); + } + + @Override + public void setValue(Boolean value) { + if (value == null) { + this.set(false); + } else { + this.set(value); + } + } + + /** + * get current value + * + * @return current value + */ + public boolean get() { + this.valid = true; + return this.value; + } + + /** + * set current value + * + * @param newValue current new value + */ + public void set(boolean newValue) { + if (newValue != this.value) { + this.value = newValue; + markInvalid(); + } + } +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/IntegerProperty.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/IntegerProperty.java new file mode 100644 index 000000000..445bdd803 --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/IntegerProperty.java @@ -0,0 +1,102 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +import lombok.ToString; + +/** + * int - 属性具备监听特性。当值发生变更时,会触发监听事件。 + *
{@code
+ *         var property = new IntegerProperty();
+ *         // add listener monitor property object
+ *         property.addListener((observable, oldValue, newValue) -> {
+ *             log.info("oldValue:{}, newValue:{}", oldValue, newValue);
+ *         });
+ *
+ *         property.get(); // value is 0
+ *         property.set(22); // When the value changes,listeners are triggered
+ *         property.get(); // value is 22
+ * }
+ * 
+ * + * @author 渔民小镇 + * @date 2024-04-17 + */ +@ToString +public final class IntegerProperty extends NumberPropertyValueObservable { + int value; + + public IntegerProperty() { + this(0); + } + + public IntegerProperty(int value) { + this.value = value; + } + + @Override + public Integer getValue() { + return get(); + } + + @Override + public void setValue(Number value) { + if (value == null) { + this.set(0); + } else { + this.set(value.intValue()); + } + } + + /** + * get current value + * + * @return current value + */ + public int get() { + this.valid = true; + return this.value; + } + + /** + * set current value + * + * @param newValue current new value + */ + public void set(int newValue) { + if (newValue != this.value) { + this.value = newValue; + markInvalid(); + } + } + + /** + * current value + 1 + */ + public void increment() { + this.set(this.value + 1); + } + + /** + * current value - 1 + */ + public void decrement() { + this.set(this.value - 1); + } +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/LongProperty.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/LongProperty.java new file mode 100644 index 000000000..0a62c7d6c --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/LongProperty.java @@ -0,0 +1,102 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +import lombok.ToString; + +/** + * long - 属性具备监听特性。当值发生变更时,会触发监听事件。 + *
{@code
+ *         var property = new LongProperty();
+ *         // add listener monitor property object
+ *         property.addListener((observable, oldValue, newValue) -> {
+ *             log.info("oldValue:{}, newValue:{}", oldValue, newValue);
+ *         });
+ *
+ *         property.get(); // value is 0
+ *         property.set(22); // When the value changes,listeners are triggered
+ *         property.get(); // value is 22
+ * }
+ * 
+ * + * @author 渔民小镇 + * @date 2024-04-17 + */ +@ToString +public final class LongProperty extends NumberPropertyValueObservable { + long value; + + public LongProperty() { + this(0); + } + + public LongProperty(long value) { + this.value = value; + } + + @Override + public Long getValue() { + return get(); + } + + @Override + public void setValue(Number value) { + if (value == null) { + this.set(0); + } else { + this.set(value.longValue()); + } + } + + /** + * get current value + * + * @return current value + */ + public long get() { + this.valid = true; + return this.value; + } + + /** + * set current value + * + * @param newValue current new value + */ + public void set(long newValue) { + if (newValue != this.value) { + this.value = newValue; + markInvalid(); + } + } + + /** + * current value + 1 + */ + public void increment() { + this.set(this.value + 1); + } + + /** + * current value - 1 + */ + public void decrement() { + this.set(this.value - 1); + } +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/NumberPropertyValueObservable.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/NumberPropertyValueObservable.java new file mode 100644 index 000000000..25f9944b8 --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/NumberPropertyValueObservable.java @@ -0,0 +1,26 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +/** + * @author 渔民小镇 + * @date 2024-04-17 + */ +abstract class NumberPropertyValueObservable extends AbstractPropertyValueObservable { +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/ObjectProperty.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/ObjectProperty.java new file mode 100644 index 000000000..dae977da0 --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/ObjectProperty.java @@ -0,0 +1,88 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +import lombok.ToString; + +/** + * object - 属性具备监听特性。当值(引用)发生变更时,会触发监听事件。 + *
{@code
+ *         YourUser user = new YourUser();
+ *
+ *         var property = new ObjectProperty(user);
+ *         // add listener monitor property object
+ *         property.addListener((observable, oldValue, newValue) -> {
+ *             log.info("oldValue:{}, newValue:{}", oldValue, newValue);
+ *         });
+ *
+ *         property.set(user); // does not trigger listeners
+ *
+ *         YourUser user2 = new YourUser();
+ *         property.set(user2); // When the value changes,listeners are triggered
+ *         property.get(); // value is user2
+ * }
+ * 
+ * + * @author 渔民小镇 + * @date 2024-04-17 + */ +@ToString +public final class ObjectProperty extends AbstractPropertyValueObservable { + T value; + + public ObjectProperty() { + this(null); + } + + public ObjectProperty(T value) { + this.value = value; + } + + @Override + public T getValue() { + return get(); + } + + @Override + public void setValue(T value) { + this.set(value); + } + + /** + * get current value + * + * @return current value + */ + public T get() { + this.valid = true; + return this.value; + } + + /** + * set current value + * + * @param newValue current new value + */ + public void set(T newValue) { + if (this.value != newValue) { + this.value = newValue; + markInvalid(); + } + } +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyAbout.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyAbout.java new file mode 100644 index 000000000..5491207bc --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyAbout.java @@ -0,0 +1,134 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author 渔民小镇 + * @date 2024-04-17 + */ +abstract class ChangeHelper { + final PropertyValueObservable observable; + + /** + * 触发值变更事件 + */ + protected abstract void fireValueChangedEvent(); + + ChangeHelper(PropertyValueObservable observable) { + this.observable = observable; + } + + static ChangeHelper create(PropertyValueObservable observable, PropertyChangeListener listener) { + return new PropertySingleChange<>(observable, observable.getValue(), listener); + } + + static ChangeHelper create(PropertyChangeListener listener) { + return new PropertySingleChange<>(null, null, listener); + } + + private static class PropertySingleChange extends ChangeHelper { + final PropertyChangeListener listener; + T currentValue; + + PropertySingleChange(PropertyValueObservable observable, T currentValue, PropertyChangeListener listener) { + super(observable); + this.currentValue = currentValue; + this.listener = listener; + } + + @Override + protected void fireValueChangedEvent() { + final T oldValue = currentValue; + currentValue = observable.getValue(); + final boolean changed = !Objects.equals(currentValue, oldValue); + if (changed) { + try { + listener.changed(observable, oldValue, currentValue); + } catch (Exception e) { + Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e); + } + } + } + + @Override + public final boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof PropertySingleChange change)) { + return false; + } + + return Objects.equals(listener, change.listener); + } + + @Override + public int hashCode() { + return Objects.hashCode(listener); + } + } +} + +@FieldDefaults(level = AccessLevel.PRIVATE) +final class ChangeHelperList { + List> list; + + void addListener(ChangeHelper helper) { + if (Objects.isNull(this.list)) { + this.list = new CopyOnWriteArrayList<>(); + } + + this.list.add(helper); + } + + void removeListener(PropertyChangeListener listener) { + if (Objects.isNull(this.list) || Objects.isNull(listener)) { + return; + } + + var helper = ChangeHelper.create(listener); + this.list.remove(helper); + } + + void addListener(PropertyValueObservable observable, PropertyChangeListener listener) { + + if (observable == null || listener == null) { + throw new NullPointerException(); + } + + var helper = ChangeHelper.create(observable, listener); + this.addListener(helper); + } + + void fireValueChangedEvent() { + if (Objects.isNull(this.list)) { + return; + } + + this.list.forEach(ChangeHelper::fireValueChangedEvent); + } +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyChangeListener.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyChangeListener.java new file mode 100644 index 000000000..50ead5069 --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyChangeListener.java @@ -0,0 +1,37 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +/** + * 属性值变更事件监听器 + * + * @author 渔民小镇 + * @date 2024-04-17 + */ +@FunctionalInterface +public interface PropertyChangeListener { + /** + * 值变更监听 + * + * @param observable current Property + * @param oldValue oldValue + * @param newValue newValue + */ + void changed(PropertyValueObservable observable, T oldValue, T newValue); +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyValueObservable.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyValueObservable.java new file mode 100644 index 000000000..2549d4e40 --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/PropertyValueObservable.java @@ -0,0 +1,55 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +/** + * 属性值对象 + * + * @author 渔民小镇 + * @date 2024-04-17 + */ +public interface PropertyValueObservable { + /** + * add ChangeListener + * + * @param listener ChangeListener + */ + void addListener(PropertyChangeListener listener); + + /** + * remove ChangeListener + * + * @param listener ChangeListener + */ + void removeListener(PropertyChangeListener listener); + + /** + * get PropertyValue + * + * @return current value + */ + T getValue(); + + /** + * set value + * + * @param value value + */ + void setValue(T value); +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/StringProperty.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/StringProperty.java new file mode 100644 index 000000000..0fc5c9e73 --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/StringProperty.java @@ -0,0 +1,86 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.iohao.game.common.kit.beans.property; + +import lombok.ToString; + +import java.util.Objects; + +/** + * String - 属性具备监听特性。当值发生变更时,会触发监听事件。 + *
{@code
+ *         var property = new StringProperty();
+ *         // add listener monitor property object
+ *         property.addListener((observable, oldValue, newValue) -> {
+ *             log.info("oldValue:{}, newValue:{}", oldValue, newValue);
+ *         });
+ *
+ *         property.get(); // value is null
+ *         property.set("22"); // When the value changes,listeners are triggered
+ *         property.get(); // value is "22"
+ * }
+ * 
+ * + * @author 渔民小镇 + * @date 2024-04-17 + */ +@ToString +public final class StringProperty extends AbstractPropertyValueObservable { + String value; + + public StringProperty() { + this(null); + } + + public StringProperty(String value) { + this.value = value; + } + + @Override + public String getValue() { + return get(); + } + + @Override + public void setValue(String value) { + this.set(value); + } + + /** + * get current value + * + * @return current value + */ + public String get() { + this.valid = true; + return this.value; + } + + /** + * set current value + * + * @param newValue current new value + */ + public void set(String newValue) { + if (!Objects.equals(this.value, newValue)) { + this.value = newValue; + markInvalid(); + } + } +} diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/package-info.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/package-info.java new file mode 100644 index 000000000..0fc58c83e --- /dev/null +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/beans/property/package-info.java @@ -0,0 +1,25 @@ +/* + * ioGame + * Copyright (C) 2021 - present 渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved. + * # iohao.com . 渔民小镇 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * 属性值变更监听特性,属性可添加监听器,当某些属性值的发生变化时,触发监听器。 + * + * @author 渔民小镇 + * @date 2024-04-17 + */ +package com.iohao.game.common.kit.beans.property; \ No newline at end of file diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/OnceTaskListener.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/OnceTaskListener.java index f04b9fe6e..eb94d900d 100644 --- a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/OnceTaskListener.java +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/OnceTaskListener.java @@ -76,8 +76,12 @@ default void run(Timeout timeout) throws Exception { } private void executeFlow() { - if (this.triggerUpdate()) { - this.onUpdate(); + try { + if (this.triggerUpdate()) { + this.onUpdate(); + } + } catch (Throwable e) { + this.onException(e); } } } diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskKit.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskKit.java index 230ff90ef..07d7602fb 100644 --- a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskKit.java +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskKit.java @@ -291,14 +291,18 @@ public void run(Timeout timeout) { } private void executeFlowTimerListener(IntervalTaskListener taskListener, Set set) { - // 移除不活跃的监听 - if (!taskListener.isActive()) { - set.remove(taskListener); - return; - } + try { + // 移除不活跃的监听 + if (!taskListener.isActive()) { + set.remove(taskListener); + return; + } - if (taskListener.triggerUpdate()) { - taskListener.onUpdate(); + if (taskListener.triggerUpdate()) { + taskListener.onUpdate(); + } + } catch (Throwable e) { + taskListener.onException(e); } } } diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskListener.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskListener.java index d50cceeaa..f82c7277f 100644 --- a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskListener.java +++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/TaskListener.java @@ -41,6 +41,18 @@ default boolean triggerUpdate() { */ void onUpdate(); + /** + * 异常回调 + *
+     *     当 triggerUpdate 或 onUpdate 方法抛出异常时,将会传递到这里
+     * 
+ * + * @param e e + */ + default void onException(Throwable e) { + System.err.println(e.getMessage()); + } + /** * 执行 onUpdate 的执行器 *
diff --git a/common/common-micro-kit/src/test/java/com/iohao/game/common/kit/beans/property/PropertyValueObservableTest.java b/common/common-micro-kit/src/test/java/com/iohao/game/common/kit/beans/property/PropertyValueObservableTest.java
new file mode 100644
index 000000000..5bccec935
--- /dev/null
+++ b/common/common-micro-kit/src/test/java/com/iohao/game/common/kit/beans/property/PropertyValueObservableTest.java
@@ -0,0 +1,178 @@
+package com.iohao.game.common.kit.beans.property;
+
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author 渔民小镇
+ * @date 2024-04-17
+ */
+@Slf4j
+public class PropertyValueObservableTest {
+    AtomicInteger counter = new AtomicInteger(0);
+    OnePropertyChangeListener listener = new OnePropertyChangeListener();
+
+    @Test
+    public void testInt() {
+        counter.set(0);
+
+        var property = new IntegerProperty();
+
+        int value = property.get();
+        property.increment();
+        Assert.assertEquals(property.get(), value + 1);
+        property.decrement();
+        Assert.assertEquals(property.get(), value);
+
+        property.addListener(listener);
+        property.addListener((observable, oldValue, newValue) -> {
+            counter.incrementAndGet();
+            log.info("2 - int - oldValue:{}, newValue:{}", oldValue, newValue);
+        });
+
+        property.set(22);
+        property.increment();
+        Assert.assertEquals(counter.get(), 4);
+
+        System.out.println();
+        property.removeListener(listener);
+        property.decrement();
+        Assert.assertEquals(counter.get(), 5);
+    }
+
+    @Test
+    public void testLong() {
+        counter.set(0);
+
+        var property = new LongProperty();
+
+        long value = property.get();
+        property.increment();
+        Assert.assertEquals(property.get(), value + 1);
+        property.decrement();
+        Assert.assertEquals(property.get(), value);
+
+        property.addListener(listener);
+        property.addListener((observable, oldValue, newValue) -> {
+            counter.incrementAndGet();
+            log.info("2 - long - oldValue:{}, newValue:{}", oldValue, newValue);
+        });
+
+        property.set(22);
+        property.increment();
+        Assert.assertEquals(counter.get(), 4);
+
+        System.out.println();
+        property.removeListener(listener);
+        property.decrement();
+        Assert.assertEquals(counter.get(), 5);
+    }
+
+    @Test
+    public void testString() {
+        counter.set(0);
+
+        var property = new StringProperty();
+
+        property.addListener((observable, oldValue, newValue) -> {
+            counter.incrementAndGet();
+            log.info("String - oldValue:{}, newValue:{}, observable:{}", oldValue, newValue, observable);
+        });
+
+        property.set("aaa");
+        property.set("bbb");
+        Assert.assertEquals(counter.get(), 2);
+    }
+
+    @Test
+    public void testBool() {
+        counter.set(0);
+
+        var property = new BooleanProperty();
+
+        property.addListener((observable, oldValue, newValue) -> {
+            counter.incrementAndGet();
+            log.info("Boolean - oldValue:{}, newValue:{}, observable:{}", oldValue, newValue, observable);
+        });
+
+        property.set(true);
+        property.set(false);
+        Assert.assertEquals(counter.get(), 2);
+    }
+
+    @Test
+    public void testObject() {
+        counter.set(0);
+
+        YourUser user = new YourUser();
+        user.age = 100;
+
+        var property = new ObjectProperty<>(user);
+
+        property.addListener((observable, oldValue, newValue) -> {
+            counter.incrementAndGet();
+            log.info("object - oldValue:{}, newValue:{}, observable:{}", oldValue, newValue, observable);
+        });
+
+        property.set(user);
+
+        YourUser user2 = new YourUser();
+        user2.age = 101;
+        property.set(user2);
+
+        Assert.assertEquals(counter.get(), 1);
+    }
+
+    @ToString
+    static class YourUser {
+        int age;
+    }
+
+    @Test
+    public void remove1() {
+        IntegerProperty property = new IntegerProperty(10);
+
+        property.addListener(new PropertyChangeListener<>() {
+            @Override
+            public void changed(PropertyValueObservable observable, Number oldValue, Number newValue) {
+                log.info("1 - newValue : {}", newValue);
+
+                if (newValue.intValue() == 9) {
+                    // 移除当前监听器
+                    observable.removeListener(this);
+                }
+            }
+        });
+
+        property.decrement(); // value == 9,并触发监听器
+        property.decrement(); // value == 8,由于监听器已经移除,所以不会触发任何事件。
+        Assert.assertEquals(property.get(), 8);
+    }
+
+    @Test
+    public void remove2() {
+        IntegerProperty property = new IntegerProperty(10);
+        // 监听器移除的示例
+        OnePropertyChangeListener onePropertyChangeListener = new OnePropertyChangeListener();
+        property.addListener(onePropertyChangeListener);
+
+        property.increment(); // value == 11,并触发监听器
+        property.removeListener(onePropertyChangeListener); // 移除监听器
+        property.increment(); // value == 12,,由于监听器已经移除,所以不会触发任何事件。
+
+        Assert.assertEquals(property.get(), 12);
+
+    }
+
+    class OnePropertyChangeListener implements PropertyChangeListener {
+        @Override
+        public void changed(PropertyValueObservable observable, Number oldValue, Number newValue) {
+            counter.incrementAndGet();
+            log.info("1 - oldValue:{}, newValue:{}, observable:{}", oldValue, newValue, observable);
+        }
+    }
+}
\ No newline at end of file
diff --git a/common/common-micro-kit/src/test/java/com/iohao/game/common/kit/concurrent/TaskKitTest.java b/common/common-micro-kit/src/test/java/com/iohao/game/common/kit/concurrent/TaskKitTest.java
index 62c385ef9..c9ec5cc58 100644
--- a/common/common-micro-kit/src/test/java/com/iohao/game/common/kit/concurrent/TaskKitTest.java
+++ b/common/common-micro-kit/src/test/java/com/iohao/game/common/kit/concurrent/TaskKitTest.java
@@ -1,13 +1,15 @@
 package com.iohao.game.common.kit.concurrent;
 
 import com.iohao.game.common.kit.RandomKit;
-import io.netty.util.Timeout;
 import lombok.extern.slf4j.Slf4j;
+import org.junit.After;
+import org.junit.Assert;
 import org.junit.Test;
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * @author 渔民小镇
@@ -15,24 +17,32 @@
  */
 @Slf4j
 public class TaskKitTest {
+    @After
+    public void tearDown() throws Exception {
+        TimeUnit.SECONDS.sleep(3);
+    }
 
     @Test
-    public void onceTaskListener() throws InterruptedException {
-        Timeout timeout = TaskKit.newTimeout(task -> {
-            //
-            log.info("1 Seconds");
-        }, 1, TimeUnit.SECONDS);
-
-        log.info("timeout : {}", timeout);
+    public void execute() {
+        TaskKit.execute(() -> {
+            // 使用 CacheThreadPool 线程池执行任务
+            log.info("CacheThreadPool consumer task");
+        });
+
+        TaskKit.executeVirtual(() -> {
+            // 使用虚拟线程执行任务
+            log.info("Virtual consumer task");
+        });
+    }
 
+    @Test
+    public void runOnce() {
         // 只执行一次,2 秒后执行
         TaskKit.runOnce(() -> log.info("2 Seconds"), 2, TimeUnit.SECONDS);
         // 只执行一次,1 分钟后执行
         TaskKit.runOnce(() -> log.info("1 Minute"), 1, TimeUnit.MINUTES);
-
-        // 只执行一次,500、800 milliseconds 后
-        TaskKit.runOnceMillis(() -> log.info("500 delayMilliseconds"), 500);
-        TaskKit.runOnceMillis(() -> log.info("800 delayMilliseconds"), 800);
+        // 只执行一次,500 milliseconds 后
+        TaskKit.runOnce(() -> log.info("500 delayMilliseconds"), 500, TimeUnit.MILLISECONDS);
 
         // 只执行一次,1500 Milliseconds后执行,当 theTriggerUpdate 为 true 时,才执行 onUpdate
         boolean theTriggerUpdate = RandomKit.randomBoolean();
@@ -48,18 +58,10 @@ public boolean triggerUpdate() {
             }
 
         }, 1500, TimeUnit.MILLISECONDS);
-
-        TimeUnit.SECONDS.sleep(3);
     }
 
     @Test
-    public void intervalTaskListener() throws InterruptedException {
-
-        // 每分钟调用一次
-        TaskKit.runIntervalMinute(() -> log.info("tick 1 Minute"), 1);
-        // 每 2 分钟调用一次
-        TaskKit.runIntervalMinute(() -> log.info("tick 2 Minute"), 2);
-
+    public void runInterval() {
         // 每 2 秒调用一次
         TaskKit.runInterval(() -> log.info("tick 2 Seconds"), 2, TimeUnit.SECONDS);
         // 每 30 分钟调用一次
@@ -123,11 +125,28 @@ public Executor getExecutor() {
                 return executorService;
             }
         }, 1, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void testException() throws InterruptedException {
+        AtomicBoolean hasEx = new AtomicBoolean(false);
+        TaskKit.runOnce(new OnceTaskListener() {
+            @Override
+            public void onUpdate() {
+                throw new RuntimeException("hello exception");
+            }
+
+            @Override
+            public void onException(Throwable e) {
+                hasEx.set(true);
+            }
+        }, 10, TimeUnit.MILLISECONDS);
 
-        TimeUnit.SECONDS.sleep(20);
+        TimeUnit.MILLISECONDS.sleep(200);
+        Assert.assertTrue(hasEx.get());
     }
 
-//    @Test
+    //    @Test
 //    public void concurrent() throws InterruptedException {
 //        LongAdder[] arrays = TaskKit.arrays;
 //        log.info("arrays : {}", Arrays.toString(arrays));
@@ -137,29 +156,29 @@ public Executor getExecutor() {
 //            extractedThread(5);
 //        }
 //
-//        TaskKit.addIntervalTaskListener(() -> {
+//        TaskKit.runOnce(() -> {
 //            // print
 //            log.info("arrays : {}", Arrays.toString(arrays));
 //        }, 1, TimeUnit.SECONDS);
 //
 //        TimeUnit.SECONDS.sleep(5);
 //    }
-
-    private static void extractedThread(int length) {
-        new Thread(() -> {
-            for (int j = 1; j < length; j++) {
-                var tempValue = j;
-                TaskKit.runInterval(new IntervalTaskListener() {
-                    public String getValue() {
-                        return length + " - " + tempValue;
-                    }
-
-                    @Override
-                    public void onUpdate() {
-
-                    }
-                }, j, TimeUnit.SECONDS);
-            }
-        }).start();
-    }
+//
+//    private static void extractedThread(int length) {
+//        new Thread(() -> {
+//            for (int j = 1; j < length; j++) {
+//                var tempValue = j;
+//                TaskKit.runInterval(new IntervalTaskListener() {
+//                    public String getValue() {
+//                        return length + " - " + tempValue;
+//                    }
+//
+//                    @Override
+//                    public void onUpdate() {
+//
+//                    }
+//                }, j, TimeUnit.SECONDS);
+//            }
+//        }).start();
+//    }
 }
\ No newline at end of file
diff --git a/common/common-validation/pom.xml b/common/common-validation/pom.xml
index 03d49025d..adc3c337d 100644
--- a/common/common-validation/pom.xml
+++ b/common/common-validation/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.5
+        21.6
         ../../pom.xml
     
     4.0.0
diff --git a/external/external-core/pom.xml b/external/external-core/pom.xml
index 7d60a5eea..0e21f3998 100644
--- a/external/external-core/pom.xml
+++ b/external/external-core/pom.xml
@@ -6,7 +6,7 @@
     
         com.iohao.game
         ioGame
-        21.5
+        21.6
         ../../pom.xml
     
 
diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegion.java b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegion.java
index 5127c0d3f..dc8543fd0 100644
--- a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegion.java
+++ b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegion.java
@@ -25,6 +25,8 @@
 
 import java.io.Serializable;
 
+import com.iohao.game.core.common.client.ExternalBizCodeCont;
+
 /**
  * 对外服业务扩展
  * 
@@ -43,6 +45,7 @@
  *
  * @author 渔民小镇
  * @date 2023-02-21
+ * @see ExternalBizCodeCont
  */
 public interface ExternalBizRegion {
     /**
diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegions.java b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegions.java
index 2e143532b..63d53b7cf 100644
--- a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegions.java
+++ b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/ExternalBizRegions.java
@@ -1,5 +1,5 @@
 /*
- * ioGame 
+ * ioGame
  * Copyright (C) 2021 - present  渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
  * # iohao.com . 渔民小镇
  *
@@ -18,9 +18,7 @@
  */
 package com.iohao.game.external.core.broker.client.ext;
 
-import com.iohao.game.external.core.broker.client.ext.impl.AttachmentExternalBizRegion;
-import com.iohao.game.external.core.broker.client.ext.impl.ExistUserExternalBizRegion;
-import com.iohao.game.external.core.broker.client.ext.impl.ForcedOfflineExternalBizRegion;
+import com.iohao.game.external.core.broker.client.ext.impl.*;
 import lombok.experimental.UtilityClass;
 import org.jctools.maps.NonBlockingHashMap;
 
@@ -48,6 +46,7 @@ public final class ExternalBizRegions {
         add(new ExistUserExternalBizRegion());
         add(new ForcedOfflineExternalBizRegion());
         add(new AttachmentExternalBizRegion());
+        add(new UserHeadMetadataExternalBizRegion());
     }
 
     public void add(ExternalBizRegion externalBizRegion) {
diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/AttachmentExternalBizRegion.java b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/AttachmentExternalBizRegion.java
index 33fc1c05b..6b494bdde 100644
--- a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/AttachmentExternalBizRegion.java
+++ b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/AttachmentExternalBizRegion.java
@@ -19,7 +19,6 @@
 package com.iohao.game.external.core.broker.client.ext.impl;
 
 import com.iohao.game.action.skeleton.core.DataCodecKit;
-import com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;
 import com.iohao.game.action.skeleton.core.exception.MsgException;
 import com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;
 import com.iohao.game.core.common.client.Attachment;
@@ -44,20 +43,15 @@ public int getBizCode() {
 
     @Override
     public Serializable request(ExternalBizRegionContext regionContext) throws MsgException {
+        // 检测用户是否存在
+        ExternalBizRegionKit.checkUserExist(regionContext);
 
         RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage();
 
         Attachment attachment = request.getData();
-
         long userId = attachment.getUserId();
-
-        // true 用户存在游戏对外服中
-        var userSessions = regionContext.getUserSessions();
-        boolean existUser = userSessions.existUserSession(userId);
-        ActionErrorEnum.dataNotExist.assertTrue(existUser);
-
         byte[] bytes = DataCodecKit.encode(attachment);
-
+        var userSessions = regionContext.getUserSessions();
         userSessions.ifPresent(userId, userSession -> userSession.option(UserSessionOption.attachment, bytes));
 
         return null;
diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExistUserExternalBizRegion.java b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExistUserExternalBizRegion.java
index 3cea83761..59b6800c2 100644
--- a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExistUserExternalBizRegion.java
+++ b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExistUserExternalBizRegion.java
@@ -18,9 +18,7 @@
  */
 package com.iohao.game.external.core.broker.client.ext.impl;
 
-import com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;
 import com.iohao.game.action.skeleton.core.exception.MsgException;
-import com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;
 import com.iohao.game.core.common.client.ExternalBizCodeCont;
 import com.iohao.game.external.core.broker.client.ext.ExternalBizRegion;
 import com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;
@@ -45,16 +43,8 @@ public int getBizCode() {
 
     @Override
     public Serializable request(ExternalBizRegionContext regionContext) throws MsgException {
-
-        RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage();
-
-        long userId = request.getData();
-        // true 用户存在游戏对外服中
-        var userSessions = regionContext.getUserSessions();
-        boolean existUser = userSessions.existUserSession(userId);
-
-        ActionErrorEnum.dataNotExist.assertTrue(existUser);
-
+        // 检测用户是否存在
+        ExternalBizRegionKit.checkUserExist(regionContext);
         return null;
     }
 }
diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExternalBizRegionKit.java b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExternalBizRegionKit.java
new file mode 100644
index 000000000..d88962f16
--- /dev/null
+++ b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ExternalBizRegionKit.java
@@ -0,0 +1,45 @@
+/*
+ * ioGame
+ * Copyright (C) 2021 - present  渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
+ * # iohao.com . 渔民小镇
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+package com.iohao.game.external.core.broker.client.ext.impl;
+
+import com.iohao.game.action.skeleton.core.exception.ActionErrorEnum;
+import com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;
+import com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;
+import lombok.experimental.UtilityClass;
+
+/**
+ * @author 渔民小镇
+ * @date 2024-04-18
+ */
+@UtilityClass
+class ExternalBizRegionKit {
+    /**
+     * 检测用户是否存在,如果不存在就抛异常
+     *
+     * @param regionContext regionContext
+     */
+    public void checkUserExist(ExternalBizRegionContext regionContext) {
+        RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage();
+        // 检测用户是否存在
+        long userId = request.getUserId();
+        var userSessions = regionContext.getUserSessions();
+        boolean existUser = userSessions.existUserSession(userId);
+        ActionErrorEnum.dataNotExist.assertTrue(existUser);
+    }
+}
diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ForcedOfflineExternalBizRegion.java b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ForcedOfflineExternalBizRegion.java
index 116c49fa8..025e766f6 100644
--- a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ForcedOfflineExternalBizRegion.java
+++ b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/ForcedOfflineExternalBizRegion.java
@@ -56,7 +56,7 @@ public int getBizCode() {
     public Serializable request(ExternalBizRegionContext regionContext) {
         RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage();
 
-        long userId = request.getData();
+        long userId = request.getUserId();
 
         // 发送强制下线消息
         var userSessions = regionContext.getUserSessions();
diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/UserHeadMetadataExternalBizRegion.java b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/UserHeadMetadataExternalBizRegion.java
new file mode 100644
index 000000000..3409667fb
--- /dev/null
+++ b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/ext/impl/UserHeadMetadataExternalBizRegion.java
@@ -0,0 +1,81 @@
+/*
+ * ioGame
+ * Copyright (C) 2021 - present  渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
+ * # iohao.com . 渔民小镇
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+package com.iohao.game.external.core.broker.client.ext.impl;
+
+import com.iohao.game.action.skeleton.core.exception.MsgException;
+import com.iohao.game.action.skeleton.protocol.BarMessage;
+import com.iohao.game.action.skeleton.protocol.HeadMetadata;
+import com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage;
+import com.iohao.game.core.common.client.ExternalBizCodeCont;
+import com.iohao.game.external.core.broker.client.ext.ExternalBizRegion;
+import com.iohao.game.external.core.broker.client.ext.ExternalBizRegionContext;
+import com.iohao.game.external.core.session.UserSession;
+
+import java.io.Serializable;
+
+/**
+ * 从用户(玩家)所在游戏对外服中获取用户自身的数据
+ *
+ * 
+ *     因为用户(玩家)的数据是存在 UserSession 中的,see {@link UserSession#employ(BarMessage)},通过该扩展,我们能获取这些数据。
+ *     employ 中的数据包括:userId、用户所绑定的游戏逻辑服、元信息 ...等,更具体的请阅读源码。
+ *
+ *     使用场景:
+ *         在模拟玩家请求时,需要用到玩家的元信息、已绑定游戏逻辑服...等相关信息时,可以从这里(UserHeadMetadataExternalBizRegion)获取。
+ *         如果需要业务的场景不复杂(也就是不需要玩家元信息、已绑定游戏逻辑服...等相关信息时),建议使用模拟玩家请求(常规的跨服调用)。
+ *
+ *         实际中,使用常规的模拟玩家请求就能满足大部分业务场景的需求了。
+ *
+ *     注意事项:
+ *         需要玩家是在线的,也就是接入了其中一个游戏对外服的。
+ *
+ *     其他参考:
+ *         模拟玩家请求 GM 后台与逻辑服交互 - 文档
+ *
+ *     使用方式
+ * 
+ * + * @author 渔民小镇 + * @date 2024-04-18 + */ +public final class UserHeadMetadataExternalBizRegion implements ExternalBizRegion { + @Override + public int getBizCode() { + return ExternalBizCodeCont.userHeadMetadata; + } + + @Override + public Serializable request(ExternalBizRegionContext regionContext) throws MsgException { + // 检测用户是否存在 + ExternalBizRegionKit.checkUserExist(regionContext); + + RequestCollectExternalMessage request = regionContext.getRequestCollectExternalMessage(); + + HeadMetadata headMetadata = request.getData(); + + long userId = request.getUserId(); + var userSessions = regionContext.getUserSessions(); + UserSession userSession = userSessions.getUserSession(userId); + + // 给 message(RequestMessage) 加上一些 user 自身的数据 + userSession.employ(headMetadata); + + return headMetadata; + } +} diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/RequestCollectExternalMessageExternalProcessor.java b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/RequestCollectExternalMessageExternalProcessor.java index 0b07b9662..cd381f9d4 100644 --- a/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/RequestCollectExternalMessageExternalProcessor.java +++ b/external/external-core/src/main/java/com/iohao/game/external/core/broker/client/processor/RequestCollectExternalMessageExternalProcessor.java @@ -81,9 +81,10 @@ public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestColle context.setRequestCollectExternalMessage(request); context.setUserSessions(this.userSessions); - try { + var hasTraceId = Objects.nonNull(request.getTraceId()); - if (Objects.nonNull(request.getTraceId())) { + try { + if (hasTraceId) { MDC.put(TraceKit.traceName, request.getTraceId()); } @@ -98,7 +99,7 @@ public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestColle itemMessage.setError(ActionErrorEnum.systemOtherErrCode); } } finally { - if (Objects.nonNull(request.getTraceId())) { + if (hasTraceId) { MDC.clear(); } } diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/session/UserSession.java b/external/external-core/src/main/java/com/iohao/game/external/core/session/UserSession.java index b62156621..c2433b6bd 100644 --- a/external/external-core/src/main/java/com/iohao/game/external/core/session/UserSession.java +++ b/external/external-core/src/main/java/com/iohao/game/external/core/session/UserSession.java @@ -89,11 +89,18 @@ public interface UserSession extends AttrOptionDynamic { */ void employ(BarMessage requestMessage); + /** + * 给 HeadMetadata 加上一些 user 自身的数据 + * + * @param headMetadata HeadMetadata + */ + void employ(HeadMetadata headMetadata); + /** * writeAndFlush * - * @param message - * @return + * @param message message + * @return ChannelFuture */ T writeAndFlush(Object message); diff --git a/external/external-netty/pom.xml b/external/external-netty/pom.xml index eac2d43ce..58f5a07e9 100644 --- a/external/external-netty/pom.xml +++ b/external/external-netty/pom.xml @@ -6,7 +6,7 @@ com.iohao.game ioGame - 21.5 + 21.6 ../../pom.xml diff --git a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/session/AbstractUserSession.java b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/session/AbstractUserSession.java index 6b7ce8b9a..7530cea36 100644 --- a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/session/AbstractUserSession.java +++ b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/session/AbstractUserSession.java @@ -65,6 +65,11 @@ abstract class AbstractUserSession implements UserSession { public void employ(BarMessage requestMessage) { HeadMetadata headMetadata = requestMessage.getHeadMetadata(); + this.employ(headMetadata); + } + + @Override + public void employ(HeadMetadata headMetadata) { // 设置请求用户的id headMetadata.setUserId(this.userId); headMetadata.setSourceClientId(this.externalClientId); diff --git a/net-bolt/bolt-broker-server/pom.xml b/net-bolt/bolt-broker-server/pom.xml index d8601b60d..e3de352e1 100644 --- a/net-bolt/bolt-broker-server/pom.xml +++ b/net-bolt/bolt-broker-server/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/net-bolt/bolt-client/pom.xml b/net-bolt/bolt-client/pom.xml index 45ff4e4e6..709775aa5 100644 --- a/net-bolt/bolt-client/pom.xml +++ b/net-bolt/bolt-client/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/net-bolt/bolt-core/pom.xml b/net-bolt/bolt-core/pom.xml index 639d663e9..4054197df 100644 --- a/net-bolt/bolt-core/pom.xml +++ b/net-bolt/bolt-core/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/client/kit/ExternalCommunicationKit.java b/net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/client/kit/ExternalCommunicationKit.java index 0c1db4f2b..d91ac9e04 100644 --- a/net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/client/kit/ExternalCommunicationKit.java +++ b/net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/client/kit/ExternalCommunicationKit.java @@ -19,11 +19,17 @@ package com.iohao.game.bolt.broker.client.kit; import com.iohao.game.action.skeleton.core.flow.FlowContext; +import com.iohao.game.action.skeleton.protocol.HeadMetadata; +import com.iohao.game.action.skeleton.protocol.RequestMessage; +import com.iohao.game.action.skeleton.protocol.external.RequestCollectExternalMessage; +import com.iohao.game.action.skeleton.protocol.external.ResponseCollectExternalItemMessage; import com.iohao.game.bolt.broker.core.client.BrokerClientHelper; import com.iohao.game.core.common.client.Attachment; import com.iohao.game.core.common.client.ExternalBizCodeCont; import lombok.experimental.UtilityClass; +import java.util.Optional; + /** * 这个工具只能在游戏逻辑服中使用 * @@ -39,11 +45,15 @@ public class ExternalCommunicationKit { * @return true 玩家在线 */ public boolean existUser(long userId) { + RequestCollectExternalMessage request = new RequestCollectExternalMessage() + // 根据业务码,调用游戏对外服与业务码对应的业务实现类 (ExistUserExternalBizRegion) + .setBizCode(ExternalBizCodeCont.existUser) + .setUserId(userId); + return BrokerClientHelper // 【游戏逻辑服】与【游戏对外服】通讯上下文 .getInvokeExternalModuleContext() - // 根据业务码,调用游戏对外服与业务码对应的业务实现类 (ExistUserExternalBizRegion) - .invokeExternalModuleCollectMessage(ExternalBizCodeCont.existUser, userId) + .invokeExternalModuleCollectMessage(request) // 只要有一条数据存在,就表示正确的 .anySuccess(); } @@ -54,6 +64,11 @@ public boolean existUser(long userId) { * @param userId 需要强制下线的 userId */ public void forcedOffline(long userId) { + RequestCollectExternalMessage request = new RequestCollectExternalMessage() + // 根据业务码,调用游戏对外服与业务码对应的业务实现类 (ForcedOfflineExternalBizRegion) + .setBizCode(ExternalBizCodeCont.forcedOffline) + .setUserId(userId); + /* * 强制玩家下线 * 实现类 ForcedOfflineExternalBizRegion @@ -62,8 +77,7 @@ public void forcedOffline(long userId) { BrokerClientHelper // 【游戏逻辑服】与【游戏对外服】通讯上下文 .getInvokeExternalModuleContext() - // 根据业务码,调用游戏对外服与业务码对应的业务实现类 (ForcedOfflineExternalBizRegion) - .invokeExternalModuleCollectMessage(ExternalBizCodeCont.forcedOffline, userId); + .invokeExternalModuleCollectMessage(request); } /** @@ -79,4 +93,50 @@ public void forcedOffline(long userId) { public void setAttachment(Attachment attachment, FlowContext flowContext) { flowContext.updateAttachment(attachment); } + + /** + * 给请求添加一些 user 自身所具备的数据,这些数据来自于用户所在游戏对外服 + *
+     *     将用户元信息、所绑定的游戏逻辑服设置到 RequestMessage headMetadata 中。
+     *
+     *     注意事项:只有玩家在线才能从其对应的游戏对外服中获取数据。
+     * 
+ * + * @param requestMessage 请求(通常是模拟的用户请求) + * @return 用户(玩家)所在游戏对外服中的 HeadMetadata 数据,headMetadataOptional 中还包括了一些其他的信息,开发者如果有需要的可从中获取。 + */ + public Optional employHeadMetadata(RequestMessage requestMessage) { + + long userId = Optional.ofNullable(requestMessage) + .map(RequestMessage::getHeadMetadata) + .map(HeadMetadata::getUserId).orElse(0L); + + if (userId <= 0) { + throw new RuntimeException("userId <= 0"); + } + + RequestCollectExternalMessage request = new RequestCollectExternalMessage() + // 根据业务码,调用游戏对外服与业务码对应的业务实现类 (UserHeadMetadataExternalBizRegion) + .setBizCode(ExternalBizCodeCont.userHeadMetadata) + .setUserId(userId) + .setData(requestMessage.getHeadMetadata()) + .setSourceClientId(requestMessage.getHeadMetadata().getSourceClientId()); + + Optional headMetadataOptional = BrokerClientHelper + // 【游戏逻辑服】与【游戏对外服】通讯上下文 + .getInvokeExternalModuleContext() + .invokeExternalModuleCollectMessage(request) + .optionalAnySuccess() + .map(ResponseCollectExternalItemMessage::getData); + + headMetadataOptional.ifPresent(externalHeadMetadata -> { + // 将用户元信息、所绑定的游戏逻辑服设置到 RequestMessage headMetadata 中 + HeadMetadata headMetadata = requestMessage.getHeadMetadata(); + headMetadata.setBindingLogicServerIds(externalHeadMetadata.getBindingLogicServerIds()); + headMetadata.setAttachmentData(externalHeadMetadata.getAttachmentData()); + }); + + // headMetadataOptional 中还包括了一些其他的信息,如有需要的可从中获取 + return headMetadataOptional; + } } diff --git a/net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/client/ExternalBizCodeCont.java b/net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/client/ExternalBizCodeCont.java index ecfa78529..7c2a43e61 100644 --- a/net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/client/ExternalBizCodeCont.java +++ b/net-bolt/bolt-core/src/main/java/com/iohao/game/core/common/client/ExternalBizCodeCont.java @@ -37,4 +37,6 @@ public interface ExternalBizCodeCont { /** 用户(玩家)的元信息同步,AttachmentExternalBizRegion */ int attachment = IoGameCommonCoreConfig.ExternalBizCode.attachment; + /** 用户(玩家)在游戏对外服的 HeadMetadata 信息 */ + int userHeadMetadata = -4; } diff --git a/pom.xml b/pom.xml index 8c03982f2..189b3a1b9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.iohao.game ioGame - 21.5 + 21.6 ioGame 生产资料公有制。 diff --git a/run-one/run-one-netty/pom.xml b/run-one/run-one-netty/pom.xml index 05d837c7a..7ce67d9a5 100644 --- a/run-one/run-one-netty/pom.xml +++ b/run-one/run-one-netty/pom.xml @@ -6,7 +6,7 @@ com.iohao.game ioGame - 21.5 + 21.6 ../../pom.xml diff --git a/widget/light-client/pom.xml b/widget/light-client/pom.xml index b6478f943..6ce95fea5 100644 --- a/widget/light-client/pom.xml +++ b/widget/light-client/pom.xml @@ -6,7 +6,7 @@ com.iohao.game ioGame - 21.5 + 21.6 ../../pom.xml diff --git a/widget/light-client/src/main/java/com/iohao/game/external/client/join/ClientRunOne.java b/widget/light-client/src/main/java/com/iohao/game/external/client/join/ClientRunOne.java index 13a4056a3..182509308 100644 --- a/widget/light-client/src/main/java/com/iohao/game/external/client/join/ClientRunOne.java +++ b/widget/light-client/src/main/java/com/iohao/game/external/client/join/ClientRunOne.java @@ -22,6 +22,7 @@ import com.iohao.game.action.skeleton.protocol.HeadMetadata; import com.iohao.game.common.consts.IoGameLogName; import com.iohao.game.common.kit.PresentKit; +import com.iohao.game.common.kit.concurrent.IntervalTaskListener; import com.iohao.game.common.kit.concurrent.TaskKit; import com.iohao.game.external.client.ClientConnectOption; import com.iohao.game.external.client.InputCommandRegion; @@ -112,15 +113,21 @@ public void startup() { * @return this */ public ClientRunOne idle(int idlePeriod) { - - TaskKit.runInterval(() -> { - BarMessage message = ExternalCodecKit.createRequest(); - HeadMetadata headMetadata = message.getHeadMetadata(); - headMetadata.setCmdCode(ExternalMessageCmdCode.idle); - - ClientUserChannel clientUserChannel = clientUser.getClientUserChannel(); - clientUserChannel.writeAndFlush(message); - + TaskKit.runInterval(new IntervalTaskListener() { + @Override + public void onUpdate() { + BarMessage message = ExternalCodecKit.createRequest(); + HeadMetadata headMetadata = message.getHeadMetadata(); + headMetadata.setCmdCode(ExternalMessageCmdCode.idle); + + ClientUserChannel clientUserChannel = clientUser.getClientUserChannel(); + clientUserChannel.writeAndFlush(message); + } + + @Override + public boolean isActive() { + return clientUser.isActive(); + } }, idlePeriod, TimeUnit.SECONDS); return this; diff --git a/widget/light-client/src/main/java/com/iohao/game/external/client/join/TcpClientStartup.java b/widget/light-client/src/main/java/com/iohao/game/external/client/join/TcpClientStartup.java index e69e65b99..c97fa5e70 100644 --- a/widget/light-client/src/main/java/com/iohao/game/external/client/join/TcpClientStartup.java +++ b/widget/light-client/src/main/java/com/iohao/game/external/client/join/TcpClientStartup.java @@ -23,7 +23,6 @@ import com.iohao.game.external.client.join.handler.ClientMessageHandler; import com.iohao.game.external.client.user.ClientUser; import com.iohao.game.external.client.user.ClientUserChannel; -import com.iohao.game.external.core.netty.handler.codec.TcpExternalCodec; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; @@ -88,6 +87,8 @@ protected void initChannel(SocketChannel ch) { ClientUserChannel userChannel = clientUser.getClientUserChannel(); userChannel.setClientChannel(channel::writeAndFlush); + userChannel.setCloseChannel(channel::close); + clientUser.getClientUserInputCommands().start(); channel.closeFuture().await(); diff --git a/widget/light-client/src/main/java/com/iohao/game/external/client/join/WebSocketClientStartup.java b/widget/light-client/src/main/java/com/iohao/game/external/client/join/WebSocketClientStartup.java index dd071e50b..e587df1dd 100644 --- a/widget/light-client/src/main/java/com/iohao/game/external/client/join/WebSocketClientStartup.java +++ b/widget/light-client/src/main/java/com/iohao/game/external/client/join/WebSocketClientStartup.java @@ -95,6 +95,8 @@ public void onMessage(ByteBuffer byteBuffer) { webSocketClient.send(bytes); }); + clientUserChannel.setCloseChannel(webSocketClient::close); + clientUser.getClientUserInputCommands().start(); // 开始连接服务器 diff --git a/widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUser.java b/widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUser.java index 9057eeb49..5e3a5489d 100644 --- a/widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUser.java +++ b/widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUser.java @@ -47,6 +47,13 @@ public interface ClientUser extends AttrOptionDynamic { void setJwt(String jwt); + /** + * 是否活跃 + * + * @return true 表示玩家活跃 + */ + boolean isActive(); + /** * 登录成功后,调用 InputCommandRegion 回调 *
diff --git a/widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUserChannel.java b/widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUserChannel.java
index ade9adb27..43420278e 100644
--- a/widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUserChannel.java
+++ b/widget/light-client/src/main/java/com/iohao/game/external/client/user/ClientUserChannel.java
@@ -79,6 +79,7 @@ public class ClientUserChannel {
 
     final DefaultClientUser clientUser;
 
+    public Runnable closeChannel;
     public Consumer clientChannel;
     /** 目标 ip (服务器 ip) */
     public InetSocketAddress inetSocketAddress;
@@ -157,6 +158,11 @@ public void addListen(ListenCommand listenCommand) {
         this.listenMap.put(cmdMerge, listenCommand);
     }
 
+    public void closeChannel() {
+        this.clientUser.setActive(false);
+        Optional.ofNullable(closeChannel).ifPresent(Runnable::run);
+    }
+
     class DefaultChannelRead implements ClientChannelRead {
         @Override
         public void read(BarMessage message) {
diff --git a/widget/light-client/src/main/java/com/iohao/game/external/client/user/DefaultClientUser.java b/widget/light-client/src/main/java/com/iohao/game/external/client/user/DefaultClientUser.java
index 30f05b9fc..52cc941d0 100644
--- a/widget/light-client/src/main/java/com/iohao/game/external/client/user/DefaultClientUser.java
+++ b/widget/light-client/src/main/java/com/iohao/game/external/client/user/DefaultClientUser.java
@@ -57,6 +57,8 @@ public class DefaultClientUser implements ClientUser {
     String nickname;
     String jwt;
 
+    boolean active = true;
+
     @Override
     public void callbackInputCommandRegion() {
         if (Objects.isNull(this.inputCommandRegions)) {
diff --git a/widget/light-domain-event/pom.xml b/widget/light-domain-event/pom.xml
index d19accf0f..5e1293aab 100644
--- a/widget/light-domain-event/pom.xml
+++ b/widget/light-domain-event/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.5
+        21.6
         ../../pom.xml
     
     4.0.0
@@ -19,13 +19,5 @@
             disruptor
             ${disruptor.version}
         
-
-        
-        
-            cn.hutool
-            hutool-core
-            5.8.11
-            test
-        
     
 
\ No newline at end of file
diff --git a/widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/StudentDomainEventTest2.java b/widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/StudentDomainEventTest2.java
index 7ab094d87..3caf5aea6 100644
--- a/widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/StudentDomainEventTest2.java
+++ b/widget/light-domain-event/src/test/java/com/iohao/game/widget/light/domain/event/StudentDomainEventTest2.java
@@ -18,7 +18,6 @@
  */
 package com.iohao.game.widget.light.domain.event;
 
-import cn.hutool.core.thread.ThreadUtil;
 import com.iohao.game.widget.light.domain.event.student.StudentCountEventHandler;
 import com.iohao.game.widget.light.domain.event.student.StudentEo;
 import lombok.extern.slf4j.Slf4j;
@@ -58,27 +57,6 @@ public void setUp() {
         domainEventContext.startup();
     }
 
-    @Test
-    public void testEventSendMulti2() throws InterruptedException {
-        StudentEo studentEo = new StudentEo(1);
-
-        ThreadUtil.concurrencyTest(100, () -> {
-            for (int j = 0; j < 20_000; j++) {
-                studentEo.send();
-            }
-        });
-
-        log.info("start");
-        /*
-         * 需要等待一下;
-         * 如果不等待,但在测试用例中又执行 domainEventContext.stop(); 方法
-         * disruptor 将不会接收事件了
-         */
-        log.info("StudentCountEventHandler.longAdder : {}", StudentCountEventHandler.longAdder);
-        TimeUnit.SECONDS.sleep(1);
-        log.info("======== longAdder ========: {}", StudentCountEventHandler.longAdder);
-    }
-
     @Test
     public void testEventSendSingle() throws InterruptedException {
         StudentEo studentEo = new StudentEo(1);
diff --git a/widget/light-game-room/pom.xml b/widget/light-game-room/pom.xml
index 9388ed1d9..77ee41fa6 100644
--- a/widget/light-game-room/pom.xml
+++ b/widget/light-game-room/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.5
+        21.6
         ../../pom.xml
     
     4.0.0
diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractFlowContextSend.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractFlowContextSend.java
index 609b16fb9..d5e6e2e46 100644
--- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractFlowContextSend.java
+++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractFlowContextSend.java
@@ -19,8 +19,7 @@
 package com.iohao.game.widget.light.room;
 
 import com.iohao.game.action.skeleton.core.ActionSend;
-import com.iohao.game.action.skeleton.core.commumication.BroadcastContext;
-import com.iohao.game.action.skeleton.core.commumication.BrokerClientContext;
+import com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;
 import com.iohao.game.action.skeleton.core.flow.FlowContext;
 import com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;
 import com.iohao.game.action.skeleton.protocol.HeadMetadata;
@@ -50,15 +49,21 @@ public abstract class AbstractFlowContextSend implements Topic, Eo, ActionSend {
 
     /** 需要推送的用户id列表 */
     final Set userIds = new NonBlockingHashSet<>();
+    final CommunicationAggregationContext aggregationContext;
 
     /** 是否执行发送领域事件操作: true 执行推送操作 */
     boolean doSend = true;
 
     /** 业务框架 flow 上下文 */
-    protected final FlowContext flowContext;
+    protected FlowContext flowContext;
 
     protected AbstractFlowContextSend(FlowContext flowContext) {
         this.flowContext = flowContext;
+        this.aggregationContext = flowContext.option(FlowAttr.aggregationContext);
+    }
+
+    public AbstractFlowContextSend(CommunicationAggregationContext aggregationContext) {
+        this.aggregationContext = aggregationContext;
     }
 
     /**
@@ -128,11 +133,8 @@ public final void execute() {
         // 在将数据推送前调用的钩子方法
         this.trick();
 
-        BrokerClientContext brokerClientContext = flowContext.option(FlowAttr.brokerClientContext);
-
         // 推送响应 (广播消息)给指定的用户列表
-        BroadcastContext broadcastContext = brokerClientContext.getBroadcastContext();
-        broadcastContext.broadcast(responseMessage, this.userIds);
+        flowContext.broadcast(responseMessage, this.userIds);
     }
 
     /**
diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractRoom.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractRoom.java
index ebde0d395..3b257ee44 100644
--- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractRoom.java
+++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractRoom.java
@@ -18,9 +18,7 @@
  */
 package com.iohao.game.widget.light.room;
 
-import com.iohao.game.action.skeleton.core.BarSkeleton;
-import com.iohao.game.action.skeleton.core.flow.ActionMethodResultWrap;
-import com.iohao.game.action.skeleton.core.flow.FlowContext;
+import com.iohao.game.common.kit.PresentKit;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
@@ -30,10 +28,8 @@
 
 import java.io.Serial;
 import java.io.Serializable;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
+import java.util.*;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -46,7 +42,10 @@
 @Setter
 @Accessors(chain = true)
 @FieldDefaults(level = AccessLevel.PROTECTED)
-public abstract class AbstractRoom implements Serializable {
+public abstract class AbstractRoom implements Serializable
+        // 房间广播增强
+        , RoomBroadcastEnhance {
+
     @Serial
     private static final long serialVersionUID = -6937915481102847959L;
 
@@ -81,15 +80,6 @@ public abstract class AbstractRoom implements Serializable {
     /** 房间状态 */
     RoomStatusEnum roomStatusEnum = RoomStatusEnum.wait;
 
-    /**
-     * 创建推送对象
-     *
-     * @param flowContext flowContext
-     * @param          t
-     * @return AbstractFlowContextSend
-     */
-    protected abstract  T createSend(FlowContext flowContext);
-
     /**
      * 玩家列表: 所有玩家信息
      *
@@ -113,6 +103,7 @@ public Collection listPlayerId(long excludePlayerId) {
                 .toList();
     }
 
+    @Override
     public Collection listPlayerId() {
         return this.playerMap.keySet();
     }
@@ -142,7 +133,7 @@ public void addPlayer(AbstractPlayer player) {
      *
      * @param player 玩家
      */
-    public void removePlayer(AbstractPlayer player){
+    public void removePlayer(AbstractPlayer player) {
         long userId = player.getId();
         this.playerMap.remove(userId);
         this.playerSeatMap.remove(player.getSeat());
@@ -153,75 +144,25 @@ public boolean isStatus(RoomStatusEnum roomStatusEnum) {
     }
 
     /**
-     * 广播业务数据给房间内的所有玩家
+     * 如果玩家在房间内,就执行给定的操作,否则不执行任何操作。
      *
-     * @param flowContext  flow 上下文
-     * @param methodResult 广播的业务数据
+     * @param userId userId
+     * @param action 给定操作
+     * @param     t
      */
-    public void broadcast(FlowContext flowContext, Object methodResult) {
-        this.broadcast(flowContext, methodResult, this.listPlayerId());
+    public  void ifPlayerExist(long userId, Consumer action) {
+        T player = this.getPlayerById(userId);
+        Optional.ofNullable(player).ifPresent(action);
     }
 
     /**
-     * 广播业务数据给用户列表
+     * 如果玩家不在房间内,就执行给定的操作,否则不执行任何操作。
      *
-     * @param flowContext  flow 上下文
-     * @param methodResult 广播的业务数据
-     * @param userIdList   用户列表
+     * @param userId   userId
+     * @param runnable 给定操作
      */
-    public void broadcast(FlowContext flowContext, Object methodResult, Collection userIdList) {
-        this.broadcast(flowContext, methodResult, userIdList, 0);
-    }
-
-    /**
-     * 广播业务数据给房间内的所有玩家, 排除指定用户
-     *
-     * @param flowContext   flow 上下文
-     * @param methodResult  广播的业务数据
-     * @param excludeUserId 排除的用户
-     */
-    public void broadcast(FlowContext flowContext, Object methodResult, long excludeUserId) {
-        this.broadcast(flowContext, methodResult, this.listPlayerId(), excludeUserId);
-    }
-
-    /**
-     * 广播业务数据给用户列表, 并排除一个用户
-     *
-     * @param flowContext   flow 上下文
-     * @param methodResult  广播的业务数据
-     * @param userIdList    用户列表
-     * @param excludeUserId 排除的用户
-     */
-    public void broadcast(FlowContext flowContext, Object methodResult, Collection userIdList, long excludeUserId) {
-        // 设置业务数据到 flowContext 中
-        flowContext.setMethodResult(methodResult);
-
-        // 把刚才设置的业务数据包装到响应对象中
-        BarSkeleton barSkeleton = flowContext.getBarSkeleton();
-        // 4 ---- wrap result 结果包装器
-        ActionMethodResultWrap actionMethodResultWrap = barSkeleton.getActionMethodResultWrap();
-        // 结果包装器
-        actionMethodResultWrap.wrap(flowContext);
-
-        AbstractFlowContextSend send = this.createSend(flowContext)
-                .addUserId(userIdList, excludeUserId);
-
-        send.send();
-    }
-
-    /**
-     * 广播业务数据给指定的用户
-     *
-     * @param flowContext  flow 上下文
-     * @param methodResult 广播的业务数据
-     * @param userId       指定的用户
-     */
-    public void broadcastUser(FlowContext flowContext, Object methodResult, long userId) {
-        flowContext.setMethodResult(methodResult);
-
-        AbstractFlowContextSend send = this.createSend(flowContext)
-                .addUserId(userId);
-
-        send.send();
+    public void ifPlayerNotExist(long userId, Runnable runnable) {
+        var player = this.getPlayerById(userId);
+        PresentKit.ifNull(player, runnable);
     }
-}
+}
\ No newline at end of file
diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomBroadcastEnhance.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomBroadcastEnhance.java
new file mode 100644
index 000000000..4a15e3ff4
--- /dev/null
+++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomBroadcastEnhance.java
@@ -0,0 +1,85 @@
+/*
+ * ioGame
+ * Copyright (C) 2021 - present  渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
+ * # iohao.com . 渔民小镇
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+package com.iohao.game.widget.light.room;
+
+import com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;
+import com.iohao.game.action.skeleton.core.flow.FlowContext;
+import com.iohao.game.action.skeleton.core.flow.attr.FlowAttr;
+import com.iohao.game.action.skeleton.kit.RangeBroadcast;
+
+import java.util.Collection;
+
+/**
+ * 房间内的广播增强
+ *
+ * @author 渔民小镇
+ * @date 2024-04-23
+ */
+interface RoomBroadcastEnhance {
+    /**
+     * 房间内的所有玩家 userIds
+     *
+     * @return players,userIds
+     */
+    Collection listPlayerId();
+
+    /**
+     * 通过 FlowContext 创建一个 RangeBroadcast,默认会添加上当前房间内的所有玩家
+     *
+     * @param flowContext flowContext
+     * @return RangeBroadcast 范围内的广播
+     */
+    default RangeBroadcast ofRangeBroadcast(FlowContext flowContext) {
+        CommunicationAggregationContext aggregationContext = flowContext.option(FlowAttr.aggregationContext);
+        return this.ofRangeBroadcast(aggregationContext);
+    }
+
+    /**
+     * 通过 CommunicationAggregationContext 创建一个 RangeBroadcast,默认会添加上当前房间内的所有玩家
+     *
+     * @param aggregationContext aggregationContext
+     * @return RangeBroadcast 范围内的广播
+     */
+    default RangeBroadcast ofRangeBroadcast(CommunicationAggregationContext aggregationContext) {
+        return this.ofEmptyRangeBroadcast(aggregationContext)
+                // 添加上房间内的所有玩家
+                .addUserId(this.listPlayerId());
+    }
+
+    /**
+     * 通过 FlowContext 创建一个 RangeBroadcast
+     *
+     * @param flowContext flowContext
+     * @return RangeBroadcast 范围内的广播
+     */
+    default RangeBroadcast ofEmptyRangeBroadcast(FlowContext flowContext) {
+        CommunicationAggregationContext aggregationContext = flowContext.option(FlowAttr.aggregationContext);
+        return this.ofEmptyRangeBroadcast(aggregationContext);
+    }
+
+    /**
+     * 通过 CommunicationAggregationContext 创建一个 RangeBroadcast
+     *
+     * @param aggregationContext aggregationContext
+     * @return RangeBroadcast 范围内的广播
+     */
+    default RangeBroadcast ofEmptyRangeBroadcast(CommunicationAggregationContext aggregationContext) {
+        return new RangeBroadcast(aggregationContext);
+    }
+}
\ No newline at end of file
diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomBroadcastFlowContext.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomBroadcastFlowContext.java
new file mode 100644
index 000000000..bf9db5f54
--- /dev/null
+++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomBroadcastFlowContext.java
@@ -0,0 +1,134 @@
+/*
+ * ioGame
+ * Copyright (C) 2021 - present  渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
+ * # iohao.com . 渔民小镇
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+package com.iohao.game.widget.light.room;
+
+import com.iohao.game.action.skeleton.core.commumication.CommunicationAggregationContext;
+import com.iohao.game.action.skeleton.core.flow.FlowContext;
+
+import java.util.Collection;
+
+/**
+ * 房间广播,兼容旧的设计
+ * 
+ *     该系列方法已经废弃,请使用下面的来代替,在语义上会更清晰
+ *     1 {@link RoomBroadcastEnhance#ofRangeBroadcast(CommunicationAggregationContext)}
+ *     2 {@link RoomBroadcastEnhance#ofRangeBroadcast(FlowContext)}
+ * 
+ * + * @author 渔民小镇 + * @date 2024-04-23 + */ +@Deprecated +public interface RoomBroadcastFlowContext extends RoomBroadcastEnhance { + + /** + * 广播业务数据给房间内的所有玩家 + *
+     *     该方法已经废弃,请使用下面的来代替,在语义上会更清晰
+     *     1 {@link RoomBroadcastEnhance#ofRangeBroadcast(CommunicationAggregationContext)}
+     *     2 {@link RoomBroadcastEnhance#ofRangeBroadcast(FlowContext)}
+     * 
+ * + * @param flowContext flow 上下文 + * @param bizData 广播的业务数据 + */ + @Deprecated + default void broadcast(FlowContext flowContext, Object bizData) { + this.broadcast(flowContext, bizData, this.listPlayerId()); + } + + /** + * 广播业务数据给用户列表 + *
+     *     该方法已经废弃,请使用下面的来代替,在语义上会更清晰
+     *     1 {@link RoomBroadcastEnhance#ofRangeBroadcast(CommunicationAggregationContext)}
+     *     2 {@link RoomBroadcastEnhance#ofRangeBroadcast(FlowContext)}
+     * 
+ * + * @param flowContext flow 上下文 + * @param bizData 广播的业务数据 + * @param userIdList 用户列表 + */ + @Deprecated + default void broadcast(FlowContext flowContext, Object bizData, Collection userIdList) { + this.broadcast(flowContext, bizData, userIdList, 0); + } + + /** + * 广播业务数据给房间内的所有玩家, 排除指定用户 + *
+     *     该方法已经废弃,请使用下面的来代替,在语义上会更清晰
+     *     1 {@link RoomBroadcastEnhance#ofRangeBroadcast(CommunicationAggregationContext)}
+     *     2 {@link RoomBroadcastEnhance#ofRangeBroadcast(FlowContext)}
+     * 
+ * + * @param flowContext flow 上下文 + * @param bizData 广播的业务数据 + * @param excludeUserId 排除的用户 + */ + @Deprecated + default void broadcast(FlowContext flowContext, Object bizData, long excludeUserId) { + this.broadcast(flowContext, bizData, this.listPlayerId(), excludeUserId); + } + + /** + * 广播业务数据给用户列表, 并排除一个用户 + *
+     *     该方法已经废弃,请使用下面的来代替,在语义上会更清晰
+     *     1 {@link RoomBroadcastEnhance#ofRangeBroadcast(CommunicationAggregationContext)}
+     *     2 {@link RoomBroadcastEnhance#ofRangeBroadcast(FlowContext)}
+     * 
+ * + * @param flowContext flow 上下文 + * @param bizData 广播的业务数据 + * @param userIdList 用户列表 + * @param excludeUserId 排除的用户 + */ + @Deprecated + default void broadcast(FlowContext flowContext, Object bizData, Collection userIdList, long excludeUserId) { + + this.ofRangeBroadcast(flowContext) + // 响应的数据 + .setResponseMessage(flowContext.getCmdInfo(), bizData) + .addUserId(userIdList) + .removeUserId(excludeUserId) + .execute(); + } + + /** + * 广播业务数据给指定的用户 + *
+     *     该方法已经废弃,请使用下面的来代替,在语义上会更清晰
+     *     1 {@link RoomBroadcastEnhance#ofRangeBroadcast(CommunicationAggregationContext)}
+     *     2 {@link RoomBroadcastEnhance#ofRangeBroadcast(FlowContext)}
+     * 
+ * + * @param flowContext flow 上下文 + * @param bizData 广播的业务数据 + * @param userId 指定的用户 + */ + @Deprecated + default void broadcastUser(FlowContext flowContext, Object bizData, long userId) { + this.ofRangeBroadcast(flowContext) + // 响应的数据 + .setResponseMessage(flowContext.getCmdInfo(), bizData) + .addUserId(userId) + .execute(); + } +} \ No newline at end of file diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomStatusEnum.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomStatusEnum.java index f4cc6f3bb..95350bacc 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomStatusEnum.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomStatusEnum.java @@ -29,5 +29,4 @@ public enum RoomStatusEnum { wait, /** 开始 */ start; - } diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/OperationContext.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/OperationContext.java index 4a37aaacf..c82dd41c8 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/OperationContext.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/operation/OperationContext.java @@ -34,5 +34,4 @@ public class OperationContext { AbstractRoom room; AbstractPlayer player; - } diff --git a/widget/light-jprotobuf/pom.xml b/widget/light-jprotobuf/pom.xml index 0e7743f12..4d5cb1d31 100644 --- a/widget/light-jprotobuf/pom.xml +++ b/widget/light-jprotobuf/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/widget/light-profile/pom.xml b/widget/light-profile/pom.xml index 9defcef30..e3cc54363 100644 --- a/widget/light-profile/pom.xml +++ b/widget/light-profile/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/widget/light-redis-lock-spring-boot-starter/pom.xml b/widget/light-redis-lock-spring-boot-starter/pom.xml index e2aa29a3d..dc0b7ef92 100644 --- a/widget/light-redis-lock-spring-boot-starter/pom.xml +++ b/widget/light-redis-lock-spring-boot-starter/pom.xml @@ -8,7 +8,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml diff --git a/widget/light-redis-lock/pom.xml b/widget/light-redis-lock/pom.xml index a19c71bc9..d0d1ab9d9 100644 --- a/widget/light-redis-lock/pom.xml +++ b/widget/light-redis-lock/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/widget/light-timer-task/pom.xml b/widget/light-timer-task/pom.xml index 3345d7c73..7acde3fad 100644 --- a/widget/light-timer-task/pom.xml +++ b/widget/light-timer-task/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.5 + 21.6 ../../pom.xml 4.0.0 diff --git a/widget/other-tool/pom.xml b/widget/other-tool/pom.xml index f5229af39..0cad0a536 100644 --- a/widget/other-tool/pom.xml +++ b/widget/other-tool/pom.xml @@ -6,7 +6,7 @@ com.iohao.game ioGame - 21.5 + 21.6 ../../pom.xml