diff --git a/.github/ISSUE_TEMPLATE/ask-question.md b/.github/ISSUE_TEMPLATE/ask-question.md index 8beb5b853..92fba6d10 100644 --- a/.github/ISSUE_TEMPLATE/ask-question.md +++ b/.github/ISSUE_TEMPLATE/ask-question.md @@ -23,3 +23,14 @@ about: 对框架使用过程中遇到的问题、不清楚或模糊的地方; + +### 复现步骤 + +描述复现步骤,并提供复现 demo + + + + +### 版本 + +- ioGame version: diff --git a/README.md b/README.md index c7eaf1712..1f159d83f 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ ioGame 是轻量级的网络编程框架,**不依赖任何第三方**中间件 com.iohao.game run-one-netty - 21.6 + 21.7 ``` diff --git a/common/common-core/pom.xml b/common/common-core/pom.xml index a5e042f59..31c71da12 100644 --- a/common/common-core/pom.xml +++ b/common/common-core/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.6 + 21.7 ../../pom.xml 4.0.0 @@ -55,7 +55,6 @@ provided - org.springframework @@ -71,14 +70,22 @@ test - + - com.github.javafaker - javafaker - 1.0.2 + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} test + + + + org.glassfish + jakarta.el + ${jakarta.el.version} + test + 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 ee8aba6b4..c5c3c1805 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.6"; + String internalVersion = "21.7"; VERSION = internalVersion .replace("", "") diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommand.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommand.java index c94fab148..54e452056 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommand.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommand.java @@ -34,8 +34,10 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; +import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; /** * ActionCommand 命令对象,也称为 action。 @@ -123,6 +125,10 @@ private ActionCommand(Builder builder) { this.deliveryContainer = builder.deliveryContainer; } + public Stream streamParamInfo() { + return this.methodHasParam ? Arrays.stream(this.paramInfos) : Stream.empty(); + } + /** * {@link ActionCommand} 命令的构建器 *

