JAVA JT/T808-2011 协议序列化 / 反序列化实现(Netty ByteBuf + 自定义注解)

一、pom

<!-- Netty 缓冲区核心包:包含ByteBuf所有核心实现 -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-buffer</artifactId>
            <version>4.1.110.Final</version> <!-- 最新稳定版,可替换为4.1.x系列其他版本 -->
        </dependency>

二、定义@length注解

import java.lang.annotation.*;

/**
 * JT/T808 无符号字段长度注解
 * length: 1=U8, 2=U16, 4=U32
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Length {
    int value(); // 字节长度 1/2/4
}

三、定义消息体

/**
 * JT/T808-2011 示例消息体
 */
public class Jt808Message {
    // 消息ID U16
    @Length(2)
    private int msgId;

    // 消息体属性 U16
    @Length(2)
    private int msgProps;

    // 终端手机号 BCD[6](808协议固定6字节)
    private String terminalPhone;

    // 流水号 U16
    @Length(2)
    private int flowId;

    // 额外示例:U8 + U32
    @Length(1)
    private int command;

    @Length(4)
    private long data;

    // getter/setter
    public int getMsgId() { return msgId; }
    public void setMsgId(int msgId) { this.msgId = msgId; }
    public int getMsgProps() { return msgProps; }
    public void setMsgProps(int msgProps) { this.msgProps = msgProps; }
    public String getTerminalPhone() { return terminalPhone; }
    public void setTerminalPhone(String terminalPhone) { this.terminalPhone = terminalPhone; }
    public int getFlowId() { return flowId; }
    public void setFlowId(int flowId) { this.flowId = flowId; }
    public int getCommand() { return command; }
    public void setCommand(int command) { this.command = command; }
    public long getData() { return data; }
    public void setData(long data) { this.data = data; }
}

四、核心工具类实现编码和解码

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.lang.reflect.Field;

/**
 * JT/T808-2011 序列化/反序列化工具(Netty ByteBuf 实现)
 * 支持 @Length 注解无符号字段:1=U8,2=U16,4=U32
 */
public class Jt808CodecUtils {

    // ======================== 编码:对象 → ByteBuf ========================
    public static ByteBuf encode(Object obj) throws Exception {
        ByteBuf buf = Unpooled.buffer();
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            field.setAccessible(true);
            Object value = field.get(obj);

            // 1. 处理 @Length 无符号数字段
            if (field.isAnnotationPresent(Length.class)) {
                int len = field.getAnnotation(Length.class).value();
                writeUnsignedField(buf, value, len);
                continue;
            }

            // 2. 特殊处理:终端手机号(808协议 BCD 6字节)
            if ("terminalPhone".equals(field.getName())) {
                writeBcdPhone(buf, (String) value);
            }
        }
        return buf;
    }

    // 写入无符号字段
    private static void writeUnsignedField(ByteBuf buf, Object value, int len) {
        long num = ((Number) value).longValue();
        switch (len) {
            case 1: {
                buf.writeByte((int) (num & 0xFF));        // U8
                break;
            }
            case 2: {
                buf.writeShort((int) (num & 0xFFFF));     // U16
                break;
            }
            case 4: {
                buf.writeInt((int) (num & 0xFFFFFFFFL));  // U32
                break;
            }
            default: {
                throw new IllegalArgumentException("不支持长度:" + len);
//                break;
            }
        }
    }

    // 写入 808 BCD 手机号(6字节)
    private static void writeBcdPhone(ByteBuf buf, String phone) {
        if (phone == null) phone = "";
        phone = String.format("%12s", phone).replace(' ', '0');
        byte[] bcd = new byte[6];
        for (int i = 0; i < 12; i += 2) {
            int h = phone.charAt(i) - '0';
            int l = phone.charAt(i + 1) - '0';
            bcd[i / 2] = (byte) ((h << 4) | l);
        }
        buf.writeBytes(bcd);
    }

    // ======================== 解码:ByteBuf → 对象 ========================
    public static <T> T decode(ByteBuf buf, Class<T> clazz) throws Exception {
        T obj = clazz.getDeclaredConstructor().newInstance();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            field.setAccessible(true);

            // 1. 处理 @Length 无符号字段
            if (field.isAnnotationPresent(Length.class)) {
                int len = field.getAnnotation(Length.class).value();
                Object val = readUnsignedField(buf, len);
                field.set(obj, val);
                continue;
            }

            // 2. 读取 BCD 手机号
            if ("terminalPhone".equals(field.getName())) {
                field.set(obj, readBcdPhone(buf));
            }
        }
        return obj;
    }

    // 读取无符号字段
    private static Object readUnsignedField(ByteBuf buf, int len) {
        switch (len) {
            case 1: {
                return buf.readUnsignedByte();   // U8 → int
            }
            case 2: {
                return buf.readUnsignedShort();  // U16 → int
            }
            case 4: {
                return buf.readUnsignedInt();    // U32 → long
            }
            default: {
                throw new IllegalArgumentException("不支持长度:" + len);
            }
        }
    }

    // 读取 BCD 手机号
    private static String readBcdPhone(ByteBuf buf) {
        byte[] bcd = new byte[6];
        buf.readBytes(bcd);
        StringBuilder sb = new StringBuilder();
        for (byte b : bcd) {
            sb.append((b >> 4) & 0x0F);
            sb.append(b & 0x0F);
        }
        return sb.toString().replaceFirst("^0+", ""); // 去掉前导0
    }
}

五、测试

import io.netty.buffer.ByteBuf;

public class Jt808Test {
    public static void main(String[] args) throws Exception {
        // 1. 构造测试消息
        Jt808Message send = new Jt808Message();
        send.setMsgId(0x0200);        // U16
        send.setMsgProps(0x0030);     // U16
        send.setTerminalPhone("13800138000");
        send.setFlowId(1001);         // U16
        send.setCommand(0x05);        // U8
        send.setData(0x12345678L);    // U32

        // 2. 编码:对象 → 字节流
        ByteBuf buf = Jt808CodecUtils.encode(send);
        System.out.println("编码后字节长度:" + buf.readableBytes());

        // 3. 解码:字节流 → 对象
        Jt808Message recv = Jt808CodecUtils.decode(buf, Jt808Message.class);

        // 4. 输出验证
        System.out.println("消息ID:0x" + Integer.toHexString(recv.getMsgId()));
        System.out.println("手机号:" + recv.getTerminalPhone());
        System.out.println("命令字:" + recv.getCommand());
        System.out.println("数据域:0x" + Long.toHexString(recv.getData()));
    }
}

 

posted @ 2026-03-20 09:33  都是城市惹的祸  阅读(15)  评论(0)    收藏  举报