Skip to content

数据实体定义的最佳实践

ray.zh edited this page Nov 21, 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
  • 其他有正确定义数据格式的实体类

在实际项目使用中,一般有两种使用方式:

1. 使用消息注册功能

2: 自行解析数据和代码

2.1. 代码示例:

完整代码位于项目com.github.misterchangray.core.example目录下

在本例中假设存在两种数据报文HeatbeatCmdEchoCmd

2.1.1. 数据实体类定义

/**
 * 报文头
 * 一般来说为公共结构, 即每个数据都应该有此字段
 */
@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
}

2.1.2. 设备上行报文解析示例

定义完成报文结构体后,当设备上行数据到达时,使用以下方式进行解析:

/**
 * 这里是项目的启动类
 */
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