Java版ISO8583报文组包/解包

一、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());
	}

}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓呆同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值