报文
协议
| 名称 | 定义 |
|---|
| 起始标志符 | EB90(小头字节序) |
| 发送会话序列号 | long(八字节小头字节序) |
| 接收会话序列号 | long(八字节小头字节序) |
| 会话源标识 | 0x00(一个字节) |
| xml的字节长度 | int(四字节小头字节序) |
| 交互内容(xml格式) | xml,字符编码为UTF-8 |
| 结束标志符号 | EB90(小头字节序) |
交互内容xml格式
| 名称 | 说明 |
|---|
| SendCode | 发送方唯一标识 |
| ReceiveCode | 接收方唯一标识 |
| Type | 消息类型 |
| Code | 目标对象唯一标识 |
| Time | 时间(格式:yyyy-MM-dd hh:mm:ss) |
| Items | 消息内容(可选) |
示例
<?xml version="1.0" encoding="UTF-8"?>
<PatrolHost>
<SendCode>Server01</SendCode>
<ReceiveCode>Client01</ReceiveCode>
<Type>251</Type>
<Code>200</Code>
<Command>4</Command>
<Time>2023-01-01 12:02:35</Time>
<Items>
<Item name="name01" age="12" group="group01" />
<Item name="name02" age="22" group="group02" />
</Items>
</PatrolHost>
实现
需求
- 能通过注解来定制xml的节点名称
- 不操作document,而是通过序列化,反序列化来操作实体
工具类
- JAXB
- 支持注解来定义xml节点名称
- 不操作document,通过对象来实现,序列化与反序列化
代码
// 最外层协议
public abstract class TcpMessage{
public TcpMessage(){}
public TcpMessage(long sessionId, long receiveSessionId, int sessionType, int xmlLength, byte[] xmlBody){
this.sessionId = sessionId
this.receiveSessionId = receiveSessionId
this.sessionType = sessionType
this.xmlLength = xmlLength
this.xmlBody = xmlBody
}
// 发送会话id
private long sessionId
// 接收会话id
private long receiveSessionId
// 会话源标识
private int sessionType
// xml报文长度
private int xmlLength
// xml报文内容
private byte[] xmlBody
}
// xml实体
@XmlRootElement(name="PatrolHost") // 指定xml根结点名字为PatrolHost
@XmlAccessorType(XmlAccessType.PROPERTY) //xml序列化时,根据属性来序列化
@XmlType(propOrder={"sendCode","receiveCode","type","code","command","time","items"}) // 序列化的顺序
public class XmlBody {
// 发送方唯一标识
private String sendCode
// 接收方唯一标识
private String receiveCode
// 消息类型
private String type
// 消息编码
private String code
// 消息命令行编码
private String command
// 发送时间
private String time
// 消息items项
private List<HashMap<String,String>> items
@XmlElement(name="SendCode") // 属性序列化时的名字
public String getSendCode(){ return sendCodel
public void setSendCode(String sendCode){this.sendCode = sendCode
@XmlElement(name="ReceiveCode") // 属性序列化时的名字
public String getReceiveCode(){return receiveCode
public void setreceiveCode(){this.receiveCode = receiveCode
@XmlElement(name="Type") // 属性序列化时的名字
public String getType(){return type
public void setType(String type){this.type=type
@XmlElement(name="Code") // 属性序列化时的名字
public String getCode(){return code
public void setCode(String code){this.code = code
@XmlElement(name="Command") // 属性序列化时的名字
public String getCommand(){return command
public void setCommand(String command){this.command = command
@XmlElement(name="Time") // 属性序列化时的名字
public String getTime(){return time
public void setTime(String time){this.time = time
@XmlElement(name="Time") // 属性序列化时的名字
// 如果要序列化Map,那么就必须是实际类型,不可用接口,所以这里是HashMap
@XmlJavaTypeAdapter(ItemsXmlAdapter.class) // List<HashMap<String,String>>的序列化器(需要自己实现)
public List<HashMap<String,String>> getItems{return items
public void setItems(List<HashMap<String,String>> items){this.items = items
}
// List<HashMap<String,String>>的序列化器
public class ItemXmlAdapter extends Xmladapter<Object, List<HashMap<String,String>>>{
// 反序列化方法
@Override
public List<HashMap<String,String>> unmarshal(Object rowsElement) throws Exception{
if(rowsElement==null){
return null
}
Element rowsEle = (Element) rowsElement
// 判断是否有HashMap数据
NodeList rowNodes = rowsEle.getChildNodes()
int rowCount = rowNodes.getLength()
if(rowCount==0){
return null
}
// 转换List<HashMap<String,String>>
List<HashMap<String,String>> result = new ArrayList<>()
for(int i=0
Node rowNode = rowNodes.item(i)
// 只查询item节点,排除其余节点
if(!rowNode.getNodeName().equalsIgnoreCase("item")){
continue
}
// 判断item中是否有attribute
NamedNodeMap attributes = rowNode.getAttributes()
if(attributes.getLength()==0){
continue
}
HashMap<String,String> map = new HashMap<>()
for(int j=0
Node item = attributes.item(j)
String key = item.getNodeName()
String value = item.getNodeValue()
map.put(key,value)
}
result.add(map)
}
return result
}
// 序列化
@Override
public Object marshal(List<HashMap<String,String>> items) throws Exception{
// 最外层的Items
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance()
DocumentBuilder db = dbf.newDocumentBuilder()
Document document = db.newDocument()
Element rootElement = document.createElement("Items")
document.appendChild(rootElement)
// Items中添加Item
if(CollectionUtils.isNotEmpty(items)){
for(HashMap<String,String> map : items){
if(CollectionUtils.isEmpty(map)){
continue
}
/**
* 组装数据
* <Items>
* <Item user="" name="" group="" />
* </Items>
Element itemElement = document.createElement("Item")
rootElement.appendChild(itemElement)
for(Map.Entry<String,String> entry : map.entrySet()){
Attr attr = document.createAttribute(entry.getKey())
attr.setValue(entry.getValue())
itemElement.setAttributeNode(attr)
}
}
}
return rootElement
}
}
// 序列化方法
public interface Serializer{
<T> T deserialize(Class<T> clazz, byte[] bytes)
<T> byte[] serialize(T object, Class<T> clazz)
enum Algorithm implements Serializer{
XML{
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes){
JAXBContext jaxbContext = null
// 我这里会有很多对象派生自XmlBody,所以有这个if分支。如果你不需要派生类,就不用写这个if分支了,直接else分支即可
if(XmlBody.class.isAssignableFrom(clazz)){
jaxbContext = JAXBContext.newInstance(XmlBody.class, clazz)
}else{
jaxbContext = JAXBContext.newInstance(clazz)
}
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller()
InputStream inputstream = new ByteArrayInputStream(bytes)
return (T) unmarshaller.unmarshal(inputStream)
}
@Override
public <T> byte[] serialize(T object, Class<T> clazz){
JAXBContext jaxbContext = JAXBContext.newInstance(clazz)
Marshaller marshaller = jaxbContext.createMarshaller()
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
marshaller.setProperty(Marshaller.JAXB_FORGMENT, true)
StringWriter writer = new StringWriter()
// 这样代码就是为了去掉一个xml自动序列化时,头文件中会多一个元素(这个元素并不会影响序列化与反序列化,我这里就是为了完全与定义的报文一致,所以才去掉)
writer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+"\n")
marshaller.marshal(object, writer)
return writer.toString().getBytes()
}
}
}
}