@@ -248,16 +254,11 @@ public static final class ParamInfo implements MethodParamResultInfo { * */ final Class actualClazz; - /** - * 是否扩展属性 - *

-         *     true 表示是扩展属性
-         * 
- */ - final boolean extension; final boolean customMethodParser; /** JSR380 验证组 */ final Class[] validatorGroups; + /** true 表示参数类型是 {@link FlowContext} */ + final boolean flowContext; /** true : 开启 JSR380 验证规范 */ boolean validator; @@ -293,9 +294,18 @@ public static final class ParamInfo implements MethodParamResultInfo { var validatedAnn = this.parameter.getAnnotation(ValidatedGroup.class); this.validatorGroups = Objects.isNull(validatedAnn) ? EMPTY_GROUPS : validatedAnn.value(); - this.extension = FlowContext.class.isAssignableFrom(paramClazz); + this.flowContext = FlowContext.class.isAssignableFrom(actualTypeArgumentClazz); } + /** + * 是否业务参数 + * + * @return true 业务参数 + * @since 21.7 + */ + public boolean isBizData() { + return !this.flowContext; + } @Override public String toString() { diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandParser.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandParser.java index 7780f10c0..79581e885 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandParser.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/ActionCommandParser.java @@ -23,18 +23,26 @@ import com.esotericsoftware.reflectasm.MethodAccess; import com.iohao.game.action.skeleton.annotation.ActionController; import com.iohao.game.action.skeleton.annotation.ActionMethod; +import com.iohao.game.action.skeleton.core.action.parser.ProtobufCheckActionParserListener; +import com.iohao.game.action.skeleton.core.codec.ProtoDataCodec; import com.iohao.game.action.skeleton.core.doc.ActionCommandDoc; import com.iohao.game.action.skeleton.core.doc.ActionDoc; import com.iohao.game.action.skeleton.core.doc.ActionDocs; +import com.iohao.game.action.skeleton.core.action.parser.ProtobufActionParserListener; +import com.iohao.game.action.skeleton.core.action.parser.ActionParserListener; +import com.iohao.game.action.skeleton.core.action.parser.ActionParserContext; import com.iohao.game.common.kit.StrKit; import lombok.AccessLevel; import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Stream; /** @@ -46,6 +54,8 @@ * @author 渔民小镇 * @date 2021-12-12 */ +@Setter +@Accessors(chain = true) @FieldDefaults(level = AccessLevel.PRIVATE) final class ActionCommandParser { /** 命令域 管理器 */ @@ -53,9 +63,12 @@ final class ActionCommandParser { final ActionCommandRegions actionCommandRegions = new ActionCommandRegions(); final BarSkeletonSetting setting; + final ActionParserListeners actionParserListeners; + BarSkeleton barSkeleton; - ActionCommandParser(BarSkeletonSetting setting) { - this.setting = setting; + ActionCommandParser(BarSkeletonBuilder builder) { + this.setting = builder.getSetting(); + this.actionParserListeners = builder.actionParserListeners; } /** @@ -69,7 +82,6 @@ final class ActionCommandParser { * @param controllerList action 类的 class 列表 */ ActionCommandParser buildAction(List> controllerList) { - var doc = new ActionCommandDocParser(this, controllerList, setting.parseDoc); // action 类的 stream @@ -82,6 +94,7 @@ ActionCommandParser buildAction(List> controllerList) { int cmd = controllerClazz.getAnnotation(ActionController.class).value(); // 子路由 map var actionCommandRegion = this.actionCommandRegions.getActionCommandRegion(cmd); + actionCommandRegion.setActionControllerClazz(controllerClazz); // true 表示交付给容器来管理 如 spring 等 boolean deliveryContainer = this.deliveryContainer(controllerClazz); @@ -133,14 +146,16 @@ ActionCommandParser buildAction(List> controllerList) { // 子路由映射 actionCommandRegion.add(command); + // 文档相关 ActionDoc actionDoc = ActionDocs.ofActionDoc(cmd, controllerClazz); actionDoc.addActionCommand(command); }); - }); // 内部将所有的 action 转换为 action 二维数组 actionCommandRegions.initActionCommandArray(setting); + // action 构建时的监听 + executeActionListeners(); return this; } @@ -223,12 +238,11 @@ private void paramInfo(Method method, ActionCommand.Builder builder) { * 1 没开启 JSR380 验证, 不做处理 * 2 过滤不需要验证的参数 */ - if (!this.setting.validator || paramInfo.isExtension()) { + if (!this.setting.validator || paramInfo.isFlowContext()) { continue; } paramInfo.validator = ValidatorKit.isValidator(parameter.getType()); - } } @@ -258,4 +272,50 @@ private Object ofActionInstance(Class controllerClazz) { return actionInstance; } + + private void executeActionListeners() { + actionCommandRegions.regionMap.forEach((cmd, actionCommandRegion) -> { + // action command, actionMethod + actionCommandRegion.getSubActionCommandMap().forEach((subCmd, command) -> { + // action 构建时的上下文 + ActionParserContext context = new ActionParserContext() + .setBarSkeleton(barSkeleton) + .setActionCommand(command); + + // action 构建时的监听 - actionCommand + this.actionParserListeners.onActionCommand(context); + }); + }); + + this.actionParserListeners.onAfter(barSkeleton); + } } + +@FieldDefaults(level = AccessLevel.PRIVATE) +final class ActionParserListeners { + final List listeners = new CopyOnWriteArrayList<>(); + + void addActionParserListener(ActionParserListener listener) { + this.listeners.add(listener); + } + + void onActionCommand(ActionParserContext context) { + this.listeners.forEach(listener -> listener.onActionCommand(context)); + } + + void onAfter(BarSkeleton barSkeleton) { + this.listeners.forEach(listener -> listener.onAfter(barSkeleton)); + } + + public boolean isEmpty() { + return this.listeners.isEmpty(); + } + + public ActionParserListeners() { + // 监听器 - 预先创建协议代理类 + if (DataCodecKit.getDataCodec() instanceof ProtoDataCodec) { + this.addActionParserListener(ProtobufActionParserListener.me()); + this.addActionParserListener(ProtobufCheckActionParserListener.me()); + } + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeletonBuilder.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeletonBuilder.java index 1e739a92b..37bb9c5a3 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeletonBuilder.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/BarSkeletonBuilder.java @@ -25,6 +25,7 @@ import com.iohao.game.action.skeleton.core.exception.MsgExceptionInfo; import com.iohao.game.action.skeleton.core.flow.*; import com.iohao.game.action.skeleton.core.flow.internal.*; +import com.iohao.game.action.skeleton.core.action.parser.ActionParserListener; import com.iohao.game.action.skeleton.core.runner.Runner; import com.iohao.game.action.skeleton.core.runner.Runners; import com.iohao.game.common.kit.concurrent.executor.*; @@ -66,6 +67,8 @@ public final class BarSkeletonBuilder { final ActionSendDocs actionSendDocs = new ActionSendDocs(); /** 错误码相关的文档 */ final ErrorCodeDocs errorCodeDocs = new ErrorCodeDocs(); + /** action 构建时的钩子方法 */ + ActionParserListeners actionParserListeners = new ActionParserListeners(); /** action工厂 */ ActionFactoryBean actionFactoryBean = new DefaultActionFactoryBean<>(); /** action 执行完后,最后需要做的事。 一般用于将数据发送到 Broker(游戏网关) */ @@ -145,6 +148,8 @@ public BarSkeleton build() { this.runners.setBarSkeleton(barSkeleton); + this.actionParserListeners = null; + return barSkeleton; } @@ -204,6 +209,11 @@ public BarSkeletonBuilder addRunner(Runner runner) { return this; } + public BarSkeletonBuilder addActionParserListener(ActionParserListener listener) { + this.actionParserListeners.addActionParserListener(listener); + return this; + } + private void extractedInOut(BarSkeleton barSkeleton) { var inOutManager = new InOutManager(this.setting, this.inOutList); barSkeleton.setInOutManager(inOutManager); @@ -211,7 +221,8 @@ private void extractedInOut(BarSkeleton barSkeleton) { private void extractedActionCommand(BarSkeleton barSkeleton) { // action 命令对象解析器 - var actionCommandParser = new ActionCommandParser(setting) + var actionCommandParser = new ActionCommandParser(this) + .setBarSkeleton(barSkeleton) // 根据 action 类列表,来构建 ActionCommand .buildAction(this.actionControllerClazzList); diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ActionParserContext.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ActionParserContext.java new file mode 100644 index 000000000..363b0cea4 --- /dev/null +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ActionParserContext.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.action.skeleton.core.action.parser; + +import com.iohao.game.action.skeleton.core.ActionCommand; +import com.iohao.game.action.skeleton.core.BarSkeleton; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +/** + * action 构建时的上下文 + * + * @author 渔民小镇 + * @date 2024-04-30 + * @since 21.7 + */ +@Getter +@Setter +@Accessors(chain = true) +@FieldDefaults(level = AccessLevel.PRIVATE) +public final class ActionParserContext { + /** 业务框架 */ + BarSkeleton barSkeleton; + /** action method */ + ActionCommand actionCommand; +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ActionParserListener.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ActionParserListener.java new file mode 100644 index 000000000..dcf4adac8 --- /dev/null +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ActionParserListener.java @@ -0,0 +1,48 @@ +/* + * 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.core.action.parser; + +import com.iohao.game.action.skeleton.core.BarSkeleton; + +/** + * action 构建时的监听器(钩子) + * + * @author 渔民小镇 + * @date 2024-04-30 + * @since 21.7 + */ +public interface ActionParserListener { + /** + * subCmd action callback + *
+     *     每个 action 都会调用一次
+     * 
+ * + * @param context action 构建时的上下文 + */ + void onActionCommand(ActionParserContext context); + + /** + * 在 {@link ActionParserListener#onActionCommand(ActionParserContext)} 之后执行 + * + * @param barSkeleton 业务框架 + */ + default void onAfter(BarSkeleton barSkeleton) { + } +} diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ProtobufActionParserListener.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ProtobufActionParserListener.java new file mode 100644 index 000000000..94211a2d1 --- /dev/null +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ProtobufActionParserListener.java @@ -0,0 +1,111 @@ +/* + * 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.core.action.parser; + +import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass; +import com.iohao.game.action.skeleton.core.ActionCommand; +import com.iohao.game.action.skeleton.core.BarSkeleton; +import com.iohao.game.action.skeleton.protocol.wrapper.*; +import com.iohao.game.common.kit.ProtoKit; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; +import org.jctools.maps.NonBlockingHashSet; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +/** + * 预先生成 proto 协议代理类 + * + * @author 渔民小镇 + * @date 2024-05-01 + * @since 21.7 + */ +@FieldDefaults(level = AccessLevel.PRIVATE) +public final class ProtobufActionParserListener implements ActionParserListener { + final Set> protoSet = new NonBlockingHashSet<>(); + + @Override + public void onActionCommand(ActionParserContext context) { + // 添加了 ProtobufClass 注解的类 + Predicate> protobufClassPredicate = c -> Objects.nonNull(c.getAnnotation(ProtobufClass.class)); + collect(context, protobufClassPredicate, this.protoSet); + } + + static void collect(ActionParserContext context, Predicate> protobufClassPredicate, Set> protoSet) { + // 将 action 的方法参数与返回值添加了 ProtobufClass 注解的类信息收集到 protoSet 中 + ActionCommand actionCommand = context.getActionCommand(); + // action 参数相关 + actionCommand.streamParamInfo() + // 只处理业务参数 + .filter(ActionCommand.ParamInfo::isBizData) + // 得到参数类型 + .map(ActionCommand.ParamInfo::getActualTypeArgumentClazz) + // 协议碎片类型不做处理 + .filter(clazz -> !WrapperKit.isWrapper(clazz)) + // 添加了 ProtobufClass 注解的类 + .filter(protobufClassPredicate) + .forEach(protoSet::add); + + // action 返回值相关 + Optional + .ofNullable(actionCommand.getActionMethodReturnInfo()) + // void 不处理 + .filter(actionMethodReturnInfo -> !actionMethodReturnInfo.isVoid()) + .map(ActionCommand.ActionMethodReturnInfo::getActualTypeArgumentClazz) + // 协议碎片类型不做处理 + .filter(clazz -> !WrapperKit.isWrapper(clazz)) + // 添加了 ProtobufClass 注解的类 + .filter(protobufClassPredicate) + .ifPresent(protoSet::add); + } + + @Override + public void onAfter(BarSkeleton barSkeleton) { + this.protoSet.forEach(ProtoKit::create); + } + + private ProtobufActionParserListener() { + // create a protobuf proxy class + ProtoKit.create(ByteValueList.class); + + ProtoKit.create(IntValue.class); + ProtoKit.create(IntValueList.class); + + ProtoKit.create(BoolValue.class); + ProtoKit.create(BoolValueList.class); + + ProtoKit.create(LongValue.class); + ProtoKit.create(LongValueList.class); + + ProtoKit.create(StringValue.class); + ProtoKit.create(StringValueList.class); + } + + public static ProtobufActionParserListener me() { + return Holder.ME; + } + + /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */ + private static class Holder { + static final ProtobufActionParserListener ME = new ProtobufActionParserListener(); + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ProtobufCheckActionParserListener.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ProtobufCheckActionParserListener.java new file mode 100644 index 000000000..f3ba483fe --- /dev/null +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/action/parser/ProtobufCheckActionParserListener.java @@ -0,0 +1,70 @@ +/* + * 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.core.action.parser; + +import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass; +import com.iohao.game.action.skeleton.core.BarSkeleton; +import lombok.extern.slf4j.Slf4j; +import org.jctools.maps.NonBlockingHashSet; + +import java.util.Set; +import java.util.function.Predicate; + +/** + * proto 协议类型添检测 + * + * @author 渔民小镇 + * @date 2024-05-02 + * @since 21.7 + */ +@Slf4j +public final class ProtobufCheckActionParserListener implements ActionParserListener { + final Set> protoSet = new NonBlockingHashSet<>(); + + @Override + public void onActionCommand(ActionParserContext context) { + // 添加了 ProtobufClass 注解的类 + Predicate> protobufClassPredicate = c -> c.getAnnotation(ProtobufClass.class) == null; + ProtobufActionParserListener.collect(context, protobufClassPredicate, protoSet); + } + + @Override + public void onAfter(BarSkeleton barSkeleton) { + if (this.protoSet.isEmpty()) { + return; + } + + log.error("======== 注意,协议类没有添加 ProtobufClass 注解 ========"); + for (Class protoClass : this.protoSet) { + log.error(protoClass.toString()); + } + } + + private ProtobufCheckActionParserListener() { + } + + public static ProtobufCheckActionParserListener me() { + return Holder.ME; + } + + /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */ + private static class Holder { + static final ProtobufCheckActionParserListener ME = new ProtobufCheckActionParserListener(); + } +} \ No newline at end of file diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocInfo.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocInfo.java index d410de953..e04a48ead 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocInfo.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/doc/DocInfo.java @@ -65,7 +65,7 @@ void add(ActionCommand subBehavior) { // 方法参数 Arrays.stream(subBehavior.getParamInfos()) - .filter(paramInfo -> !paramInfo.isExtension()) + .filter(paramInfo -> !paramInfo.isFlowContext()) .map(this::paramInfoToString) .forEach(methodParam -> paramMap.put("methodParam", methodParam)); diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/MsgExceptionInfo.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/MsgExceptionInfo.java index 5809ec98c..9680bd724 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/MsgExceptionInfo.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/exception/MsgExceptionInfo.java @@ -92,6 +92,27 @@ default void assertNonNull(Object value) throws MsgException { assertTrue(Objects.nonNull(value)); } + /** + * 断言值 value 为 null, 就抛出异常 + * + * @param value 断言值 + * @throws MsgException e + */ + default void assertNullThrows(Object value) throws MsgException { + assertTrueThrows(Objects.isNull(value)); + } + + /** + * 断言值 value 为 null, 就抛出异常 + * + * @param value 断言值 + * @param msg 自定义消息 + * @throws MsgException e + */ + default void assertNullThrows(Object value, String msg) throws MsgException { + assertTrueThrows(Objects.isNull(value), msg); + } + /** * 断言必须是 true, 否则抛出异常 * diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionMethodParamParser.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionMethodParamParser.java index 873710568..2808c425e 100644 --- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionMethodParamParser.java +++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/core/flow/internal/DefaultActionMethodParamParser.java @@ -58,15 +58,15 @@ public Object[] listParam(final FlowContext flowContext) { for (int i = 0; i < len; i++) { // 方法参数信息 ActionCommand.ParamInfo paramInfo = paramInfos[i]; - Class paramClazz = paramInfo.getActualTypeArgumentClazz(); // flow 上下文 - if (FlowContext.class.isAssignableFrom(paramClazz)) { + if (paramInfo.isFlowContext()) { params[i] = flowContext; continue; } // 得到方法参数解析器,把字节解析成 action 业务参数 + Class paramClazz = paramInfo.getActualTypeArgumentClazz(); var methodParser = MethodParsers.getMethodParser(paramClazz); var param = methodParser.parseParam(request.getData(), paramInfo); params[i] = param; 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 index d9029f2e9..fc5a4f228 100644 --- 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 @@ -54,14 +54,26 @@ * .addUserId(List.of(3L, 4L, 5L)) * // 排除一些用户,被排除的用户将不会接收到广播 * .removeUserId(1) - * // 执行广播 + * .removeUserId(4) + * // 执行广播,只有 2、3、5 可以接收到广播 * .execute(); * * // example - 2 * new RangeBroadcast(flowContext) - * // 需要广播的数据 - * .setResponseMessage(cmdInfo, playerReady) - * // // 添加需要接收广播的用户 + * // 需要广播的数据(路由、业务数据) + * .setResponseMessage(cmdInfo, StringValue.of("hello")) + * // 添加需要接收广播的用户 + * .addUserId(1) + * // 执行广播 + * .execute(); + * + * // example - 3 + * BrokerClientContext brokerClient = ...; + * var aggregationContext = brokerClient.getCommunicationAggregationContext(); + * new RangeBroadcast(aggregationContext) + * // 需要广播的数据(路由、业务数据) + * .setResponseMessage(cmdInfo, StringValue.of("hello")) + * // 添加需要接收广播的用户 * .addUserId(1) * // 执行广播 * .execute(); @@ -81,6 +93,8 @@ public class RangeBroadcast { ResponseMessage responseMessage; /** 是否执行发送领域事件操作: true 执行推送操作 */ boolean doSend = true; + /** 检查 userIds ;当值为 true 时,userIds 必须有元素 */ + boolean checkEmptyUser = true; public RangeBroadcast(CommunicationAggregationContext aggregationContext) { Objects.requireNonNull(aggregationContext); @@ -92,7 +106,7 @@ public RangeBroadcast(FlowContext flowContext) { } /** - * 响应消息到远程, 此方法是同步推送 + * 响应消息到远程端(用户、玩家) *
      *     模板方法模式:
      *         在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。
@@ -125,12 +139,8 @@ public final void execute() {
 
         Objects.requireNonNull(this.responseMessage);
 
-        if (CollKit.isEmpty(this.userIds)) {
-            throw new RuntimeException("没有添加消息推送人 " + this.getClass());
-        }
-
-        // 推送响应 (广播消息)给指定的用户列表
-        this.aggregationContext.broadcast(this.responseMessage, this.userIds);
+        // 开始广播
+        this.broadcast();
     }
 
     /**
@@ -155,6 +165,21 @@ protected void logic() {
     protected void trick() {
     }
 
+    /**
+     * 广播数据
+     */
+    protected void broadcast() {
+        boolean emptyUser = CollKit.isEmpty(this.userIds);
+        if (checkEmptyUser && emptyUser) {
+            throw new RuntimeException("没有添加消息推送人 " + this.getClass());
+        }
+
+        // 推送响应(广播消息)给指定的用户列表
+        if (!emptyUser) {
+            this.aggregationContext.broadcast(this.responseMessage, this.userIds);
+        }
+    }
+
     public RangeBroadcast setResponseMessage(ResponseMessage responseMessage) {
         this.responseMessage = responseMessage;
         return this;
@@ -218,4 +243,13 @@ public RangeBroadcast removeUserId(long excludeUserId) {
     protected void disableSend() {
         this.doSend = false;
     }
+
+    public RangeBroadcast disableEmptyUserCheck() {
+        this.checkEmptyUser = false;
+        return this;
+    }
+
+    protected CommunicationAggregationContext getAggregationContext() {
+        return this.aggregationContext;
+    }
 }
diff --git a/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKit.java b/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKit.java
index 3ac94a7c0..efc20d2be 100644
--- a/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKit.java
+++ b/common/common-core/src/main/java/com/iohao/game/action/skeleton/protocol/wrapper/WrapperKit.java
@@ -20,9 +20,11 @@
 
 import com.iohao.game.action.skeleton.core.DataCodecKit;
 import com.iohao.game.common.kit.CollKit;
+import com.sun.jdi.BooleanValue;
 import lombok.experimental.UtilityClass;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * 装箱、拆箱包装工具
@@ -73,4 +75,33 @@ public  ByteValueList ofListByteValue(List values) {
 
         return ByteValueList.of(values.stream().map(DataCodecKit::encode).toList());
     }
+
+    /** 框架支持的协议碎片类型 */
+    final Set> wrapperTypeSet = Set.of(
+            int.class,
+            Integer.class,
+            IntValue.class,
+
+            long.class,
+            Long.class,
+            LongValue.class,
+
+            boolean.class,
+            Boolean.class,
+            BoolValue.class,
+
+            String.class,
+            StringValue.class
+    );
+
+    /**
+     * 框架支持的协议碎片类型
+     *
+     * @param clazz class
+     * @return true 是框架支持的协议碎片类型
+     * @since 21.7
+     */
+    public boolean isWrapper(Class clazz) {
+        return wrapperTypeSet.contains(clazz);
+    }
 }
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/ActionParserListenerTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/ActionParserListenerTest.java
new file mode 100644
index 000000000..2bf99d0bc
--- /dev/null
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/ActionParserListenerTest.java
@@ -0,0 +1,72 @@
+package com.iohao.game.action.skeleton.core;
+
+import com.iohao.game.action.skeleton.core.action.ExampleActionCmd;
+import com.iohao.game.action.skeleton.core.action.pojo.BeeApple;
+import com.iohao.game.action.skeleton.core.action.pojo.DogValid;
+import com.iohao.game.action.skeleton.core.data.TestDataKit;
+import com.iohao.game.action.skeleton.core.flow.FlowContext;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author 渔民小镇
+ * @date 2024-05-01
+ */
+@Slf4j
+public class ActionParserListenerTest {
+    BarSkeleton barSkeleton;
+
+    @Before
+    public void setUp() throws InterruptedException {
+        BarSkeletonBuilder builder = TestDataKit.createBuilder();
+
+        barSkeleton = builder.build();
+
+        // 等待 protobuf proxy class 加载完成。 see JProtobufParserActionListener
+        TimeUnit.MILLISECONDS.sleep(1000);
+    }
+
+    @Test
+    public void onActionCommand() {
+        long l = System.currentTimeMillis();
+
+        extractedBeeHello();
+        extractedBeeHelloDog();
+
+        log.info("l : {}", System.currentTimeMillis() - l);
+    }
+
+    private void extractedBeeHello() {
+        var bizData = new BeeApple();
+        bizData.content = "a";
+
+        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.hello);
+        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, bizData);
+        flowContext.inOutStartTime();
+
+        barSkeleton.handle(flowContext);
+
+        var data = flowContext.getResponse().getData(BeeApple.class);
+
+        Assert.assertEquals(data.content, "a,I'm hello");
+    }
+
+    private void extractedBeeHelloDog() {
+        var bizData = new DogValid();
+        bizData.name = "a";
+
+        CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.hello_dog);
+        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, bizData);
+        flowContext.inOutStartTime();
+
+        barSkeleton.handle(flowContext);
+
+        var data = flowContext.getResponse().getData(DogValid.class);
+
+        Assert.assertEquals(data.name, "a");
+    }
+}
\ No newline at end of file
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/BarSkeletonTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/BarSkeletonTest.java
index f7609e8ca..02f1ad1c4 100644
--- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/BarSkeletonTest.java
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/BarSkeletonTest.java
@@ -21,39 +21,30 @@
 import com.iohao.game.action.skeleton.core.action.ExampleActionCmd;
 import com.iohao.game.action.skeleton.core.action.pojo.BeeApple;
 import com.iohao.game.action.skeleton.core.data.TestDataKit;
-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 org.junit.Before;
 import org.junit.Test;
 
 public class BarSkeletonTest {
+    BarSkeleton barSkeleton;
+
+    @Before
+    public void setUp() {
+        // 构建业务框架
+        barSkeleton = TestDataKit.newBarSkeleton();
+    }
 
     @Test
     public void newBuilder() {
-        // 构建业务框架
-        BarSkeleton barSkeleton = TestDataKit.newBarSkeleton();
 
         // 模拟路由信息
         CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.hello);
 
-        // 模拟请求
-        HeadMetadata headMetadata = new HeadMetadata();
-        headMetadata.setCmdInfo(cmdInfo);
-
-        RequestMessage requestMessage = new RequestMessage();
-        requestMessage.setHeadMetadata(headMetadata);
-
         // 模拟请求数据 (一般由前端传入)
         BeeApple beeApple = new BeeApple();
         beeApple.setContent("hello 塔姆!");
         beeApple.setId(101);
-        // 把模拟请求的数据,放入请求对象中
-
-        byte[] data = DataCodecKit.encode(beeApple);
-        requestMessage.setData(data);
 
-        var flowContext = new FlowContext()
-                .setRequest(requestMessage);
+        var flowContext = TestDataKit.ofFlowContext(cmdInfo, beeApple);
 
         // 业务框架处理用户请求
         barSkeleton.handle(flowContext);
@@ -66,29 +57,16 @@ public void newBuilder() {
 
     @Test
     public void testVoid() {
-        // 构建业务框架
-        BarSkeleton barSkeleton = TestDataKit.newBarSkeleton();
 
         // 模拟路由信息
         CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.test_void);
 
-        // 模拟请求
-        HeadMetadata headMetadata = new HeadMetadata();
-        headMetadata.setCmdInfo(cmdInfo);
-
-        RequestMessage requestMessage = new RequestMessage();
-        requestMessage.setHeadMetadata(headMetadata);
-
         // 模拟请求数据 (一般由前端传入)
         BeeApple beeApple = new BeeApple();
         beeApple.setContent("hello 塔姆!");
         beeApple.setId(1010);
-        // 把模拟请求的数据,放入请求对象中
-        byte[] data = DataCodecKit.encode(beeApple);
-        requestMessage.setData(data);
 
-        var flowContext = new FlowContext()
-                .setRequest(requestMessage);
+        var flowContext = TestDataKit.ofFlowContext(cmdInfo, beeApple);
 
         // 业务框架处理用户请求
         barSkeleton.handle(flowContext);
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380Test.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380Test.java
index 6e7e7d6bb..0a1d9fcb2 100644
--- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380Test.java
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380Test.java
@@ -1,5 +1,5 @@
 /*
- * ioGame 
+ * ioGame
  * Copyright (C) 2021 - present  渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
  * # iohao.com . 渔民小镇
  *
@@ -22,7 +22,8 @@
 import com.iohao.game.action.skeleton.core.action.pojo.DogValid;
 import com.iohao.game.action.skeleton.core.data.TestDataKit;
 import com.iohao.game.action.skeleton.core.flow.FlowContext;
-import com.iohao.game.action.skeleton.protocol.RequestMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -30,6 +31,7 @@
  * @author 渔民小镇
  * @date 2022-07-09
  */
+@Slf4j
 public class JSR380Test {
 
     BarSkeleton barSkeleton;
@@ -48,39 +50,10 @@ public void jsr380() {
 
         CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.jsr380);
 
-        RequestMessage requestMessage = TestDataKit.createRequestMessage(cmdInfo);
-        requestMessage.setData(dogValid);
-
-        FlowContext flowContext = new FlowContext();
-        flowContext.setRequest(requestMessage);
+        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, dogValid);
 
         barSkeleton.handle(flowContext);
-    }
 
-
-//    @Test
-//    public void name() {
-//        DogValid dogValid = new DogValid();
-//        dogValid.name = "abc";
-//        Validator validator = ValidatorKit.getValidator();
-//
-//
-//        BeanDescriptor constraintsForClass = validator.getConstraintsForClass(DogValid.class);
-//        Set constrainedProperties = constraintsForClass.getConstrainedProperties();
-//        log.info("c : {}", constraintsForClass);
-//
-//        Set> validate = validator.validate(dogValid);
-//
-//
-//        log.info("{}", validate.size());
-//
-//        for (ConstraintViolation violation : validate) {
-//            log.info("{}", validate);
-//            String message = violation.getMessage();
-//            Path propertyPath = violation.getPropertyPath();
-//
-//            log.info("message {}, path: {}", message, propertyPath.toString());
-//        }
-//
-//    }
+        Assert.assertTrue(flowContext.getResponse().hasError());
+    }
 }
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380ValidatedGroupTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380ValidatedGroupTest.java
index b21f5c203..aeaf13adf 100644
--- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380ValidatedGroupTest.java
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/JSR380ValidatedGroupTest.java
@@ -22,7 +22,7 @@
 import com.iohao.game.action.skeleton.core.action.pojo.BirdValid;
 import com.iohao.game.action.skeleton.core.data.TestDataKit;
 import com.iohao.game.action.skeleton.core.flow.FlowContext;
-import com.iohao.game.action.skeleton.protocol.RequestMessage;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -44,31 +44,24 @@ public void setUp() {
     @Test
     public void updateGroupTest() {
         BirdValid birdValid = new BirdValid();
-
         CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.validated_group_update);
 
-        RequestMessage requestMessage = TestDataKit.createRequestMessage(cmdInfo);
-        requestMessage.setData(birdValid);
-
-        FlowContext flowContext = new FlowContext();
-        flowContext.setRequest(requestMessage);
+        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, birdValid);
 
         barSkeleton.handle(flowContext);
+
+        Assert.assertTrue(flowContext.getResponse().hasError());
     }
 
     @Test
     public void createGroupTest() {
         BirdValid birdValid = new BirdValid();
-
         CmdInfo cmdInfo = CmdInfo.of(ExampleActionCmd.BeeActionCmd.cmd, ExampleActionCmd.BeeActionCmd.validated_group_create);
 
-        RequestMessage requestMessage = TestDataKit.createRequestMessage(cmdInfo);
-        requestMessage.setData(birdValid);
-
-        FlowContext flowContext = new FlowContext();
-        flowContext.setRequest(requestMessage);
+        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, birdValid);
 
         barSkeleton.handle(flowContext);
-    }
 
