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