From ec4efbbdbbc159f2749cdd8067a56d3000331e05 Mon Sep 17 00:00:00 2001 From: kingston-csj <475139136@qq.com> Date: Sun, 9 Nov 2025 15:48:21 +0800 Subject: [PATCH] =?UTF-8?q?struct-codec=E5=A2=9E=E5=8A=A0MapCodec=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=AD=97=E5=85=B8=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSIONS.md | 7 ++ .../jforgame/codec/struct/ArrayCodec.java | 8 +- .../java/jforgame/codec/struct/Codec.java | 1 + .../codec/struct/CollectionCodec.java | 98 ++++++++++--------- .../jforgame/codec/struct/FieldCodecMeta.java | 92 +++++++++-------- .../java/jforgame/codec/struct/MapCodec.java | 64 ++++++++++++ .../src/test/java/TestArrayMessage.java | 26 +++++ .../src/test/java/TestChildMessage.java | 1 + .../src/test/java/TestMapMessage.java | 27 +++++ .../src/test/java/message/ItemVo.java | 34 +++++++ .../src/test/java/message/PlayerBackpack.java | 17 ++++ .../test/java/message/PlayerBackpack2.java | 18 ++++ .../{ => message}/PlayerLevelUpMessage.java | 2 + .../java/{ => message}/PlayerMessage.java | 2 + 14 files changed, 309 insertions(+), 88 deletions(-) create mode 100644 jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/MapCodec.java create mode 100644 jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestArrayMessage.java create mode 100644 jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestMapMessage.java create mode 100644 jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/ItemVo.java create mode 100644 jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerBackpack.java create mode 100644 jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerBackpack2.java rename jforgame-codec-parent/jforgame-codec-struct/src/test/java/{ => message}/PlayerLevelUpMessage.java (93%) rename jforgame-codec-parent/jforgame-codec-struct/src/test/java/{ => message}/PlayerMessage.java (92%) diff --git a/VERSIONS.md b/VERSIONS.md index 0056ce33..6f479a89 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -254,11 +254,18 @@ Container#inject()方法增加主键检查 Container增加#validate(DataRepository dataRepository)方法,允许容器进行关联数据校验 +### jforgame-codec-struct + 增加MapCodec,以支持字典字段 + ## V4.0.0 api变动!!(计划) Container去掉#validate()无参方法 + +### jforgame-codec-struct + StructMessageCodec更名为BeanAutoCodec ProtobufMessageCodec更名为ProtobufCodec + ArrayCodec/CollectionCodec容器元素支持继承关系 diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/ArrayCodec.java b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/ArrayCodec.java index ddf5b390..a489738e 100644 --- a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/ArrayCodec.java +++ b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/ArrayCodec.java @@ -7,13 +7,16 @@ import java.nio.ByteBuffer; * 数组属性序列化 * 注:由于数组元素bean没有像Message一样注册id, * 因此数组的元素不能是父类或抽象类 + * 数组长度不能超过Short.MAX_VALUE,即最多65535 */ public class ArrayCodec extends Codec { @Override public Object decode(ByteBuffer in, Class type, Class wrapper) { int size = ByteBuffUtil.readShort(in); - + if (size < 0) { + throw new RuntimeException("Array size less than zero!"); + } Object array = ReflectUtil.newArray(type, wrapper, size); for (int i = 0; i < size; i++) { @@ -32,6 +35,9 @@ public class ArrayCodec extends Codec { return; } int size = Array.getLength(value); + if (size > Short.MAX_VALUE) { + throw new RuntimeException("Collection size less than zero or exceed max short value!"); + } ByteBuffUtil.writeShort(out, (short) size); for (int i = 0; i < size; i++) { Object elem = Array.get(value, i); diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/Codec.java b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/Codec.java index c8792036..6d8a66b6 100644 --- a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/Codec.java +++ b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/Codec.java @@ -37,6 +37,7 @@ public abstract class Codec { register(String.class, new StringCodec()); register(List.class, new CollectionCodec()); register(Set.class, new CollectionCodec()); + register(Map.class, new MapCodec()); register(Object[].class, new ArrayCodec()); } diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/CollectionCodec.java b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/CollectionCodec.java index c1eaca61..44e06f88 100644 --- a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/CollectionCodec.java +++ b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/CollectionCodec.java @@ -9,58 +9,64 @@ import java.util.List; import java.util.Set; /** -* -* 集合属性序列化 -* 注:由于集合元素bean没有像Message一样注册id, -* 因此集合的元素不能是父类或抽象类 -*/ + * 集合属性序列化 + * 注:由于集合元素bean没有像Message一样注册id, + * 因此集合的元素不能是父类或抽象类 + * 集合长度不能超过Short.MAX_VALUE,即最多65535 + */ public class CollectionCodec extends Codec { - @Override - @SuppressWarnings("all") - public Object decode(ByteBuffer in, Class type, Class wrapper) { - int size = ByteBuffUtil.readShort(in); - int modifier = type.getModifiers(); - Collection result = null; + @Override + @SuppressWarnings("all") + public Object decode(ByteBuffer in, Class type, Class wrapper) { + int size = ByteBuffUtil.readShort(in); + if (size < 0) { + throw new RuntimeException("Collection size less than zero!"); + } + int modifier = type.getModifiers(); + Collection result = null; - if (Modifier.isAbstract(modifier) || Modifier.isInterface(modifier)) { - if (List.class.isAssignableFrom(type)) { - result = new ArrayList<>(); - } else if (Set.class.isAssignableFrom(type)) { - result = new HashSet<>(); - } - } else { - try { - result = (Collection)type.newInstance(); - }catch(Exception e) { - result = new ArrayList<>(); - } - } + if (Modifier.isAbstract(modifier) || Modifier.isInterface(modifier)) { + if (List.class.isAssignableFrom(type)) { + result = new ArrayList<>(); + } else if (Set.class.isAssignableFrom(type)) { + result = new HashSet<>(); + } + } else { + try { + result = (Collection) type.newInstance(); + } catch (Exception e) { + result = new ArrayList<>(); + } + } - for (int i=0; i wrapper) { - if (value == null) { - ByteBuffUtil.writeShort(out, (short)0); - return; - } - Collection collection = (Collection)value; - int size = collection.size(); - ByteBuffUtil.writeShort(out, (short)size); + @Override + public void encode(ByteBuffer out, Object value, Class wrapper) { + if (value == null) { + ByteBuffUtil.writeShort(out, (short) 0); + return; + } + Collection collection = (Collection) value; + int size = collection.size(); + if (size > Short.MAX_VALUE) { + throw new RuntimeException("Collection size less than zero or exceed max short value!"); + } + ByteBuffUtil.writeShort(out, (short) size); - for (Object elem:collection) { - Class clazz = elem.getClass(); - Codec fieldCodec = getSerializer(clazz); - fieldCodec.encode(out, elem, wrapper); - } - } + for (Object elem : collection) { + Class clazz = elem.getClass(); + Codec fieldCodec = getSerializer(clazz); + fieldCodec.encode(out, elem, wrapper); + } + } } diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/FieldCodecMeta.java b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/FieldCodecMeta.java index 16ce8c1b..44bd958b 100644 --- a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/FieldCodecMeta.java +++ b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/FieldCodecMeta.java @@ -2,53 +2,63 @@ package jforgame.codec.struct; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.Collection; +import java.util.Map; public class FieldCodecMeta { - - private Field field; - - private Class type; - - private Codec codec; - /** collection类型里的元素 */ - private Class wrapper; - - public static FieldCodecMeta valueOf(Field field, Codec codec) { - FieldCodecMeta meta = new FieldCodecMeta(); - meta.field = field; - Class type = field.getType(); - meta.type = type; - meta.codec = codec; - - if (Collection.class.isAssignableFrom(type)) { - meta.wrapper = (Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]; - } else if (type.isArray()) { - meta.wrapper = type.getComponentType(); - } - return meta; - } - public Field getField() { - return field; - } + private Field field; - public Class getType() { - return type; - } + private Class type; - public Codec getCodec() { - return codec; - } + private Codec codec; + /** + * collection类型里的元素 + */ + private Class wrapper; - public Class getWrapper() { - return wrapper; - } + public static FieldCodecMeta valueOf(Field field, Codec codec) { + FieldCodecMeta meta = new FieldCodecMeta(); + meta.field = field; + Class type = field.getType(); + meta.type = type; + meta.codec = codec; + // 处理 Collection 类型(存储元素类型) + if (Collection.class.isAssignableFrom(type)) { + meta.wrapper = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + } else if (type.isArray()) { + // 处理数组类型(存储元素类型) + meta.wrapper = type.getComponentType(); + } // 处理 Map 类型(Key 固定为 String,只存储 Value 类型) + else if (Map.class.isAssignableFrom(type)) { + // 泛型参数:[0]是Key(忽略,强制为String),[1]是Value类型 + Type[] actualTypes = ((ParameterizedType) field.getGenericType()).getActualTypeArguments(); + meta.wrapper = (Class) actualTypes[1]; // 只存 Value 类型 + } + return meta; + } + + public Field getField() { + return field; + } + + public Class getType() { + return type; + } + + public Codec getCodec() { + return codec; + } + + public Class getWrapper() { + return wrapper; + } + + @Override + public String toString() { + return "FieldCodecMeta [field=" + field + ", type=" + type + ", serializer=" + codec + ", wrapper=" + + wrapper + "]"; + } - @Override - public String toString() { - return "FieldCodecMeta [field=" + field + ", type=" + type + ", serializer=" + codec + ", wrapper=" - + wrapper + "]"; - } - } diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/MapCodec.java b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/MapCodec.java new file mode 100644 index 00000000..54790553 --- /dev/null +++ b/jforgame-codec-parent/jforgame-codec-struct/src/main/java/jforgame/codec/struct/MapCodec.java @@ -0,0 +1,64 @@ +package jforgame.codec.struct; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +/** + * Map 编解码器(Key 强制为 String 类型,后续大版本升级再做改造) + */ +public class MapCodec extends Codec { + + @Override + @SuppressWarnings("all") + public Object decode(ByteBuffer in, Class type, Class valueType) { + // 读取 Map 大小(short 类型) + int size = ByteBuffUtil.readShort(in); + Map result = new HashMap<>(size); + if (valueType == null) { + throw new IllegalArgumentException("MapCodec: valueType is null"); + } + + // Value 类型:优先用 wrapper 传入的类型,否则默认 Object + Class actualValueType = valueType != null ? valueType : Object.class; + Codec valueCodec = getSerializer(actualValueType); + + // 循环解码键值对(Key 固定为 String) + for (int i = 0; i < size; i++) { + // 解码 Key(强制用 StringCodec) + String key = (String) getSerializer(String.class).decode(in, String.class, null); + // 解码 Value(用指定的 Value 类型编解码器) + Object value = valueCodec.decode(in, actualValueType, null); + result.put(key, value); + } + + return result; + } + + @Override + public void encode(ByteBuffer out, Object target, Class wrapper) { + if (target == null) { + // 空 Map 写入 size=0 + ByteBuffUtil.writeShort(out, (short) 0); + return; + } + + Map map = (Map) target; + int size = map.size(); + ByteBuffUtil.writeShort(out, (short) size); + + // 循环编码键值对(Key 强制为 String) + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // 编码 Key + getSerializer(String.class).encode(out, key, null); + + // 编码 Value(根据实际类型获取编解码器) + Class valueType = value != null ? value.getClass() : Object.class; + Codec valueCodec = getSerializer(valueType); + valueCodec.encode(out, value, null); + } + } + +} \ No newline at end of file diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestArrayMessage.java b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestArrayMessage.java new file mode 100644 index 00000000..e595741b --- /dev/null +++ b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestArrayMessage.java @@ -0,0 +1,26 @@ +import jforgame.codec.MessageCodec; +import jforgame.codec.struct.StructMessageCodec; +import message.ItemVo; +import message.PlayerBackpack; +import org.junit.Test; + +public class TestArrayMessage { + + private MessageCodec messageCodec = new StructMessageCodec(); + + @Test + public void test() throws Exception { + PlayerBackpack backpack = new PlayerBackpack(); + for (int i = 0; i < 10; i++) { + ItemVo vo = new ItemVo(); + vo.setItemId(i); + vo.setCount(i); + backpack.getItems().add(vo); + } + + byte[] encode = messageCodec.encode(backpack); + + PlayerBackpack newMsg = (PlayerBackpack) messageCodec.decode(PlayerBackpack.class, encode); + System.out.println(newMsg); + } +} diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestChildMessage.java b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestChildMessage.java index 88b2fd2a..000dfa2b 100644 --- a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestChildMessage.java +++ b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestChildMessage.java @@ -1,5 +1,6 @@ import jforgame.codec.MessageCodec; import jforgame.codec.struct.StructMessageCodec; +import message.PlayerLevelUpMessage; import org.junit.Assert; import org.junit.Test; diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestMapMessage.java b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestMapMessage.java new file mode 100644 index 00000000..1f6754a2 --- /dev/null +++ b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/TestMapMessage.java @@ -0,0 +1,27 @@ +import jforgame.codec.MessageCodec; +import jforgame.codec.struct.StructMessageCodec; +import message.ItemVo; +import message.PlayerBackpack2; +import org.junit.Test; + +public class TestMapMessage { + + private MessageCodec messageCodec = new StructMessageCodec(); + + @Test + public void test() throws Exception { + PlayerBackpack2 backpack = new PlayerBackpack2(); + for (int i = 0; i < 10; i++) { + ItemVo vo = new ItemVo(); + vo.setUid("uid_" + i); + vo.setItemId(i); + vo.setCount(i); + backpack.getItems().put(vo.getUid(), vo); + } + + byte[] encode = messageCodec.encode(backpack); + + PlayerBackpack2 newMsg = (PlayerBackpack2) messageCodec.decode(PlayerBackpack2.class, encode); + System.out.println(newMsg); + } +} diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/ItemVo.java b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/ItemVo.java new file mode 100644 index 00000000..7fbfb3b4 --- /dev/null +++ b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/ItemVo.java @@ -0,0 +1,34 @@ +package message; + +public class ItemVo { + + private String uid; + + private int itemId; + + private int count; + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public int getItemId() { + return itemId; + } + + public void setItemId(int itemId) { + this.itemId = itemId; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } +} diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerBackpack.java b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerBackpack.java new file mode 100644 index 00000000..6dd51091 --- /dev/null +++ b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerBackpack.java @@ -0,0 +1,17 @@ +package message; + +import java.util.LinkedList; +import java.util.List; + +public class PlayerBackpack { + + private List items = new LinkedList<>(); + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerBackpack2.java b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerBackpack2.java new file mode 100644 index 00000000..c2154e9c --- /dev/null +++ b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerBackpack2.java @@ -0,0 +1,18 @@ +package message; + +import java.util.HashMap; +import java.util.Map; + +public class PlayerBackpack2 { + + private Map items = new HashMap<>(); + + + public Map getItems() { + return items; + } + + public void setItems(Map items) { + this.items = items; + } +} diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/PlayerLevelUpMessage.java b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerLevelUpMessage.java similarity index 93% rename from jforgame-codec-parent/jforgame-codec-struct/src/test/java/PlayerLevelUpMessage.java rename to jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerLevelUpMessage.java index d0a19f59..c1be1ed1 100644 --- a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/PlayerLevelUpMessage.java +++ b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerLevelUpMessage.java @@ -1,3 +1,5 @@ +package message; + public class PlayerLevelUpMessage extends PlayerMessage { private int afterLevel; diff --git a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/PlayerMessage.java b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerMessage.java similarity index 92% rename from jforgame-codec-parent/jforgame-codec-struct/src/test/java/PlayerMessage.java rename to jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerMessage.java index aec8a007..dd92a6f5 100644 --- a/jforgame-codec-parent/jforgame-codec-struct/src/test/java/PlayerMessage.java +++ b/jforgame-codec-parent/jforgame-codec-struct/src/test/java/message/PlayerMessage.java @@ -1,3 +1,5 @@ +package message; + public class PlayerMessage { private long playerId;