一、8583协议简介
8583协议是基于ISO8583报文国际标准的包格式的通讯协议,8583包最多由128个字段域组成,每个域都有统一的规定,并有定长与变长之分。8583包前面一段为位图,它是组包解包确定字段域的关键。
二、位图规则
位图是8583报文组包和解包的关键。我们将位图转换为二进制字符串来分析,如果第一位为1,表示报文有128个域,如果第一位为0,则表示报文有64个域,其它第几位为1,就代表哪个域有值。
三、8583格式报文参考
以下边这段报文为例:
0075082082200001000000000400000010000000030314584300004208030900003010804025370
分析:
(1)“0075”:报文长度
(2)“0820”:消息类型
(3)“82200001000000000400000010000000”:BITMAP(位图,十六进制,转换为二进制为:10000010001000000000000000000001000000000000000000000000000000000000010000000000000000000000000000010000000000000000000000000000。位图转换为二进制后,总长度为128,第一位1表示报文有128个域,后边第七、十一、三十二、七十、一百位为1,表示这些域有值)
(4)“0303145843”:第7域,传输时间
(5)“000042”:第11域,系统跟踪号
(6)“0803090000”:第32域,受理机构/银行标识码(08为长度,03090000为实际内容)
(7)“301”:第70域,网络管理信息码
(8)“0804025370”:第100域,接收机构/银行标识码(08为长度,04025370为实际内容)
四、组包/解包思路
假设我们现在要实现一个128域报文的组包和解包功能。
组包思路:
1、定义一个报文类,其中128个域就是我们的128个类属性;
2、定义一个属性注解,用来将类属性的域索引、字段长度以及字段是定长还是变长标注清楚;
3、通过反射拿到类的所有字段,并按照注解中的域索引从小到大进行排序;
4、循环解析对象的每个属性,如果属性有值,则对应的将二进制位图的第几位修改为1,同时按照注解中定义的字段长度以及字段是定长还是变长进行处理,将处理完的信息进行拼接。
5、拿到第4步拼接完的报文信息,按照(4位报文长度 + 4位消息类型 + 32位BITMAP + 报文信息)这个顺序,将全部信息拼接。
解包思路:
1、2、3、同上;
4、报文第1 ~ 4位为报文长度;第5 ~ 8位为消息类型;第9 ~ 40位为十六进制位图信息;第41 ~ 末尾为报文信息。拿到十六进制位图信息后,将其转换为二进制字符串,从第二位开始循环二进制字符串的每一位,如果为1,找到对应索引的属性,根据字段长度以及字段是定长还是变长读取报文,通过反射给对应的属性赋值。
五、相关代码
ISO8583DTO128 报文类(这里只示意几个属性)
/**
* 报文交互DTO,128域
*/
@Data
public class ISO8583DTO128 {
/**
* 数据类型
*/
private String messageType;
/**
* 域7交易传输时间(TRANSMISSION-DATE-AND-TIME)
* n10,10位定长数字字符;
* 格式:MMDDhhmmss
* 交易发起方的系统工作日日期和时间。
*/
@ISO8583Annotation(
fldIndex = 7, dataFldLength = 10, fldFlag = FldFlag.FIXED
)
private String transmissionDateAndTime7;
/**
* 域11系统跟踪号(SYSTEM-TRACE-AUDIT-NUMBER)
* n6,6位定长数字字符
* 受理方赋予交易的一组数字,用于唯一标识一笔交易的编号。
*/
@ISO8583Annotation(
fldIndex = 11, dataFldLength = 6, fldFlag = FldFlag.FIXED
)
private String systemTraceAuditNumber11;
/**
* 域32受理机构标识码(ACQUIRING-INSTITUTION-DENTIFICATION-CODE)
* n..12(LLVAR)
* 2个字节的长度值+最大12个字节(数字字符)的受理方标识码
*/
@ISO8583Annotation(
fldIndex = 32, dataFldLength = 12, fldFlag = FldFlag.UNFIXED_2
)
private String acquiringInstitutionDentificationCode32;
/**
* 域70网络管理信息码 (NETWORK-MANAGEMENT-INFORMATION-CODE)
* n3,3位定长数字字符;
* 网络业务管理功能码;
*/
@ISO8583Annotation(
fldIndex = 70, dataFldLength = 3, fldFlag = FldFlag.FIXED
)
private String networkManagementInformationCode70;
/**
* 域100接收机构标识码(DESTINATION-INSTITUTION-IDENTIFICATION-CODE)
* n..12(LLVAR),2个字节的长度值+最大12个字节(数字字符)的接收方标识码;
* 在消息中表示消息接收方机构的标识;
*/
@ISO8583Annotation(
fldIndex = 100, dataFldLength = 12, fldFlag = FldFlag.UNFIXED_2
)
private String destinationInstitutionIdentificationCode100;
}
ISO8583Annotation 字段域注解类
/**
* ISO8583字段域注解类
*/
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ISO8583Annotation {
/**
* 域索引
* */
int fldIndex();
/**
* 域字段标识
* FIXED:固定长度;UNFIXED_2:2位变长;UNFIXED_3:3位变长
* */
FldFlag fldFlag();
/**
* 数据域长度
* */
int dataFldLength();
}
FldFlag 注解中用到的枚举
/**
* 域字段标识
*/
public enum FldFlag {
/** 固定长度 */
FIXED,
/** 2位变长 */
UNFIXED_2,
/** 3位变长 */
UNFIXED_3
}
ISO8583Util 报文组包/解包工具类
/**
* ISO8583报文组包/解包工具类
*/
public class ISO8583Util {
private ISO8583Util() {
}
/**
* 128域组包
* @param iso8583DTO128 报文交互DTO,128域
* 4位报文长度 + 4位消息类型 + 32位BITMAP + 报文信息
* @return
*/
public static String packISO8583DTO128(ISO8583DTO128 iso8583DTO128) throws IncorrectLengthException {
StringBuilder sendMsg = new StringBuilder();
// 先拼接消息类型
sendMsg.append(iso8583DTO128.getMessageType());
// 拼接BITMAP + 报文信息
sendMsg.append(getBitMapAndMsg(iso8583DTO128, 128));
// 计算报文长度,长度占4个字节,不足4字节左补0
int sendMsgLen = sendMsg.length();
String sendMsgLenStr = Integer.toString(sendMsgLen);
sendMsgLenStr = NumberStringUtil.addLeftChar(sendMsgLenStr, 4, '0');
// 将4位报文长度插到最前边
sendMsg.insert(0,sendMsgLenStr);
return sendMsg.toString();
}
/**
* 128域解包
* @param receivedMsg 收到的报文消息
* 4位报文长度 + 4位消息类型 + 32位BITMAP + 报文信息
* @return
* @throws IncorrectMessageException
*/
public static ISO8583DTO128 unpackISO8583DTO128(String receivedMsg) throws IncorrectMessageException{
if(null == receivedMsg){
throw new IncorrectMessageException("报文为空");
}
int totalLen = receivedMsg.length();
if(totalLen < 40){
throw new IncorrectMessageException("报文格式不正确,报文长度最少为40");
}
int msgLen = Integer.valueOf(receivedMsg.substring(0,4));
if(msgLen != totalLen - 4){
throw new IncorrectMessageException("报文长度不匹配");
}
String messageType = receivedMsg.substring(4,8);
String hexBitMap = receivedMsg.substring(8,40);
String binaryBitMap = NumberStringUtil.hexToBinaryString(hexBitMap);
String[] binaryBitMapArgs = binaryBitMap.split("");
String msg = receivedMsg.substring(40);
ISO8583DTO128 iso8583DTO128 = (ISO8583DTO128) msgToObject(ISO8583DTO128.class, binaryBitMapArgs, msg);
iso8583DTO128.setMessageType(messageType);
return iso8583DTO128;
}
private static String getBitMapAndMsg(Object iso8583DTO, int bitLen) throws IncorrectLengthException {
// 获取ISO8583DTO类的属性,key为fldIndex域序号,value为属性名
Map<Integer, String> iso8583DTOFldMap = getISO8583DTOFldMap(iso8583DTO.getClass());
// 初始化域位图
StringBuffer bitMap = initBitMap(bitLen);
// 获取有值的域,并生成位图
PropertyDescriptor propertyDescriptor;
String fldName;
String fldValue;
// 按照格式处理后的值
String fldSendValue;
// 将每个域对应的值,保存到对应下标中
String[] fldSendValues = new String[bitLen];
try {
// 循环判断哪个字段有值
for(Map.Entry<Integer, String> entry : iso8583DTOFldMap.entrySet()){
fldName = entry.getValue();
propertyDescriptor = new PropertyDescriptor(fldName, iso8583DTO.getClass());
fldValue = (String) propertyDescriptor.getReadMethod().invoke(iso8583DTO);
if(StringUtils.isNotEmpty(fldValue)){
// 如果此域有值,将对应的位图位置修改为1
bitMap = bitMap.replace(entry.getKey()-1,entry.getKey(),"1");
// 根据注解对值进行处理
Field field = iso8583DTO.getClass().getDeclaredField(fldName);
fldSendValue = verifyAndTransValue(field,fldValue);
fldSendValues[entry.getKey()-1] = fldSendValue;
}
}
} catch (IncorrectLengthException e) {
throw e;
} catch (Exception e){
logger.log(LOG_CODE, "对象序列化报错", e);
return "";
}
// 将128位2进制位图转换为32位16进制数据
String bitMapHexStr = NumberStringUtil.binaryToHexString(bitMap.toString());
StringBuffer bitMapAndMsg = new StringBuffer();
// 位图在前,先拼接位图
bitMapAndMsg.append(bitMapHexStr);
// 拼接报文数据
for(String value : fldSendValues){
if(StringUtils.isNotEmpty(value)){
bitMapAndMsg.append(value);
}
}
return bitMapAndMsg.toString();
}
/**
* 获取ISO8583DTO类的属性,key为fldIndex,value为属性名
* @param clazz
* @return
*/
private static Map<Integer, String> getISO8583DTOFldMap(Class clazz) {
Map<Integer, String> map = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
boolean fldHasAnnotation = field.isAnnotationPresent(ISO8583Annotation.class);
if (fldHasAnnotation) {
ISO8583Annotation fldAnnotation = field.getAnnotation(ISO8583Annotation.class);
map.put(fldAnnotation.fldIndex(), field.getName());
}
}
return map;
}
private static Object msgToObject(Class clazz, String[] binaryBitMapArgs, String msg) {
try{
// 返回对象
Object retObject = clazz.newInstance();
// 获取ISO8583DTO类的属性,key为fldIndex域序号,value为属性名
Map<Integer, String> iso8583DTOFldMap = getISO8583DTOFldMap(clazz);
int indexFlag = 0;
String fldName;
Field field;
ISO8583Annotation fldAnnotation;
FldFlag fldFlag;
int dataLength;
String fldValue;
// 循环位图,第一位表示64域或者128域,所以从第二位开始
for(int i=1,len=binaryBitMapArgs.length; i<len; i++){
if(Objects.equals(binaryBitMapArgs[i], "0")){
// 0表示没有值,跳过
continue;
}
// 位图下标从1开始,所以需要+1
fldName = iso8583DTOFldMap.get(i+1);
field = clazz.getDeclaredField(fldName);
fldAnnotation = field.getAnnotation(ISO8583Annotation.class);
fldFlag = fldAnnotation.fldFlag();
if(Objects.equals(FldFlag.FIXED, fldFlag)){
dataLength = fldAnnotation.dataFldLength();
}else if(Objects.equals(FldFlag.UNFIXED_2, fldFlag)){
dataLength = Integer.valueOf(msg.substring(indexFlag, indexFlag=indexFlag+2));
}else if(Objects.equals(FldFlag.UNFIXED_3, fldFlag)){
dataLength = Integer.valueOf(msg.substring(indexFlag, indexFlag=indexFlag+3));
}else{
//未知类型,不做处理
continue;
}
fldValue = msg.substring(indexFlag,indexFlag+dataLength);
indexFlag += dataLength;
field.setAccessible(true);
field.set(retObject,fldValue);
}
return retObject;
}catch (Exception e){
logger.log(LOG_CODE, "对象反序列化报错", e);
return null;
}
}
/**
* 组包时根据字段原值按照其配置规则转为十六进制 PACK
* @param field
* @param fldValue 字段值
* @exception Exception .
* */
private static String verifyAndTransValue(Field field, String fldValue)
throws IncorrectLengthException {
boolean fldHasAnnotation = field.isAnnotationPresent(ISO8583Annotation.class);
if (!fldHasAnnotation) {
return fldValue;
}
ISO8583Annotation iso8583Annotation = field.getAnnotation(ISO8583Annotation.class);
FldFlag fldFlag = iso8583Annotation.fldFlag();
int expectLen = iso8583Annotation.dataFldLength();
int actualLen = fldValue.length();
// 固定长度,则校验一下长度是否一致
if(Objects.equals(FldFlag.FIXED, fldFlag)){
if(actualLen != expectLen){
String msg = String.format("%s长度不正确,期望长度为[%d],实际长度为[%d]。"
,field.getName(),expectLen,actualLen);
throw new IncorrectLengthException(msg);
}
return fldValue;
}
// 可变长度,校验一下长度是否超过上限。如果长度符合,则在前边拼接长度值
if (Objects.equals(FldFlag.UNFIXED_2,fldFlag) || Objects.equals(FldFlag.UNFIXED_3,fldFlag)) {
if(actualLen > expectLen){
String msg = String.format("%s长度不正确,最大长度为[%d],实际长度为[%d]。"
,field.getName(),expectLen,actualLen);
throw new IncorrectLengthException(msg);
}
int len = 2;
if(Objects.equals(FldFlag.UNFIXED_3,fldFlag)){
len = 3;
}
// 在报文前边拼接长度
fldValue = NumberStringUtil.addLeftChar(String.valueOf(actualLen),len, '0') + fldValue;
return fldValue;
}
return fldValue;
}
/**
* 初始64和128域位图。
* 64域的全是0;128域的除第一位为1,其它位全为0
* @param d
* @return
*/
private static StringBuffer initBitMap(int d) {
StringBuffer bf = new StringBuffer();
if(d != 64 && d != 128){
return bf;
}
if(d == 64){
bf.append("0");
}else{
// 128域的第一位为"1"
bf.append("1");
}
for(int i = 1; i < d; i++){
bf.append("0");
}
return bf;
}
}
NumberStringUtil 字符工具类
/**
* 字符工具类
*/
public class NumberStringUtil {
public NumberStringUtil() {
}
/**
* 2进制字符串转16进制字符串
* @param binaryStr
* @return
*/
public static String binaryToHexString(String binaryStr){
StringBuffer bf = new StringBuffer();
String hexString;
Integer temInt;
// 每四位2进制,对应一位16进制,并从小位开始计算
for(int i= binaryStr.length(); i>0; i=i-4){
temInt = Integer.valueOf(binaryStr.substring(i-4<0?0:i-4,i), 2);
hexString = Integer.toHexString(temInt);
bf.insert(0, hexString);
}
return bf.toString();
}
/**
* 16进制字符串转2进制字符串
* @param hexString
* @return
*/
public static String hexToBinaryString(String hexString){
StringBuffer bf = new StringBuffer();
String[] hexValues = hexString.split("");
String binaryString;
// 每一位16进制,对应四位2进制
for(String hexValue : hexValues){
binaryString = Integer.toBinaryString(Integer.valueOf(hexValue,16));
// 如果不足4位,左补0
binaryString = addLeftChar(binaryString,4,'0');
bf.append(binaryString);
}
return bf.toString();
}
/**
* 左补字符至指定长度
* @param str 原字符串
* @param length 需要补到的长度
* @param c 补位的字节
* @return
*/
public static String addLeftChar(String str, int length, char c) {
str = (str == null) ? "" : str;
StringBuilder sb = new StringBuilder(str);
int num = length - str.length();
for (int i = 0; i < num; i = i + 1) {
sb.insert(0, c);
}
return sb.toString();
}
/**
* 右补字符至指定长度
* @param str 原字符串
* @param length 需要补到的长度
* @param c 补位的字节
* @return
*/
public static String addRightChar(String str, int length, char c) {
str = (str == null) ? "" : str;
StringBuilder sb = new StringBuilder(str);
int num = length - str.length();
for (int i = 0; i < num; i = i + 1) {
sb.append(c);
}
return sb.toString();
}
}
IncorrectLengthException 长度不正确异常
/**
* 长度不正确异常
*/
@Data
public class IncorrectLengthException extends Exception{
private String msg;
public IncorrectLengthException(String msg) {
this.msg = msg;
}
}
IncorrectMessageException 报文格式不正确异常
/**
* 报文格式不正确异常
*/
@Data
public class IncorrectMessageException extends Exception{
private String msg;
public IncorrectMessageException(String msg) {
this.msg = msg;
}
}
以上,代码就齐了,使用的时候,只需要调组包和解包方法就可以了。
public static void main(String[] args) {
ISO8583DTO128 iso8583DTO128 = new ISO8583DTO128();
iso8583DTO128.setMessageType("0820");
iso8583DTO128.setTransmissionDateAndTime7("0303145843");
iso8583DTO128.setSystemTraceAuditNumber11("000042");
iso8583DTO128.setAcquiringInstitutionDentificationCode32("03090000");
iso8583DTO128.setNetworkManagementInformationCode70("301");
iso8583DTO128.setDestinationInstitutionIdentificationCode100("04025370");
String sendMsg;
try {
// 组包
sendMsg = ISO8583Util.packISO8583DTO128(iso8583DTO128);
System.out.println(sendMsg);
// 解包
ISO8583DTO128 iso8583DTO1281 = ISO8583Util.unpackISO8583DTO128(sendMsg);
System.out.println(iso8583DTO1281.toString());
} catch (IncorrectLengthException e) {
System.out.println(e.getMsg());
} catch (IncorrectMessageException e) {
System.out.println(e.getMsg());
}
}