+        Assert.assertTrue(flowContext.getResponse().hasError());
+    }
 }
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/SimpleWrapperActionTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/SimpleWrapperActionTest.java
new file mode 100644
index 000000000..099a0f3ac
--- /dev/null
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/SimpleWrapperActionTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.core;
+
+import com.iohao.game.action.skeleton.core.action.SimpleWrapperAction;
+import com.iohao.game.action.skeleton.core.data.TestDataKit;
+import com.iohao.game.action.skeleton.core.flow.FlowContext;
+import com.iohao.game.action.skeleton.protocol.wrapper.IntValue;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.iohao.game.action.skeleton.core.action.ExampleActionCmd.SimpleWrapperActionActionCmd.*;
+
+/**
+ * @author 渔民小镇
+ * @date 2024-05-02
+ */
+@Slf4j
+public class SimpleWrapperActionTest {
+
+    BarSkeleton barSkeleton;
+
+    @Before
+    public void setUp() {
+        barSkeleton = TestDataKit.createBuilder(SimpleWrapperAction.class::equals).build();
+    }
+
+    @Test
+    public void testInt() {
+        CmdInfo cmdInfo = CmdInfo.of(cmd, testInt);
+
+        FlowContext flowContext = TestDataKit.ofFlowContext(cmdInfo, IntValue.of(100));
+
+        barSkeleton.handle(flowContext);
+    }
+}
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperIntTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperIntTest.java
index 776b33297..022bfd4e2 100644
--- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperIntTest.java
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperIntTest.java
@@ -1,5 +1,5 @@
 /*
- * ioGame 
+ * ioGame
  * Copyright (C) 2021 - present  渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
  * # iohao.com . 渔民小镇
  *
@@ -27,6 +27,8 @@
 import lombok.Getter;
 import lombok.Setter;
 import lombok.experimental.FieldDefaults;
+import org.junit.Before;
+import org.junit.Test;
 
 import java.util.ArrayList;
 
@@ -51,24 +53,21 @@ private FlowContext createIntValueFlowContext(int subCmd) {
         IntValue intValue = new IntValue();
         intValue.value = 100;
 
-        RequestMessage requestMessage = TestDataKit.createRequestMessage(cmdInfo);
-        requestMessage.setData(intValue);
-
-        return new FlowContext()
-                .setRequest(requestMessage);
+        return TestDataKit.ofFlowContext(cmdInfo, intValue);
     }
 
     BarSkeleton barSkeleton;
 
-    //    @Before
+    @Before
     public void setUp() {
         barSkeleton = TestDataKit.newBarSkeleton();
     }
 
 
-    //    @Test
+    @Test
     public void intValue1() {
         FlowContext flowContext;
+
         flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.intValue2Void);
         // 业务框架处理用户请求
         barSkeleton.handle(flowContext);
@@ -119,7 +118,7 @@ public void intValue2() {
         barSkeleton.handle(flowContext);
     }
 
-    //    @Test
+    @Test
     public void integerValue() {
         FlowContext flowContext;
         flowContext = this.createIntValueFlowContext(WrapperIntActionCmd.integer2Void);
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperLongTest.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperLongTest.java
index 53e1834b7..374c38f19 100644
--- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperLongTest.java
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/WrapperLongTest.java
@@ -20,12 +20,13 @@
 
 import com.iohao.game.action.skeleton.core.data.TestDataKit;
 import com.iohao.game.action.skeleton.core.flow.FlowContext;
-import com.iohao.game.action.skeleton.protocol.RequestMessage;
 import com.iohao.game.action.skeleton.protocol.wrapper.LongValue;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.experimental.FieldDefaults;
+import org.junit.Before;
+import org.junit.Test;
 
 import static com.iohao.game.action.skeleton.core.action.ExampleActionCmd.WrapperLongActionCmd;
 
@@ -48,21 +49,17 @@ private FlowContext createLongValueFlowContext(int subCmd) {
         LongValue longValue = new LongValue();
         longValue.value = 100;
 
-        RequestMessage requestMessage = TestDataKit.createRequestMessage(cmdInfo);
-        requestMessage.setData(longValue);
-
-        return new FlowContext()
-                .setRequest(requestMessage);
+        return TestDataKit.ofFlowContext(cmdInfo, longValue);
     }
 
     BarSkeleton barSkeleton;
 
-    //    @Before
+    @Before
     public void setUp() {
         barSkeleton = TestDataKit.newBarSkeleton();
     }
 
-    //    @Test
+    @Test
     public void longValue1() {
         FlowContext flowContext = null;
         flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longValue2Void);
@@ -82,7 +79,7 @@ public void longValue1() {
         barSkeleton.handle(flowContext);
     }
 
-    //    @Test
+    @Test
     public void longValue2() {
         FlowContext flowContext = null;
         flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.long2Void);
@@ -102,9 +99,9 @@ public void longValue2() {
         barSkeleton.handle(flowContext);
     }
 
-    //    @Test
+    @Test
     public void longerValue3() {
-        FlowContext flowContext = null;
+        FlowContext flowContext;
         flowContext = this.createLongValueFlowContext(WrapperLongActionCmd.longer2Void);
         // 业务框架处理用户请求
         barSkeleton.handle(flowContext);
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 e9bda5d1a..6f2b8ee61 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
@@ -62,6 +62,12 @@ public void thatVoid(BeeApple beeApple) {
         that.setContent(beeApple.content + ",I'm thatVoid");
     }
 
+    @ActionMethod(ExampleActionCmd.BeeActionCmd.hello_dog)
+    public DogValid helloDog(DogValid dogValid) {
+        log.info("dogValid : {}", dogValid);
+        return dogValid;
+    }
+
     @ActionMethod(ExampleActionCmd.BeeActionCmd.jsr380)
     public void jsr380(DogValid dogValid) {
         log.info("{}", dogValid);
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/ExampleActionCmd.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/ExampleActionCmd.java
index c4f8fa127..f6338eebd 100644
--- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/ExampleActionCmd.java
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/ExampleActionCmd.java
@@ -35,6 +35,7 @@ interface BeeActionCmd {
         int jsr380 = 4;
         int validated_group_update = 5;
         int validated_group_create = 6;
+        int hello_dog = 7;
     }
 
 
@@ -84,4 +85,9 @@ interface WrapperLongActionCmd {
         int longer2LongValue = 10;
         int longer2LongList = 11;
     }
+
+    interface SimpleWrapperActionActionCmd {
+        int cmd = 13;
+        int testInt = 0;
+    }
 }
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/SimpleWrapperAction.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/SimpleWrapperAction.java
new file mode 100644
index 000000000..89a6008c9
--- /dev/null
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/action/SimpleWrapperAction.java
@@ -0,0 +1,36 @@
+/*
+ * 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.core.action;
+
+import com.iohao.game.action.skeleton.annotation.ActionController;
+import com.iohao.game.action.skeleton.annotation.ActionMethod;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author 渔民小镇
+ * @date 2024-05-02
+ */
+@Slf4j
+@ActionController(ExampleActionCmd.SimpleWrapperActionActionCmd.cmd)
+public class SimpleWrapperAction {
+    @ActionMethod(ExampleActionCmd.SimpleWrapperActionActionCmd.testInt)
+    public void testInt(int age) {
+        System.out.println(age);
+    }
+}
\ No newline at end of file
diff --git a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/data/TestDataKit.java b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/data/TestDataKit.java
index 8daaf2ea1..f054b66dd 100644
--- a/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/data/TestDataKit.java
+++ b/common/common-core/src/test/java/com/iohao/game/action/skeleton/core/data/TestDataKit.java
@@ -19,63 +19,79 @@
 package com.iohao.game.action.skeleton.core.data;
 
 import com.iohao.game.action.skeleton.annotation.ActionController;
