-
Notifications
You must be signed in to change notification settings - Fork 20
数据实体定义的最佳实践
ray.zh edited this page Dec 3, 2023
·
21 revisions
框架对于数据结构实体定义是没有限制的。 可以使用以下数据类型:
- byte/boolean/char/short/int/long/float/double
- Byte/Boolean/Char/Short/Integer/Long/Float/Double/String/UByte/UShort/UInt/ULong/UNumber
- 其他有正确定义数据格式的实体类
对于框架来说,在实际项目使用中,两种使用方式:
- 一是使用框架的消息注册,此时需要消息体中至少存在一个命令字段,此时在进行消息
pack
操作时,可以不传入目标类,框架将会根据注册消息自动寻找并使用。 - 二是自定义消息,然后分段解析后再进行业务操作
下面示例中将分别展示这两种方式使用上的区别。
完整代码位于项目com.github.misterchangray.core.messager
目录下
在本例中假设存在两种消息报文Teacher
和Student
,其中Teacher
是使用组合的方式将报文头进行整合。
// 这是公共头
@MagicClass
public class Head {
@MagicField(order = 1, defaultVal = 0xA8)
private byte head;
@MagicField(order = 2)
private UInt len;
// cmdField = true,标记此字段为消息id字段
@MagicField(order = 3, cmdField = true)
private byte cmd = 34;
// ignore setter/getter
}
// 注意需要实现 MagicMessage 类
// 此消息中cmd属性在定义时申明为33, 即注册后cmd=33代表为`Student`消息
@MagicClass
public class Student implements MagicMessage {
@MagicField(order = 1)
private byte head;
@MagicField(order = 2, calcLength = true)
private UInt len;
// 因为每个消息都有独立的cmd字段,所以这里将消息类型在申明时固定写死为33
// 没有使用公共头,所以类型字段建议static,final定义,避免被修改
// cmdField = true,标记此字段为消息id字段
@MagicField(order = 3, cmdField = true)
private static final byte cmd = 33;
@MagicField(order = 4)
private int age;
@MagicField(order = 5, size = 10)
private String name;
@Override
public int cmd() {
return this.cmd;
}
// ignore setter/getter
}
// 注意需要实现 MagicMessage 类
// 此消息使用给公共头,所以注册时需要指定消息类型
@MagicClass
public class Teacher implements MagicMessage {
@MagicField(order = 1)
private Head head;
@MagicField(order = 4)
private int age;
@MagicField(order = 5, size = 10)
private String name;
@Override
public int cmd() {
return this.head.getCmd();
}
// ignore setter/getter
}
public void main() {
// 消息注册,Student消息类型在定义时已申明,所以可以直接注册
MagicByte.registerCMD(Student.class);
// 准备测试数据
Student student = new Student();
student.setAge(13);
student.setName("AA");
// 序列号转换
byte[] bytes = MagicByte.unpackToByte(student);
MagicMessage s1 = MagicByte.pack(bytes);
// 可以通过 cmd() 函数获取当前消息类型, 前提是必须要实现接口 MagicMessage.cmd 方法
if(s1.cmd() == 33) {
// 将消息转为具体的消息值即可实现业务逻辑
Student s = (Student) s1;
}
// Teacher消息使用公共头,所以注册时需要指定消息id
// 注册后cmd=34代表为 Teacher 消息
MagicByte.registerCMD(34, Teacher.class);
// 准备测试数据
Head head = new Head();
head.setCmd((byte) 34);
head.setLen(UInt.valueOf(17));
Teacher teacher = new Teacher();
teacher.setHead(head);
teacher.setAge(38);
teacher.setName("EABC");
// 序列号转换
byte[] bytes2 = MagicByte.unpackToByte(teacher);
MagicMessage s2 = MagicByte.pack(bytes2);
// 可以通过 cmd() 函数获取当前消息类型, 前提是必须要实现接口 MagicMessage.cmd 方法
if(s2.cmd() == 33) {
// 将消息转为具体的消息值即可实现业务逻辑
Teacher s3 = (Teacher)s2;
}
}
完整代码位于项目com.github.misterchangray.core.example
目录下
在本例中假设存在两种数据报文HeatbeatCmd
和EchoCmd
/**
* 报文头
* 一般来说为公共结构, 即每个数据都应该有此字段
*/
@MagicClass
public class Head {
@MagicField(order = 1)
private byte header;
// calcLength = true 时,此字段将会在序列化时自动填充数据实际长度
@MagicField(order = 3, calcLength = true)
private byte length;
// 假设 1=HeartbeatCmd,2=EchoCmd
@MagicField(order = 5)
private byte cmd;
// ignore setter/getter
}
/**
* 心跳报文
*
* 一般来说会周期性上报
*/
@MagicClass()
public class HeartbeatCmd {
// 这里组合使用 Head 结构
@MagicField(order = 1)
private Head head;
// 8 bytes
@MagicField(order = 3)
private long timestamp;
// 1 byte
@MagicField(order = 5)
private byte checkCode;
// ignore setter/getter
}
/**
* 指定的业务报文
*
* 一般来说,这里应该是业务报文,
* 这里假设设备端存在一个回响报文, 设备端原样返回云端下发数据
*/
@MagicClass()
public class EchoCmd {
// 这里组合使用 Head 结构
@MagicField(order = 1)
private Head head;
// string 总字节数为10
@MagicField(order = 3, size = 10)
private String body;
// 1 byte
@MagicField(order = 5)
private byte checkCode;
// ignore setter/getter
}
定义完成报文结构体后,当设备上行数据到达时, 先解析消息头,然后根据消息头标识进行消息体解析,代码如下:
/**
* 这里是项目的启动类
*/
public class ApplicationStartup {
public static void main(String[] args) {
// 假设此处数据为设备的上行数据
byte[] deviceUploadMessage = new byte[]{};
// 以下为业务端逻辑解析建议方式
// 1. 首先应该解析报文头
Head pack = MagicByte.pack(deviceUploadMessage, Head.class);
// 2. 这里省略检查报文头逻辑
// 3. 根据报文类型解析为特定的报文格式
switch (pack.getCmd()) {
case 1:
HeartbeatCmd pack1 = MagicByte.pack(deviceUploadMessage, HeartbeatCmd.class);
doHeartbeatCmd(pack1);
break;
case 2:
EchoCmd pack2 = MagicByte.pack(deviceUploadMessage, EchoCmd.class);
doEchoCmd(pack2);
break;
// 更多的报文类型
}
}
private static void doEchoCmd(EchoCmd pack2) {
// 这里处理EchoCmd 的业务
}
private static void doHeartbeatCmd(HeartbeatCmd pack1) {
// 这里处理 HeartbeatCmd 的业务
}
}
以上是框架的建议的使用方式。其他的情况框架将会做如下处理:
- 未被
@MagicField
进行标注的字段将会被字节忽略 - 继承的属性直接被忽略。因为不支持继承。
- 未知的数据类型, 即没有自定义转换且也没有受到支持的数据类型;比如使用 Map, Object 什么的。
- 如果使用包装类型且未赋值, 则将使用原始类型默认值。
- 数组或对象为
Null
时, 将使用 0x00 进行填充。 - 如果需要计算校验和的场景,建议返回buffer然后直接附加校验和结果
-
strict=true
模式将会自动继承, 并且要求数据完整(即数据长度不匹配将不会解析); 默认false
若大家在使用过程中有疑问,可以参考右边的目录列表进行查询