package com.zy.common.utils;
|
|
import com.zy.common.model.annotations.*;
|
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.Expression;
|
import org.springframework.expression.ExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
|
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayOutputStream;
|
import java.io.DataInputStream;
|
import java.io.DataOutputStream;
|
import java.lang.reflect.Array;
|
import java.lang.reflect.Field;
|
import java.nio.charset.StandardCharsets;
|
import java.util.*;
|
|
/**
|
* Tcp协议报文基类
|
* @author vincent
|
*/
|
public class Struct implements java.io.Serializable {
|
|
private transient Map<String, byte[]> decodeByteMapping = new HashMap<>();
|
private transient Map<String, byte[]> encodeByteMapping = new HashMap<>();
|
// 解码前的原生字节数组
|
private transient byte[] dataBytes = null;
|
private transient byte[] decodeBytes = null;
|
private transient byte[] encodeBytes = null;
|
private transient byte[] unusedBytes = null;
|
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
public static <T extends Struct> T decode(Class<? extends Struct> cls, byte[] bytes) throws Exception {
|
Struct instance = cls.newInstance();
|
return instance.decode(bytes);
|
}
|
|
// 字节数组 ===>> 转为十六进制字符串
|
public static String toHex(byte[] bytes) {
|
StringBuilder buf = new StringBuilder(bytes.length * 2);
|
for (byte b : bytes) { // 使用String的format方法进行转换
|
buf.append(String.format("%02x", b & 0xff));
|
}
|
return buf.toString().toUpperCase();
|
}
|
|
public static byte[] toBytes(String str) {
|
if (str == null || str.trim().equals("")) return new byte[0];
|
byte[] bytes = new byte[str.length() / 2];
|
for (int i = 0; i < str.length() / 2; i++) {
|
String subStr = str.substring(i * 2, i * 2 + 2);
|
bytes[i] = (byte) Integer.parseInt(subStr, 16);
|
}
|
return bytes;
|
}
|
|
/**
|
* @功能: BCD码转为10进制串(阿拉伯数据)
|
* @参数: BCD码
|
* @结果: 10进制串
|
*/
|
public static String bcd2Str(byte[] bytes) {
|
StringBuffer temp = new StringBuffer(bytes.length * 2);
|
for (int i = 0; i < bytes.length; i++) {
|
temp.append((byte) ((bytes[i] & 0xf0) >>> 4));
|
temp.append((byte) (bytes[i] & 0x0f));
|
}
|
return temp.toString().substring(0, 1).equalsIgnoreCase("0") ? temp
|
.toString().substring(1) : temp.toString();
|
}
|
|
/**
|
* @功能: 10进制串转为BCD码
|
* @参数: 10进制串
|
* @结果: BCD码
|
*/
|
public static byte[] str2Bcd(String asc) {
|
int len = asc.length();
|
int mod = len % 2;
|
if (mod != 0) {
|
asc = "0" + asc;
|
len = asc.length();
|
}
|
byte abt[] = new byte[len];
|
if (len >= 2) {
|
len = len / 2;
|
}
|
byte bbt[] = new byte[len];
|
abt = asc.getBytes();
|
int j, k;
|
for (int p = 0; p < asc.length() / 2; p++) {
|
if ((abt[2 * p] >= '0') && (abt[2 * p] <= '9')) {
|
j = abt[2 * p] - '0';
|
} else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) {
|
j = abt[2 * p] - 'a' + 0x0a;
|
} else {
|
j = abt[2 * p] - 'A' + 0x0a;
|
}
|
if ((abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) {
|
k = abt[2 * p + 1] - '0';
|
} else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) {
|
k = abt[2 * p + 1] - 'a' + 0x0a;
|
} else {
|
k = abt[2 * p + 1] - 'A' + 0x0a;
|
}
|
int a = (j << 4) + k;
|
byte b = (byte) a;
|
bbt[p] = b;
|
}
|
return bbt;
|
}
|
|
public static byte getXor(byte[] datas, int start, int end) {
|
byte temp = datas[start];
|
for (int i = start + 1; i < end; i++) {
|
temp ^= datas[i];
|
}
|
return temp;
|
}
|
|
/**
|
* 求校验和的算法
|
* @param b 需要求校验和的字节数组
|
* @return 校验和
|
*/
|
public static byte sumCheck(byte[] b, int start, int end) {
|
int sum = 0;
|
for (int i = start; i < end; i++) {
|
sum = sum + b[i];
|
}
|
return (byte) (sum & 0xff);
|
}
|
|
//字符串转换为ascii
|
public static byte[] StrToAsc(String content) {
|
try {
|
return content.getBytes(StandardCharsets.US_ASCII);
|
} catch (Exception e) {
|
e.printStackTrace();
|
return null;
|
}
|
}
|
|
//ascii转换为string
|
public static String AscToStr(byte[] bytes) {
|
StringBuffer sbu = new StringBuffer();
|
for (byte aByte : bytes) {
|
sbu.append(Character.toString((char) aByte));
|
}
|
return sbu.toString();
|
}
|
|
public Class getClassType(String name) {
|
return null;
|
}
|
|
/**
|
* 判断大小端
|
* @return true: 小端 / false: 大端
|
*/
|
public boolean isReverse() {
|
return this.getClass().getAnnotation(little.class) != null;
|
}
|
|
/**
|
* 获取字节数组
|
*/
|
public byte[] toBytes() throws Exception {
|
return encode(this);
|
}
|
|
public Map<String, String> toDecodeHexMapping() throws Exception {
|
if (this.decodeByteMapping == null) this.decodeByteMapping = new HashMap<>();
|
Map<String, String> mapping = getHex(this.decodeByteMapping);
|
if (this.decodeBytes != null) {
|
mapping.put("byte[]", Struct.toHex(this.decodeBytes));
|
}
|
return mapping;
|
}
|
|
public Map<String, String> toEncodeHexMapping() throws Exception {
|
if (this.encodeBytes == null) this.encode(this);
|
if (this.encodeByteMapping == null) this.encodeByteMapping = new HashMap<>();
|
Map<String, String> mapping = getHex(this.encodeByteMapping);
|
mapping.put("byte[]", Struct.toHex(this.encodeBytes));
|
return mapping;
|
}
|
|
public Map<String, String> getHex(Map<String, byte[]> bytemapping) throws Exception {
|
Map<String, String> map = new LinkedHashMap<>();
|
Field[] fields = this.getClass().getFields();
|
for (Field field : fields) {
|
byte[] bytes = bytemapping.get(field.getName());
|
if (bytes != null) {
|
map.put(field.getName(), Struct.toHex(bytes));
|
}
|
}
|
return map;
|
}
|
|
// 转java对象到数组
|
public byte[] encode(Struct entity) throws Exception {
|
ByteArrayOutputStream baOs = new ByteArrayOutputStream();
|
write(new DataOutputStream(baOs), entity);
|
entity.encodeBytes = baOs.toByteArray();
|
return entity.encodeBytes;
|
}
|
|
/**
|
* 写数据到输出流
|
*
|
* @param dos
|
* @param entity
|
*/
|
public void write(DataOutputStream dos, Struct entity) throws Exception {
|
if (entity == null) return;
|
if (entity.encodeByteMapping == null) entity.encodeByteMapping = new HashMap<>();
|
Field[] fields = entity.getClass().getFields();
|
EvaluationContext context = new StandardEvaluationContext();
|
for (Field field : Objects.requireNonNull(ReflectUtils.removeStaticField(fields))) {
|
field.setAccessible(true);
|
|
String name = field.getName();
|
Class<?> type = field.getType();
|
String typeName = type.getSimpleName();
|
if (typeName.equals("Struct")) {
|
if (entity.getClassType(name) != null) {
|
type = entity.getClassType(name);
|
typeName = type.getSimpleName();
|
} else if (field.get(entity) != null) {
|
type = field.get(entity).getClass();
|
typeName = type.getSimpleName();
|
}
|
}
|
|
size size = field.getAnnotation(size.class);
|
bit bit = field.getAnnotation(bit.class);
|
expr expr = field.getAnnotation(expr.class);
|
flag flag = field.getAnnotation(flag.class);
|
if (size == null && bit == null && expr == null) {
|
continue;
|
}
|
|
int bited = 0;//已写位数
|
int length = 0;//定义的长度
|
if (size != null) {
|
length = size.value();
|
bited += size.value() * 8;
|
} else if (bit != null) {
|
//按位写方式未实现处理
|
throw new RuntimeException("按位写方式未实现处理");
|
} else if (expr != null) {
|
ExpressionParser parser = new SpelExpressionParser();
|
Expression exp = parser.parseExpression(expr.value());
|
Integer len = null;
|
try {
|
len = CommonUtils.parseInt(exp.getValue(context, entity));
|
} catch (Exception e) {
|
len = 0;
|
}
|
if (len != null) {
|
if (len == -1) continue;
|
length = len;
|
bited += len * 8;
|
}
|
}
|
context.setVariable(name, field.get(entity));
|
|
byte[] bytes = new byte[length];
|
|
if (typeName.equals("int") || typeName.equals("Integer")) {
|
dos.write(bytes = convert(length, field.getInt(entity)));
|
} else if (typeName.equals("long") || typeName.equals("Long")) {
|
dos.write(bytes = convert(length, field.getLong(entity)));
|
} else if (typeName.equals("short") || typeName.equals("Short")) {
|
dos.write(bytes = convert(length, field.getShort(entity)));
|
} else if (typeName.equals("byte") || typeName.equals("Byte")) {
|
dos.write(bytes = convert(length, field.getByte(entity)));
|
} else if (typeName.equals("String")) {
|
String str = (String) field.get(entity);
|
if (str == null)
|
str = "";
|
str = str.replaceAll(" ", " ");
|
|
byte[] bts = null;
|
if (flag != null && flag.value().equals("ASCII")) {
|
bts = Struct.StrToAsc(str);
|
} else if (flag != null && flag.value().equals("BCD")) {
|
bts = Struct.str2Bcd(str);
|
} else {
|
bts = str.getBytes();
|
}
|
|
if (length == 0) {//如果长度未设置,以当前实际的字节为准
|
bytes = new byte[bts.length];
|
}
|
System.arraycopy(bts, 0, bytes, 0, bts.length);
|
dos.write(bytes);
|
} else if (typeName.equals("byte[]")) {
|
byte[] bts = (byte[]) field.get(entity);
|
if (bts != null) {
|
if (length == 0) {//如果长度未设置,以当前实际的字节为准
|
bytes = new byte[bts.length];
|
}
|
System.arraycopy(bts, 0, bytes, 0, bts.length);
|
dos.write(bytes);
|
}
|
} else if (type.isArray()) {
|
ByteArrayOutputStream baOs1 = new ByteArrayOutputStream();
|
DataOutputStream dos1 = new DataOutputStream(baOs1);
|
for (Object object : (Object[]) field.get(entity)) {
|
write(dos1, (Struct) object);
|
}
|
byte[] bts = baOs1.toByteArray();
|
if (length == 0) {//如果长度未设置,以当前实际的字节为准
|
bytes = new byte[bts.length];
|
}
|
System.arraycopy(bts, 0, bytes, 0, bts.length);
|
dos.write(bytes);
|
} else {
|
byte[] bts = encode((Struct) field.get(entity));
|
if (length == 0) {//如果长度未设置,以当前实际的字节为准
|
bytes = new byte[bts.length];
|
}
|
System.arraycopy(bts, 0, bytes, 0, bts.length);
|
dos.write(bytes);
|
}
|
entity.encodeByteMapping.put(name, bytes);
|
}
|
}
|
|
public byte[] convert(int size, long value) throws Exception {
|
ByteArrayOutputStream baOs = new ByteArrayOutputStream();
|
DataOutputStream dos = new DataOutputStream(baOs);
|
switch (size) {
|
case 1://byte
|
dos.write((byte) value);
|
break;
|
case 2://short
|
dos.writeShort(this.isReverse() ? Short.reverseBytes((short) value) : (short) value);
|
break;
|
case 4://int
|
dos.writeInt(this.isReverse() ? Integer.reverseBytes((int) value) : (int) value);
|
break;
|
case 8://long
|
dos.writeLong(this.isReverse() ? Long.reverseBytes((long) value) : (long) value);
|
break;
|
}
|
return baOs.toByteArray();
|
}
|
|
public long convertEx(long value, int size) throws Exception {
|
switch (size) {
|
case 1://byte
|
return value;
|
case 2://short
|
return (this.isReverse() ? Short.reverseBytes((short) value) : (short) value);
|
case 4://int
|
return (this.isReverse() ? Integer.reverseBytes((int) value) : (int) value);
|
case 8://long
|
return (this.isReverse() ? Long.reverseBytes((long) value) : (long) value);
|
}
|
return 0;
|
}
|
|
/**
|
* 解码: 字节数组 ====>> java对象
|
*/
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
public <T extends Struct> T decode(byte[] bytes) throws Exception {
|
this.dataBytes = bytes;
|
Struct instance = this;
|
Class<? extends Struct> cls = this.getClass();
|
Field[] fields = cls.getFields();
|
if (this.decodeByteMapping == null) instance.decodeByteMapping = new HashMap();
|
// spring el表达式
|
EvaluationContext context = new StandardEvaluationContext();
|
context.setVariable("_", bytes.length);
|
int bited = 0;//已读位数
|
for (Field field : Objects.requireNonNull(ReflectUtils.removeStaticField(fields))) {
|
size size = field.getAnnotation(size.class);
|
bit bit = field.getAnnotation(bit.class);
|
expr expr = field.getAnnotation(expr.class);
|
flag flag = field.getAnnotation(flag.class);
|
// 没有size、bit、expr注解的变量不进行解析
|
if (size == null && bit == null && expr == null) {
|
continue;
|
}
|
String name = field.getName();
|
Class<?> type = field.getType();
|
String typeName = type.getSimpleName();
|
if (typeName.equals("Struct")) {
|
if (instance.getClassType(name) != null) {
|
type = instance.getClassType(name);
|
typeName = type.getSimpleName();
|
}
|
}
|
context.setVariable("__", bytes.length - bited / 8);
|
byte[] bts = null;
|
if (size != null) {
|
bts = new byte[size.value()];
|
System.arraycopy(bytes, bited / 8, bts, 0, size.value());
|
bited += size.value() * 8;
|
} else if (bit != null) {
|
int val = 0;
|
String strBin = "";
|
for (int i = bited; i < bited + bit.value(); i++) {//逐位扫描
|
if ((bytes[i / 8] & (1 << (i % 8))) != 0) {//如果位为1
|
strBin += "1";
|
} else {
|
strBin += "0";
|
}
|
val = Integer.parseInt(strBin, 2);
|
}
|
byte[] result = new byte[4];
|
result[3] = (byte) (val);
|
result[2] = (byte) (val >> 8);
|
result[1] = (byte) (val >> 16);
|
result[0] = (byte) (val >> 24);
|
bts = new byte[(int) Math.ceil((double) bit.value() / 8.0)];
|
System.arraycopy(result, result.length - bts.length, bts, 0, bts.length);
|
bited += bit.value();
|
} else if (expr != null) {
|
ExpressionParser parser = new SpelExpressionParser();
|
Expression exp = parser.parseExpression(expr.value());
|
Integer len = CommonUtils.parseInt(exp.getValue(context, instance));
|
if (len != null) {
|
if (len == -1) continue;
|
bts = new byte[len];
|
System.arraycopy(bytes, bited / 8, bts, 0, len);
|
bited += len * 8;
|
}
|
}
|
if (bts.length == 0) continue;
|
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bts));
|
if (typeName.equals("int") || typeName.equals("Integer")) {
|
field.setInt(instance, (int) convertEx(dis.readInt(), 4));
|
} else if (typeName.equals("long") || typeName.equals("Long")) {
|
field.setLong(instance, convertEx(dis.readLong(), 8));
|
} else if (typeName.equals("short") || typeName.equals("Short")) {
|
field.setShort(instance, (short) convertEx(dis.readShort(), 2));
|
} else if (typeName.equals("byte") || typeName.equals("Byte")) {
|
field.setByte(instance, (byte) convertEx(dis.readByte(), 1));
|
} else if (typeName.equals("byte[]")) {
|
field.set(instance, bts);
|
} else if (typeName.equals("String")) {
|
String strStr = new String(bts, StandardCharsets.UTF_8);
|
if (flag != null && flag.value().equals("BCD")) {
|
strStr = bcd2Str(bts);
|
} else if (flag != null && flag.value().equals("ASCII")) {
|
strStr = Struct.AscToStr(bts);
|
}
|
strStr = strStr.replaceAll("[\u0000]", "");
|
field.set(instance, strStr);
|
} else if (type.isArray()) {
|
byte[] arrayBytes = bts;
|
List list = new ArrayList();
|
while (arrayBytes.length > 0 && list.size() < 256) {
|
Struct item = decode((Class<? extends Struct>) type.getComponentType(), arrayBytes);
|
list.add(item);
|
arrayBytes = item.unusedBytes;
|
}
|
Object array = Array.newInstance(type.getComponentType(), list.size());
|
for (int i = 0; i < list.size(); i++) {
|
Array.set(array, i, list.get(i));
|
}
|
field.set(instance, array);
|
} else if (type == Struct.class || Struct.class.isAssignableFrom(type)) {
|
byte[] arrayBytes = bts;
|
Struct item = decode((Class<? extends Struct>) type, arrayBytes);
|
field.set(instance, item);
|
} else {
|
throw new RuntimeException(cls.getSimpleName() + "字段" + field.getName() + "类型未知");
|
}
|
instance.decodeByteMapping.put(name, bts);
|
context.setVariable(name, field.get(instance));
|
}
|
byte[] useBytes = new byte[bited / 8];
|
System.arraycopy(bytes, 0, useBytes, 0, bited / 8);
|
instance.decodeBytes = useBytes;
|
byte[] unusedBytes = new byte[bytes.length - (bited / 8)];
|
if (unusedBytes.length > 0) {
|
System.arraycopy(bytes, useBytes.length, unusedBytes, 0, unusedBytes.length);
|
}
|
instance.unusedBytes = unusedBytes;
|
return (T) instance;
|
}
|
|
public static class TestMessage {
|
public @size(1) byte begin = 0x7e;//起始符
|
public @size(2) short type = 0;//消息ID
|
public @bit(2) byte retain;//消息体属性>保留
|
public @bit(1) byte subpkg;//消息体属性>是否分包,0表示不分包
|
public @bit(3) byte encrypt;//消息体属性>加密方式,0表示不加密
|
public @bit(10) short length;//消息体长度
|
public @size(6)
|
@flag("BCD") String number;//手机号码
|
public @size(2) short serial;//消息流水号
|
public @expr("#subpkg==1?2:0") short pkgs;//分包数
|
public @expr("#subpkg==1?2:0") short index;//包序号
|
public @expr("#length") byte[] data;//数据部分
|
public @size(1) byte verify;//校验码
|
public @size(1) byte end = 0x7e;//结束符
|
}
|
|
}
|