-import com.iohao.game.action.skeleton.core.BarSkeleton;
-import com.iohao.game.action.skeleton.core.BarSkeletonBuilder;
-import com.iohao.game.action.skeleton.core.CmdInfo;
+import com.iohao.game.action.skeleton.core.*;
 import com.iohao.game.action.skeleton.core.action.BeeAction;
+import com.iohao.game.action.skeleton.core.flow.FlowContext;
 import com.iohao.game.action.skeleton.core.flow.internal.DebugInOut;
-import com.iohao.game.action.skeleton.protocol.HeadMetadata;
 import com.iohao.game.action.skeleton.protocol.RequestMessage;
 import com.iohao.game.common.kit.ClassScanner;
 import lombok.experimental.UtilityClass;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Predicate;
 
 @UtilityClass
 public class TestDataKit {
 
     public BarSkeleton newBarSkeleton() {
-        BarSkeletonBuilder builder = createBuilder();
-        builder.setActionAfter(flowContext -> {
-
-        });
-        return builder.build();
+        return createBuilder().build();
     }
 
-    public BarSkeletonBuilder createBuilder() {
+    public BarSkeletonBuilder createBuilder(Predicate> appendPredicateFilter) {
         // 尽量做到所有操作是可插拔的. 详细配置 see BarSkeletonBuilder.build
         BarSkeletonBuilder builder = BarSkeleton.newBuilder();
 
         builder.addInOut(new DebugInOut());
 
-        // 添加(请求响应)处理类. 用户可以定义自己的业务控制器 - 这里推荐实现扫描包的形式添加 tcp 处理类
-//        builder
-//                .addActionController(BeeAction.class)
-//                .addActionController(BirdAction.class)
-//        ;
+        builder.setActionAfter(flowContext -> System.out.println());
 
-        Predicate> predicateFilter = (clazz) -> clazz.getAnnotation(ActionController.class) != null;
-
-        String packagePath = BeeAction.class.getPackageName();
-
-        ClassScanner classScanner = new ClassScanner(packagePath, predicateFilter);
-        List> classList = classScanner.listScan();
+        List> classList = getClasses(appendPredicateFilter);
 
         classList.forEach(builder::addActionController);
 
+        BarSkeletonSetting setting = builder.getSetting();
+        setting.setPrintHandler(false);
+        setting.setPrintInout(false);
+        setting.setPrintDataCodec(false);
+        setting.setPrintRunners(false);
+        setting.setPrintHandler(false);
 
         return builder;
     }
 
-    public RequestMessage createRequestMessage(CmdInfo cmdInfo) {
-        // 模拟请求
-        HeadMetadata headMetadata = new HeadMetadata();
-        headMetadata.setCmdInfo(cmdInfo);
+    private List> getClasses(Predicate> appendPredicateFilter) {
+        Predicate> predicateFilter = (clazz) -> {
+            if (clazz.getAnnotation(ActionController.class) == null) {
+                return false;
+            }
+
+            if (Objects.nonNull(appendPredicateFilter)) {
+                if (appendPredicateFilter.test(clazz)) {
+                    return true;
+                }
+
+                return false;
+            }
+
+            return true;
+        };
+
+        String packagePath = BeeAction.class.getPackageName();
+        ClassScanner classScanner = new ClassScanner(packagePath, predicateFilter);
+        return classScanner.listScan();
+    }
+
+    public BarSkeletonBuilder createBuilder() {
+        return createBuilder(null);
+    }
+
+    public FlowContext ofFlowContext(CmdInfo cmdInfo, Object data) {
+        RequestMessage requestMessage = BarMessageKit.createRequestMessage(cmdInfo, data);
 
-        RequestMessage requestMessage = new RequestMessage();
-        requestMessage.setHeadMetadata(headMetadata);
+        FlowContext flowContext = new FlowContext();
+        flowContext.setRequest(requestMessage);
 
-        return requestMessage;
+        return flowContext;
     }
 }
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 6e44bbb08..13581dd57 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
@@ -1,6 +1,7 @@
 package com.iohao.game.action.skeleton.protocol.wrapper;
 
 import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
 import org.junit.Test;
 
 /**
@@ -19,4 +20,22 @@ public void of() {
         Object of1 = WrapperKit.of(intV);
         log.info("of1 : {}", of1);
     }
+
+    @Test
+    public void isWrapper() {
+        Assert.assertTrue(WrapperKit.isWrapper(int.class));
+        Assert.assertTrue(WrapperKit.isWrapper(Integer.class));
+        Assert.assertTrue(WrapperKit.isWrapper(IntValue.class));
+
+        Assert.assertTrue(WrapperKit.isWrapper(long.class));
+        Assert.assertTrue(WrapperKit.isWrapper(Long.class));
+        Assert.assertTrue(WrapperKit.isWrapper(LongValue.class));
+
+        Assert.assertTrue(WrapperKit.isWrapper(boolean.class));
+        Assert.assertTrue(WrapperKit.isWrapper(Boolean.class));
+        Assert.assertTrue(WrapperKit.isWrapper(BoolValue.class));
+
+        Assert.assertTrue(WrapperKit.isWrapper(String.class));
+        Assert.assertTrue(WrapperKit.isWrapper(StringValue.class));
+    }
 }
\ No newline at end of file
diff --git a/common/common-core/src/test/resources/logback.xml b/common/common-core/src/test/resources/logback.xml
new file mode 100644
index 000000000..002a55233
--- /dev/null
+++ b/common/common-core/src/test/resources/logback.xml
@@ -0,0 +1,114 @@
+
+
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+     
+
+    
+    
+    
+    
+        
+            
+            
+            ${log.pattern}
+            utf8
+        
+    
+
+    
+    
+        ${log.base}/${log.moduleName}.log
+        
+        
+            ${log.base}/archive/${log.moduleName}_all_%d{yyyy-MM-dd}.%i.log.zip
+            
+            
+                ${log.max.size}
+            
+        
+        
+        
+            %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{80}.%method:%L -%msg%n
+        
+    
+
+    
+    
+        ${log.base}/${log.moduleName}_other.log
+        
+            ${log.base}/archive/${log.moduleName}_other_%d{yyyy-MM-dd}.%i.log.zip
+            
+            
+                ${log.max.size}
+            
+        
+        
+            %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{56}.%method:%L -%msg%n
+        
+    
+
+    
+    
+        ${log.base}/${log.moduleName}_err.log
+        
+            ${log.base}/archive/${log.moduleName}_err_%d{yyyy-MM-dd}.%i.log.zip
+            
+            
+                ${log.max.size}
+            
+        
+        
+            %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{56}.%method:%L - %msg%n
+        
+        
+        
+            ERROR
+            ACCEPT
+            DENY
+        
+    
+
+    
+    
+    
+    
+        0
+        256
+        true
+        
+    
+
+      
+    
+        0
+        256
+        true
+        
+    
+
+    
+    
+        
+        
+        
+        
+    
+
+    
+    
+         
+        
+        
+    
+
diff --git a/common/common-kit/pom.xml b/common/common-kit/pom.xml
index 6e5fd8635..2124a9e75 100644
--- a/common/common-kit/pom.xml
+++ b/common/common-kit/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.6
+        21.7
         ../../pom.xml
     
     4.0.0
diff --git a/common/common-kit/src/main/java/com/iohao/game/common/kit/ProtoKit.java b/common/common-kit/src/main/java/com/iohao/game/common/kit/ProtoKit.java
index ea790c8ec..5a203d367 100644
--- a/common/common-kit/src/main/java/com/iohao/game/common/kit/ProtoKit.java
+++ b/common/common-kit/src/main/java/com/iohao/game/common/kit/ProtoKit.java
@@ -22,6 +22,7 @@
 import com.baidu.bjf.remoting.protobuf.ProtobufProxy;
 import com.iohao.game.common.consts.CommonConst;
 import com.iohao.game.common.consts.IoGameLogName;
+import com.iohao.game.common.kit.concurrent.TaskKit;
 import lombok.experimental.UtilityClass;
 import lombok.extern.slf4j.Slf4j;
 
@@ -83,4 +84,11 @@ public  T parseProtoByte(byte[] data, Class clazz) {
 
         return null;
     }
+
+    public void create(Class clazz) {
+        TaskKit.executeVirtual(() -> {
+            // create a protobuf proxy class
+            ProtobufProxy.create(clazz);
+        });
+    }
 }
diff --git a/common/common-micro-kit/pom.xml b/common/common-micro-kit/pom.xml
index 6aa6098e6..e322bfcc1 100644
--- a/common/common-micro-kit/pom.xml
+++ b/common/common-micro-kit/pom.xml
@@ -6,7 +6,7 @@
     
         com.iohao.game
         ioGame
-        21.6
+        21.7
         ../../pom.xml
     
 
diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/RuntimeKit.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/RuntimeKit.java
new file mode 100644
index 000000000..e03e66688
--- /dev/null
+++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/RuntimeKit.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;
+
+import lombok.experimental.UtilityClass;
+
+/**
+ * Runtime 相关工具
+ *
+ * @author 渔民小镇
+ * @date 2024-05-01
+ * @since 21.7
+ */
+@UtilityClass
+public class RuntimeKit {
+    /**
+     * 默认使用 Runtime.getRuntime().availableProcessors()。
+     * 如果有一些特殊环境需要模拟的,可以设置该变量。
+     */
+    public int availableProcessors = Runtime.getRuntime().availableProcessors();
+}
diff --git a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/UserThreadExecutorRegion.java b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/UserThreadExecutorRegion.java
index da619e4a9..5a2b835f3 100644
--- a/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/UserThreadExecutorRegion.java
+++ b/common/common-micro-kit/src/main/java/com/iohao/game/common/kit/concurrent/executor/UserThreadExecutorRegion.java
@@ -18,6 +18,8 @@
  */
 package com.iohao.game.common.kit.concurrent.executor;
 
+import com.iohao.game.common.kit.RuntimeKit;
+
 /**
  * 用户线程执行器管理域
  * 
@@ -58,7 +60,7 @@ public ThreadExecutor getThreadExecutor(long userId) {
     }
 
     static int availableProcessors2n() {
-        int n = Runtime.getRuntime().availableProcessors();
+        int n = RuntimeKit.availableProcessors;
         n |= (n >> 1);
         n |= (n >> 2);
         n |= (n >> 4);
diff --git a/common/common-validation/pom.xml b/common/common-validation/pom.xml
index adc3c337d..838191255 100644
--- a/common/common-validation/pom.xml
+++ b/common/common-validation/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.6
+        21.7
         ../../pom.xml
     
     4.0.0
diff --git a/external/external-core/pom.xml b/external/external-core/pom.xml
index 0e21f3998..5533b5448 100644
--- a/external/external-core/pom.xml
+++ b/external/external-core/pom.xml
@@ -6,7 +6,7 @@
     
         com.iohao.game
         ioGame
-        21.6
+        21.7
         ../../pom.xml
     
 
diff --git a/external/external-core/src/main/java/com/iohao/game/external/core/config/ExternalGlobalConfig.java b/external/external-core/src/main/java/com/iohao/game/external/core/config/ExternalGlobalConfig.java
index 8ec4999a8..1255b8034 100644
--- a/external/external-core/src/main/java/com/iohao/game/external/core/config/ExternalGlobalConfig.java
+++ b/external/external-core/src/main/java/com/iohao/game/external/core/config/ExternalGlobalConfig.java
@@ -42,7 +42,8 @@ public class ExternalGlobalConfig {
     public AccessAuthenticationHook accessAuthenticationHook = new DefaultAccessAuthenticationHook();
     /** 游戏对外服路由缓存 */
     public ExternalCmdCache externalCmdCache;
-
+    /** true 表示开启简单日志打印 netty handler. see SimpleLoggerHandler */
+    public boolean enableLoggerHandler = true;
     /**
      * 协议开关,用于一些协议级别的开关控制,比如 安全加密校验等。 : 0 不校验
      * 
diff --git a/external/external-netty/pom.xml b/external/external-netty/pom.xml
index 58f5a07e9..2eb6543d6 100644
--- a/external/external-netty/pom.xml
+++ b/external/external-netty/pom.xml
@@ -6,7 +6,7 @@
     
         com.iohao.game
         ioGame
-        21.6
+        21.7
         ../../pom.xml
     
 
diff --git a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/DefaultExternalCore.java b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/DefaultExternalCore.java
index 6242f8e32..131447814 100644
--- a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/DefaultExternalCore.java
+++ b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/DefaultExternalCore.java
@@ -18,13 +18,17 @@
  */
 package com.iohao.game.external.core.netty;
 
+import com.iohao.game.action.skeleton.core.DataCodecKit;
+import com.iohao.game.action.skeleton.core.codec.ProtoDataCodec;
 import com.iohao.game.bolt.broker.core.common.IoGameGlobalConfig;
 import com.iohao.game.common.consts.IoGameLogName;
 import com.iohao.game.common.kit.PresentKit;
+import com.iohao.game.common.kit.ProtoKit;
 import com.iohao.game.external.core.ExternalCore;
 import com.iohao.game.external.core.config.ExternalJoinEnum;
 import com.iohao.game.external.core.hook.UserHook;
 import com.iohao.game.external.core.hook.internal.DefaultUserHook;
+import com.iohao.game.external.core.message.ExternalMessage;
 import com.iohao.game.external.core.micro.MicroBootstrap;
 import com.iohao.game.external.core.micro.join.ExternalJoinSelector;
 import com.iohao.game.external.core.micro.join.ExternalJoinSelectors;
@@ -102,6 +106,11 @@ private void defaultSetting() {
 
         // 当前游戏对外服所使用的连接方式
         userSessions.option(UserSessionOption.externalJoin, joinEnum);
+
+        // ================== 其他 ==================
+        if (DataCodecKit.getDataCodec() instanceof ProtoDataCodec) {
+            ProtoKit.create(ExternalMessage.class);
+        }
     }
 
     private void aware() {
diff --git a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SimpleLoggerHandler.java b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SimpleLoggerHandler.java
new file mode 100644
index 000000000..ea95c485a
--- /dev/null
+++ b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SimpleLoggerHandler.java
@@ -0,0 +1,60 @@
+/*
+ * 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.netty.handler;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 简单日志打印,通常是不活跃的连接、触发异常的连接
+ *
+ * @author 渔民小镇
+ * @date 2024-05-01
+ * @since 21.7
+ */
+@Slf4j
+@ChannelHandler.Sharable
+public final class SimpleLoggerHandler extends ChannelInboundHandlerAdapter {
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+        log.info("channelInactive channel.remoteAddress() : {}", ctx.channel().remoteAddress());
+        super.channelInactive(ctx);
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        log.error(cause.getMessage(), cause);
+        super.exceptionCaught(ctx, cause);
+    }
+
+    private SimpleLoggerHandler() {
+    }
+
+    public static SimpleLoggerHandler me() {
+        return Holder.ME;
+    }
+
+    /** 通过 JVM 的类加载机制, 保证只加载一次 (singleton) */
+    private static class Holder {
+        static final SimpleLoggerHandler ME = new SimpleLoggerHandler();
+    }
+}
diff --git a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketUserSessionHandler.java b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketUserSessionHandler.java
index 0b1ea8c81..b6cf0457d 100644
--- a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketUserSessionHandler.java
+++ b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/handler/SocketUserSessionHandler.java
@@ -51,7 +51,7 @@ public void setUserSessions(UserSessions userSessions) {
     }
 
     @Override
-    public void channelActive(ChannelHandlerContext ctx) {
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
         BrokerClientModuleMessage moduleMessage = brokerClient.getBrokerClientModuleMessage();
         int idHash = moduleMessage.getIdHash();
 
@@ -59,20 +59,24 @@ public void channelActive(ChannelHandlerContext ctx) {
         SocketUserSession userSession = userSessions.add(ctx);
         userSession.setExternalClientId(idHash);
 
-        ctx.fireChannelActive();
+        super.channelActive(ctx);
     }
 
     @Override
-    public void channelInactive(ChannelHandlerContext ctx) {
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
         // 从 session 管理中移除
         var userSession = this.userSessions.getUserSession(ctx);
         this.userSessions.removeUserSession(userSession);
+
+        super.channelInactive(ctx);
     }
 
     @Override
-    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
         // 从 session 管理中移除
         var userSession = this.userSessions.getUserSession(ctx);
         this.userSessions.removeUserSession(userSession);
+
+        super.exceptionCaught(ctx, cause);
     }
 }
diff --git a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/SocketMicroBootstrapFlow.java b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/SocketMicroBootstrapFlow.java
index 02ddacfdf..bdf9df70e 100644
--- a/external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/SocketMicroBootstrapFlow.java
+++ b/external/external-netty/src/main/java/com/iohao/game/external/core/netty/micro/SocketMicroBootstrapFlow.java
@@ -79,6 +79,10 @@ public void pipelineIdle(PipelineContext context) {
 
     @Override
     public void pipelineCustom(PipelineContext context) {
+        // 日志打印(异常时)
+        if (ExternalGlobalConfig.enableLoggerHandler) {
+            context.addLast("SimpleLoggerHandler", SimpleLoggerHandler.me());
+        }
 
         // 路由存在检测
         context.addLast("CmdCheckHandler", CmdCheckHandler.me());
diff --git a/net-bolt/bolt-broker-server/pom.xml b/net-bolt/bolt-broker-server/pom.xml
index e3de352e1..49b940208 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.6
+        21.7
         ../../pom.xml
     
     4.0.0
diff --git a/net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/BalancedManager.java b/net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/BalancedManager.java
index fb8330c1a..56b8e93f7 100644
--- a/net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/BalancedManager.java
+++ b/net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/balanced/BalancedManager.java
@@ -1,5 +1,5 @@
 /*
- * ioGame 
+ * ioGame
  * Copyright (C) 2021 - present  渔民小镇 (262610965@qq.com、luoyizhu@gmail.com) . All Rights Reserved.
  * # iohao.com . 渔民小镇
  *
@@ -29,10 +29,7 @@
 import lombok.experimental.FieldDefaults;
 import org.jctools.maps.NonBlockingHashMap;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 负载管理器
@@ -96,6 +93,10 @@ public void register(BrokerClientModuleMessage brokerClientModuleMessage) {
     public BrokerClientProxy remove(String address) {
         BrokerClientProxy brokerClientProxy = this.refMap.get(address);
 
+        if (Objects.isNull(brokerClientProxy)) {
+            return null;
+        }
+
         BrokerClientType brokerClientType = brokerClientProxy.getBrokerClientType();
         var loadBalanced = this.getRegionLoadBalanced(brokerClientType);
 
diff --git a/net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ConnectionCloseEventBrokerProcessor.java b/net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ConnectionCloseEventBrokerProcessor.java
index 8cda05221..d1630f46f 100644
--- a/net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ConnectionCloseEventBrokerProcessor.java
+++ b/net-bolt/bolt-broker-server/src/main/java/com/iohao/game/bolt/broker/server/processor/ConnectionCloseEventBrokerProcessor.java
@@ -54,22 +54,21 @@ public final class ConnectionCloseEventBrokerProcessor implements ConnectionEven
 
     @Override
     public void onEvent(String remoteAddress, Connection connection) {
-        if (IoGameGlobalConfig.openLog) {
-            log.info("Broker ConnectionEventType:【{}】 remoteAddress:【{}】,Connection:【{}】",
-                    ConnectionEventType.CLOSE, remoteAddress, connection
-            );
-        }
-
         Objects.requireNonNull(connection);
 
         BalancedManager balancedManager = this.brokerServer.getBalancedManager();
         // 当前下线的逻辑服
         BrokerClientProxy brokerClientProxy = balancedManager.remove(remoteAddress);
 
-        extractedPrint(remoteAddress, brokerClientProxy);
-        BrokerPrintKit.print(this.brokerServer);
-
         Optional.ofNullable(brokerClientProxy).ifPresent(proxy -> {
+            if (IoGameGlobalConfig.openLog) {
+                log.info("Broker ConnectionEventType:【{}】,remoteAddress:【{}】,brokerClientProxy:【{}】,Connection:【{}】",
+                        ConnectionEventType.CLOSE, remoteAddress, brokerClientProxy, connection
+                );
+
+                BrokerPrintKit.print(this.brokerServer);
+            }
+
             String id = proxy.getId();
             BrokerClientModuleMessage moduleMessage = this.brokerClientModules.removeById(id);
 
@@ -83,12 +82,4 @@ public void onEvent(String remoteAddress, Connection connection) {
             LineKit.offline(context);
         });
     }
-
-    private void extractedPrint(String remoteAddress, BrokerClientProxy brokerClientProxy) {
-        if (IoGameGlobalConfig.openLog) {
-            log.info("Broker ConnectionEventType:【{}】 remoteAddress:【{}】,brokerClientProxy:【{}】",
-                    ConnectionEventType.CLOSE, remoteAddress, brokerClientProxy
-            );
-        }
-    }
 }
diff --git a/net-bolt/bolt-client/pom.xml b/net-bolt/bolt-client/pom.xml
index 709775aa5..9238ea664 100644
--- a/net-bolt/bolt-client/pom.xml
+++ b/net-bolt/bolt-client/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.6
+        21.7
         ../../pom.xml
     
     4.0.0
diff --git a/net-bolt/bolt-core/pom.xml b/net-bolt/bolt-core/pom.xml
index 4054197df..f2dc2e337 100644
--- a/net-bolt/bolt-core/pom.xml
+++ b/net-bolt/bolt-core/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.6
+        21.7
         ../../pom.xml
     
     4.0.0
diff --git a/net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/DefaultUserProcessorExecutorStrategy.java b/net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/DefaultUserProcessorExecutorStrategy.java
index 6f8c2d8e7..325001014 100644
--- a/net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/DefaultUserProcessorExecutorStrategy.java
+++ b/net-bolt/bolt-core/src/main/java/com/iohao/game/bolt/broker/core/common/DefaultUserProcessorExecutorStrategy.java
@@ -20,6 +20,7 @@
 
 import com.iohao.game.bolt.broker.core.aware.UserProcessorExecutorAware;
 import com.iohao.game.common.kit.MoreKit;
+import com.iohao.game.common.kit.RuntimeKit;
 import com.iohao.game.common.kit.concurrent.DaemonThreadFactory;
 import lombok.extern.slf4j.Slf4j;
 import org.jctools.maps.NonBlockingHashMap;
@@ -70,7 +71,7 @@ Executor ofExecutor(String name) {
         Executor executor = this.executorMap.get(name);
 
         if (Objects.isNull(executor)) {
-            int corePoolSize = Runtime.getRuntime().availableProcessors();
+            int corePoolSize = RuntimeKit.availableProcessors;
             var tempExecutor = createExecutor(name, corePoolSize, corePoolSize);
             executor = MoreKit.firstNonNull(this.executorMap.putIfAbsent(name, tempExecutor), tempExecutor);
 
diff --git a/pom.xml b/pom.xml
index 189b3a1b9..ddec8de1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     com.iohao.game
     ioGame
-    21.6
+    21.7
     ioGame
     
         生产资料公有制。
diff --git a/run-one/run-one-netty/pom.xml b/run-one/run-one-netty/pom.xml
index 7ce67d9a5..b614222ca 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.6
+        21.7
         ../../pom.xml
     
 
diff --git a/widget/light-client/pom.xml b/widget/light-client/pom.xml
index 6ce95fea5..a319ce5f3 100644
--- a/widget/light-client/pom.xml
+++ b/widget/light-client/pom.xml
@@ -6,7 +6,7 @@
     
         com.iohao.game
         ioGame
-        21.6
+        21.7
         ../../pom.xml
     
 
diff --git a/widget/light-domain-event/pom.xml b/widget/light-domain-event/pom.xml
index 5e1293aab..f827430bf 100644
--- a/widget/light-domain-event/pom.xml
+++ b/widget/light-domain-event/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.6
+        21.7
         ../../pom.xml
     
     4.0.0
diff --git a/widget/light-game-room/pom.xml b/widget/light-game-room/pom.xml
index 77ee41fa6..a57f36ddc 100644
--- a/widget/light-game-room/pom.xml
+++ b/widget/light-game-room/pom.xml
@@ -5,7 +5,7 @@
     
         ioGame
         com.iohao.game
-        21.6
+        21.7
         ../../pom.xml
     
     4.0.0
@@ -19,14 +19,6 @@
             ${project.parent.version}
             provided
         
-
-        
-            com.iohao.game
-            light-domain-event
-            ${project.parent.version}
-            provided
-        
-
     
 
 
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
deleted file mode 100644
index d5e6e2e46..000000000
--- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractFlowContextSend.java
+++ /dev/null
@@ -1,205 +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.widget.light.room;
-
-import com.iohao.game.action.skeleton.core.ActionSend;
-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;
-import com.iohao.game.action.skeleton.protocol.ResponseMessage;
-import com.iohao.game.widget.light.domain.event.message.Eo;
-import com.iohao.game.widget.light.domain.event.message.Topic;
-import lombok.AccessLevel;
-import lombok.experimental.Accessors;
-import lombok.experimental.FieldDefaults;
-import lombok.extern.slf4j.Slf4j;
-import org.jctools.maps.NonBlockingHashSet;
-
-import java.util.Collection;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * 抽象响应消息 推送
- *
- * @author 渔民小镇
- * @date 2022-03-31
- */
-@Slf4j
-@Accessors(chain = true)
-@FieldDefaults(level = AccessLevel.PRIVATE)
-public abstract class AbstractFlowContextSend implements Topic, Eo, ActionSend {
-
-    /** 需要推送的用户id列表 */
-    final Set userIds = new NonBlockingHashSet<>();
-    final CommunicationAggregationContext aggregationContext;
-
-    /** 是否执行发送领域事件操作: true 执行推送操作 */
-    boolean doSend = true;
-
-    /** 业务框架 flow 上下文 */
-    protected FlowContext flowContext;
-
-    protected AbstractFlowContextSend(FlowContext flowContext) {
-        this.flowContext = flowContext;
-        this.aggregationContext = flowContext.option(FlowAttr.aggregationContext);
-    }
-
-    public AbstractFlowContextSend(CommunicationAggregationContext aggregationContext) {
-        this.aggregationContext = aggregationContext;
-    }
-
-    /**
-     * 在将数据推送到调用方之前,触发的方法
-     * 
-     *     可以做一些逻辑,在逻辑中可以决定是否执行推送
-     * 
- */ - protected void logic() { - - } - - /** - * 小把戏 (钩子方法). 子类可以做些其他的事情 - *
-     *     在将数据推送到调用方之前,触发的方法
-     * 
- */ - protected void trick() { - } - - /** - * 响应消息到远程, 此方法是同步推送 - *
-     *     如果没有特殊情况 , 使用异步推送 (当前类的) send 方法
-     * 
- * 模板方法模式: - *
-     * 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。
-     * 模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
-     * 要点:
-     * - “模板方法”定义了算法的步骤,把这些步骤的实现延迟到子类。
-     * - 模板方法模式为我们提供了一种代码复用的重要技巧。
-     * - 模板方法的抽象类可以定义具体方法、抽象方法和钩子。
-     * - 抽象方法由子类实现。
-     * - 钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。
-     * - 为了防止子类改变模板方法中的算法,可以将模板方法声明为final。
-     * - 好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用低层模块。
-     * - 你将在真实世界代码中看到模板方法模式的许多变体,不要期待它们全都是一眼就可以被你一眼认出的。
-     * - 策略模式和模板方法模式都封装算法,一个用组合,一个用继承。
-     * - 工厂方法是模板方法的一种特殊版本。
-     * 
- */ - public final void execute() { - Objects.requireNonNull(this.flowContext, "flowContext must not be null"); - - if (userIds.isEmpty()) { - throw new RuntimeException("没有添加消息推送人 " + this.getClass()); - } - - // 子类构建响应内容 - this.logic(); - - // 钩子方法 - if (!doSend) { - return; - } - - // 响应对象 - ResponseMessage responseMessage = this.flowContext.getResponse(); - HeadMetadata headMetadata = responseMessage.getHeadMetadata(); - - // 路由设置 - int cmdMerge = this.getCmdMerge(); - headMetadata.setCmdMerge(cmdMerge); - - // 在将数据推送前调用的钩子方法 - this.trick(); - - // 推送响应 (广播消息)给指定的用户列表 - flowContext.broadcast(responseMessage, this.userIds); - } - - /** - * 接收广播的用户 - * - * @param userIds 用户id列表 - * @return me - */ - public AbstractFlowContextSend addUserId(Collection userIds) { - this.userIds.addAll(userIds); - return this; - } - - /** - * 接收广播的用户 - * - * @param userId 用户id - * @return me - */ - public AbstractFlowContextSend addUserId(long userId) { - this.userIds.add(userId); - return this; - } - - /** - * 添加用户id列表 - * - * @param userIds 用户id列表 - * @param excludeUserId 需要排除的用户id - * @return me - */ - public AbstractFlowContextSend addUserId(Collection userIds, long excludeUserId) { - return this.addUserId(userIds) - .removeUserId(excludeUserId); - } - - @Override - public Class getTopic() { - return AbstractFlowContextSend.class; - } - - - @Override - public int getCmdMerge() { - return flowContext.getActionCommand().getCmdInfo().getCmdMerge(); - } - - /** - * 不执行推送数据的操作 - */ - protected void disableSend() { - this.doSend = false; - } - - /** - * 排除用户id - * - * @param userId 用户id - * @return me - */ - private AbstractFlowContextSend removeUserId(long userId) { - if (userId > 0) { - this.userIds.remove(userId); - } - - return this; - } -} diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/GameFlow.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/GameFlow.java index bb1b69542..330a63802 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/GameFlow.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/GameFlow.java @@ -58,14 +58,14 @@ public class GameFlow { /** * 游戏开始 * - * @param abstractRoom room + * @param room room * @return true 游戏开始 */ - public boolean startGame(AbstractRoom abstractRoom) { - boolean startBefore = this.roomGameStartCustom.startBefore(abstractRoom); + public boolean startGame(Room room) { + boolean startBefore = this.roomGameStartCustom.startBefore(room); if (startBefore) { - this.roomGameStartCustom.startAfter(abstractRoom); + this.roomGameStartCustom.startAfter(room); } return startBefore; @@ -77,20 +77,20 @@ public boolean startGame(AbstractRoom abstractRoom) { * 根据 创建游戏规则 * * @param createRoomInfo 创建房间信息 - * @param AbstractRoom + * @param {@link Room} * @return 房间 */ - public T createRoom(CreateRoomInfo createRoomInfo) { + public T createRoom(CreateRoomInfo createRoomInfo) { return this.roomCreateCustom.createRoom(createRoomInfo); } /** * 构建房间内的玩家 * - * @param AbstractPlayer + * @param {@link Player} * @return 玩家 */ - public T createPlayer() { + public T createPlayer() { return this.roomPlayerCreateCustom.createPlayer(); } @@ -98,12 +98,12 @@ public T createPlayer() { * 进入房间 * * @param userId 玩家 id - * @param abstractRoom 玩家所在房间 + * @param room 玩家所在房间 * @param roomEnterInfo 进入房间请求信息 * @return enter Response */ - public RoomEnterInfo enterRoom(long userId, AbstractRoom abstractRoom, RoomEnterInfo roomEnterInfo) { - return this.roomEnterCustom.enterRoom(userId, abstractRoom, roomEnterInfo); + public RoomEnterInfo enterRoom(long userId, Room room, RoomEnterInfo roomEnterInfo) { + return this.roomEnterCustom.enterRoom(userId, room, roomEnterInfo); } /** diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractPlayer.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/Player.java similarity index 96% rename from widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractPlayer.java rename to widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/Player.java index 34e0cf2c8..56912e907 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractPlayer.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/Player.java @@ -37,7 +37,7 @@ @Setter @Accessors(chain = true) @FieldDefaults(level = AccessLevel.PROTECTED) -public abstract class AbstractPlayer implements Serializable { +public class Player implements Serializable { @Serial private static final long serialVersionUID = -26338708253909097L; /** userId 玩家 id */ 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/Room.java similarity index 83% rename from widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/AbstractRoom.java rename to widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/Room.java index 3b257ee44..14d18eeae 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/Room.java @@ -31,6 +31,7 @@ import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; /** * 抽象房间 @@ -41,8 +42,9 @@ @Getter @Setter @Accessors(chain = true) +@SuppressWarnings("unchecked") @FieldDefaults(level = AccessLevel.PROTECTED) -public abstract class AbstractRoom implements Serializable +public class Room implements Serializable // 房间广播增强 , RoomBroadcastEnhance { @@ -56,7 +58,7 @@ public abstract class AbstractRoom implements Serializable * value is player *
*/ - final Map playerMap = new NonBlockingHashMap<>(); + final Map playerMap = new NonBlockingHashMap<>(); /** * 玩家位置 @@ -86,12 +88,15 @@ public abstract class AbstractRoom implements Serializable * @param 玩家 * @return 所有玩家信息 (包括退出房间的玩家信息) */ - @SuppressWarnings("unchecked") - public Collection listPlayer() { + public Collection listPlayer() { return (Collection) this.playerMap.values(); } - public List listPlayer(Predicate predicate) { + public Stream streamPlayer() { + return this.playerMap.values().stream(); + } + + public List listPlayer(Predicate predicate) { return listPlayer().stream() .filter(predicate) .toList(); @@ -108,8 +113,7 @@ public Collection listPlayerId() { return this.playerMap.keySet(); } - @SuppressWarnings("unchecked") - public T getPlayerById(long userId) { + public T getPlayerById(long userId) { return (T) this.playerMap.get(userId); } @@ -122,18 +126,18 @@ public boolean existUser(long userId) { * * @param player 玩家 */ - public void addPlayer(AbstractPlayer player) { + public void addPlayer(Player player) { long userId = player.getId(); this.playerMap.put(userId, player); this.playerSeatMap.put(player.getSeat(), userId); } /** - * 移出玩家 + * 移除玩家 * * @param player 玩家 */ - public void removePlayer(AbstractPlayer player) { + public void removePlayer(Player player) { long userId = player.getId(); this.playerMap.remove(userId); this.playerSeatMap.remove(player.getSeat()); @@ -150,7 +154,7 @@ public boolean isStatus(RoomStatusEnum roomStatusEnum) { * @param action 给定操作 * @param t */ - public void ifPlayerExist(long userId, Consumer action) { + public void ifPlayerExist(long userId, Consumer action) { T player = this.getPlayerById(userId); Optional.ofNullable(player).ifPresent(action); } @@ -165,4 +169,12 @@ public void ifPlayerNotExist(long userId, Runnable runnable) { var player = this.getPlayerById(userId); PresentKit.ifNull(player, runnable); } + + public int countPlayer() { + return this.getPlayerMap().size(); + } + + public boolean isEmptyPlayer() { + return this.playerMap.isEmpty(); + } } \ No newline at end of file diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomService.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomService.java index d07df6712..4ffeed034 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomService.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/RoomService.java @@ -41,7 +41,7 @@ public class RoomService { * value : room *
*/ - final Map roomMap = new ConcurrentHashMap<>(); + final Map roomMap = new ConcurrentHashMap<>(); /** * 玩家对应的房间 map @@ -53,7 +53,7 @@ public class RoomService { final Map userRoomMap = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") - public T getRoomByUserId(long userId) { + public T getRoomByUserId(long userId) { // 通过 userId 得到 roomId Long roomId = userRoomMap.get(userId); @@ -66,11 +66,11 @@ public T getRoomByUserId(long userId) { } @SuppressWarnings("unchecked") - public T getRoom(long roomId) { + public T getRoom(long roomId) { return (T) this.roomMap.get(roomId); } - public void addRoom(AbstractRoom room) { + public void addRoom(Room room) { long roomId = room.getRoomId(); this.roomMap.put(roomId, room); } @@ -80,12 +80,12 @@ public void addRoom(AbstractRoom room) { * * @param room 房间 */ - public void removeRoom(AbstractRoom room) { + public void removeRoom(Room room) { long roomId = room.getRoomId(); this.roomMap.remove(roomId); } - public void addPlayer(AbstractRoom room, AbstractPlayer player) { + public void addPlayer(Room room, Player player) { room.addPlayer(player); this.userRoomMap.put(player.getId(), room.getRoomId()); } @@ -96,13 +96,13 @@ public void addPlayer(AbstractRoom room, AbstractPlayer player) { * @param room 房间 * @param player 玩家 */ - public void removePlayer(AbstractRoom room, AbstractPlayer player) { + public void removePlayer(Room room, Player player) { room.removePlayer(player); this.userRoomMap.remove(player.getId()); } @SuppressWarnings("unchecked") - public Collection listRoom() { + public Collection listRoom() { return (Collection) this.roomMap.values(); } } diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomCreateCustom.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomCreateCustom.java index 4375c97c7..372686837 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomCreateCustom.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomCreateCustom.java @@ -18,7 +18,7 @@ */ package com.iohao.game.widget.light.room.flow; -import com.iohao.game.widget.light.room.AbstractRoom; +import com.iohao.game.widget.light.room.Room; import com.iohao.game.widget.light.room.CreateRoomInfo; /** @@ -37,8 +37,8 @@ public interface RoomCreateCustom { * 根据 创建游戏规则 * * @param createRoomInfo 创建房间信息 - * @param AbstractRoom + * @param Room * @return 房间 */ - T createRoom(CreateRoomInfo createRoomInfo); + T createRoom(CreateRoomInfo createRoomInfo); } diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomEnterCustom.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomEnterCustom.java index 2eb081c8d..2ef0ad155 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomEnterCustom.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomEnterCustom.java @@ -18,7 +18,7 @@ */ package com.iohao.game.widget.light.room.flow; -import com.iohao.game.widget.light.room.AbstractRoom; +import com.iohao.game.widget.light.room.Room; /** * 进入房间 (重连) @@ -32,10 +32,10 @@ public interface RoomEnterCustom { * 进入房间 * * @param userId 玩家 id - * @param abstractRoom 玩家所在房间 + * @param room 玩家所在房间 * @param roomEnterInfo 进入房间请求信息 * @return enter Response */ - RoomEnterInfo enterRoom(long userId, AbstractRoom abstractRoom, RoomEnterInfo roomEnterInfo); + RoomEnterInfo enterRoom(long userId, Room room, RoomEnterInfo roomEnterInfo); } diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomGameStartCustom.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomGameStartCustom.java index 59ef3a14d..7aeb2a04a 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomGameStartCustom.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomGameStartCustom.java @@ -18,7 +18,7 @@ */ package com.iohao.game.widget.light.room.flow; -import com.iohao.game.widget.light.room.AbstractRoom; +import com.iohao.game.widget.light.room.Room; /** * 游戏开始 @@ -39,10 +39,10 @@ public interface RoomGameStartCustom { * 所以最好预留一个这样的验证接口, 交给子类游戏来定义开始游戏的规则 *
* - * @param abstractRoom 房间 + * @param room 房间 * @return 返回 true, 会执行 {@link RoomGameStartCustom#startAfter}. 并更新用户的状态为战斗状态 */ - boolean startBefore(AbstractRoom abstractRoom); + boolean startBefore(Room room); /** * 游戏开始前的 after 逻辑. 这里可以游戏正式开始的逻辑 @@ -52,7 +52,7 @@ public interface RoomGameStartCustom { * 回合制 进入战斗 * * - * @param abstractRoom 房间 + * @param room 房间 */ - void startAfter(AbstractRoom abstractRoom); + void startAfter(Room room); } diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomPlayerCreateCustom.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomPlayerCreateCustom.java index 1399762c1..83cc8e97b 100644 --- a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomPlayerCreateCustom.java +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/flow/RoomPlayerCreateCustom.java @@ -18,7 +18,7 @@ */ package com.iohao.game.widget.light.room.flow; -import com.iohao.game.widget.light.room.AbstractPlayer; +import com.iohao.game.widget.light.room.Player; /** * 创建玩家 - 自定义 @@ -33,8 +33,8 @@ public interface RoomPlayerCreateCustom { /** * 构建房间内的玩家 * - * @param AbstractPlayer + * @param {@link Player} * @return 玩家 */ - T createPlayer(); + T createPlayer(); } diff --git a/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/kit/RoomKit.java b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/kit/RoomKit.java new file mode 100644 index 000000000..3ec3cd79a --- /dev/null +++ b/widget/light-game-room/src/main/java/com/iohao/game/widget/light/room/kit/RoomKit.java @@ -0,0 +1,49 @@ +/* + * 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.kit; + +import com.iohao.game.widget.light.room.Room; +import lombok.experimental.UtilityClass; + +/** + * @author 渔民小镇 + * @date 2024-04-30 + * @since 21.7 + */ +@UtilityClass +public class RoomKit { + /** + * 从房间内获取一个空位置 + * + * @param room 房间 + * @return 空的位置 + */ + public int getEmptySeatNo(Room room) { + // 玩家位置 map + var playerSeatMap = room.getPlayerSeatMap(); + + for (int i = 0; i < room.getSpaceSize(); i++) { + if (!playerSeatMap.containsKey(i)) { + return i; + } + } + + return -1; + } +} 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 c82dd41c8..7b75996d1 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 @@ -18,8 +18,8 @@ */ package com.iohao.game.widget.light.room.operation; -import com.iohao.game.widget.light.room.AbstractPlayer; -import com.iohao.game.widget.light.room.AbstractRoom; +import com.iohao.game.widget.light.room.Player; +import com.iohao.game.widget.light.room.Room; /** * 操作上下文 @@ -31,7 +31,7 @@ public class OperationContext { /** 操作类型 */ int operation; - AbstractRoom room; + Room room; - AbstractPlayer player; + Player player; } diff --git a/widget/light-jprotobuf/pom.xml b/widget/light-jprotobuf/pom.xml index 4d5cb1d31..01ed60342 100644 --- a/widget/light-jprotobuf/pom.xml +++ b/widget/light-jprotobuf/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.6 + 21.7 ../../pom.xml 4.0.0 diff --git a/widget/light-profile/pom.xml b/widget/light-profile/pom.xml index e3cc54363..dc72b1ed3 100644 --- a/widget/light-profile/pom.xml +++ b/widget/light-profile/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.6 + 21.7 ../../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 dc0b7ef92..ff692f66c 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.6 + 21.7 ../../pom.xml diff --git a/widget/light-redis-lock/pom.xml b/widget/light-redis-lock/pom.xml index d0d1ab9d9..3fc0eb300 100644 --- a/widget/light-redis-lock/pom.xml +++ b/widget/light-redis-lock/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.6 + 21.7 ../../pom.xml 4.0.0 diff --git a/widget/light-timer-task/pom.xml b/widget/light-timer-task/pom.xml index 7acde3fad..dbd2dd988 100644 --- a/widget/light-timer-task/pom.xml +++ b/widget/light-timer-task/pom.xml @@ -5,7 +5,7 @@ ioGame com.iohao.game - 21.6 + 21.7 ../../pom.xml 4.0.0 diff --git a/widget/other-tool/pom.xml b/widget/other-tool/pom.xml index 0cad0a536..92f976bca 100644 --- a/widget/other-tool/pom.xml +++ b/widget/other-tool/pom.xml @@ -6,7 +6,7 @@ com.iohao.game ioGame - 21.6 + 21.7 ../../pom.xml