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

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章