我的java版银联8583协议解析库,超简单,超直观的实现及示例

一直以来做嵌入式软件开发,跟银联8583协议通信打交道太多了。

最近有需要把8383协议的解析用到android上,但是搜遍了整个互联网,没发现有哪个简单好用点的java版8583解析库。就自己动手自己做一个吧,让其尽可能的简单,直观

如果在这个互联网上谁遇到过比这个还简单直观的,请留言我,我观摩下再进一步改进。

来做个对比,J8583CN :中国版的8583报文Java实现,实现了对8583报文创建、编辑、读写、解析。使用起来比较简单,且能灵活配置。 j8583cn 的报文标准参考了中国银联2.0和部分商行的标准。代码参考了源j8583程序

附带J8583CN的下载链接:https://sourceforge.net/projects/j8583cn/?source=typ_redirect

这份代码我下载下来了看,在懂8383协议的基础上一时半会儿也没看明白怎么用的,也不想花时间研究他了

我的github地址

https://github.com/yangyongzhen/Easy8583Ans.git

以下来看一个银联签到报文的组包:

不用关注BitMap如何填,如何组织,不用关注报文结构和长度,只需要根据协议填 你需要的域就行啦!

就是这么简单,filed[0] 到filed[63] 分别对应 1到 64域。

有多么简单?有多么直观? 

请看以下签到报文组包示例:

public void frame8583QD( __8583Fields[] field, Pack tx){

    //消息类型
    tx.MsgType[0] = 0x08;
    tx.MsgType[1] = 0x00;
    //11域,受卡方系统跟踪号BCD 通讯流水
    field[10].ishave = 1;
    field[10].len = 3;
    String tmp = String.format("%06d",commSn);
    field[10].data = hexStringToBytes(tmp);
    //41域,终端号
    field[40].ishave = 1;
    field[40].len = 8;
    field[40].data = PosNum.getBytes();
    //42域,商户号
    field[41].ishave = 1;
    field[41].len = 15;
    field[41].data = ManNum.getBytes();
    //60域
    field[59].ishave = 1;
    field[59].len = 0x11;
    field[59].data = new byte[6];
    field[59].data[0] = 0x00;
    arraycopy(piciNum,0,field[59].data,1,3);
    field[59].data[4] = 0x00;
    field[59].data[5] = 0x30;
    //62域
    field[61].ishave = 1;
    field[61].len = 0x25;
    field[61].data = new byte[25];
    String str = "Sequence No12";
    arraycopy(str.getBytes(),0,field[61].data,0,13);
    arraycopy(licenceNum,0,field[61].data,13,4);
    arraycopy(PosNum.getBytes(),0,field[61].data,17,8);
    //63域
    field[62].ishave = 1;
    field[62].len = 0x03;
    field[62].data = new byte[3];
    field[62].data[0] = 0x30;
    field[62].data[1] = 0x30;
    field[62].data[2] = 0x31;
    /*报文组帧,自动组织这些域到Pack的TxBuffer中*/
    pack8583Fields(field,tx);
}

最终组织好的报文在哪呢?在报文结构类Pack中的Txbuffer中,长度为Txlen

关键方法

pack8583Fields

它完成自动的配置位图和计算长度,并把组织好的报文放到TxBuffer中,供通信发送使用。

解析部分关键方法

ans8583Fields

它完成自动解析收到的报文,把各个解析出来的域和长度放到fieldRecv的各个域中。

需要外部设置的参数在哪放?

外部需要配置的有:终端号,商户号,主秘钥,TPDU。这些在类里定义的有set和get方法。

请看 报文结构类定义:

/**
 * Created by yangyongzhen on 2018/06/27
 * simple 8583 Protocol Analysis
QQ:534117529
 */
public class Easy8583Ans {

    //通信涉及到的一些参数,内部使用
    private static long commSn = 1; //通讯流水号
    private static byte[] piciNum = new byte[3];//批次号
    private static byte[] licenceNum = new byte[4];//入网许可证编号

    //需要外部设置的参数有:商户号,终端号,主秘钥,TPDU(以下的为默认值,并提供set和get方法)
    private static String ManNum  = "808411341310014"; //商户号
    private static String PosNum  = "70782214"; //终端号
    private static String MainKey = "31313131313131313131313131313131"; //主秘钥
    private static String TPDU    = "6005010000";
    /**
     * 定义8583的报文协议包结构 Len+Tpdu+Head+MsgType+BitMap+Data
     */
    public class Pack {

        public byte[] Len;
        public byte[] Tpdu;
        public byte[] Head;
        public byte[] MsgType;
        public byte[] BitMap;
        public byte[] TxBuffer;
        public int TxLen;

        public Pack() {
            Len = new byte[2];
            Tpdu = hexStringToBytes(TPDU);
            Head = new byte[]{0x61, 0x31, 0x00, 0x31, 0x11,0x08};//这几项一般固定,不会变动
            MsgType = new byte[2];
            BitMap = new byte[8];
            TxBuffer = new byte[1024];
            TxLen = 0;
      
        }

        @Override
        public String toString() {
            return "Pack{" +
                    "Len=" + bytesToHexString(Len) +
                    ", Tpdu=" + bytesToHexString(Tpdu) +
                    ", Head=" + bytesToHexString(Head) +
                    ", MsgType=" + bytesToHexString(MsgType) +
                    ", BitMap=" + bytesToHexString(BitMap) +
                    ", TxBuffer=" + bytesToHexString(TxBuffer) +
                    ", TxLen=" + TxLen +
         
                    '}';
        }
    }

    /**
     * 域定义
     */
    public class __8583Fields {
        int ishave;   /*是否存在*/
        int type;    /*类型(取值范围0-2,组帧时0:表示不计长度 1:表示长度占1字节,2:表示2长度占2字节)*/
        int len;     /*域长度(需根据各个域的定义要求正确取值)*/
        byte[] data; /*域内容*/
        __8583Fields()
        {
            ishave = 0;
            type = 0;
            len = 0;
            data =null;
        }
    }

    public __8583Fields[] fieldSend; //发送的域
    public __8583Fields[] fieldRecv; //接收的域
    public Pack pack;

    public Easy8583Ans()
    {
        fieldSend = new __8583Fields[64];
        fieldRecv = new __8583Fields[64];
        pack = new Pack();
        init8583Fields(fieldSend);
        init8583Fields(fieldRecv);
    }

如何发送报文给银联后台呢?

假如你通信有Send(byte[] sendbuf, int len) 方法

那么只需 Send(pack.TxBuffer,pack.TxLen)

想打印出来报文日志在哪看? 直接pack.ToString()即可

附带一个调用的demo:

public static void main(String[] args) {
    My8583Ans myans = new My8583Ans();
    //签到组包
    myans.frame8583QD(myans.fieldsSend,myans.pack);
    //打印出待发送的报文
    byte[] send = new byte[myans.pack.txLen];
    arraycopy(myans.pack.txBuffer,0,send,0,myans.pack.txLen);
    System.out.println("->send:");
    System.out.println(My8583Ans.bytesToHexString(send));
    System.out.println(myans.pack.toString());
    System.out.println(myans.getFields(myans.fieldsSend));
    //接收解析,假设收到的报文在recv中
    String recvstr ="007960000001386131003111080810003800010AC0001450021122130107200800085500323231333031343931333239303039393939393930363030313433303137303131393939390011000005190030004046F161A743497B32EAC760DF5EA57DF5900ECCE3977731A7EA402DDF0000000000000000CFF1592A";
    System.out.println("->recv:"+recvstr);
    byte[] recv = My8583Ans.hexStringToBytes(recvstr);
    // mypack.ans8583Fields(bt,bt.length,mypack.fieldsRecv);
    //解析
    System.out.println("开始解析...");
    int ret = myans.ans8583QD(recv,recv.length);
    if(ret == 0){
        //打印出解析成功的各个域
        System.out.println("签到成功!");
        System.out.println(myans.getFields(myans.fieldsRecv));
    }

做了个getDields方法打印出了各个域的信息。

输出结果如下,连带每个域的日志都有了,够简单直观了吧:
 com.example.yang.myapplication.My8583Ans
->send:

0057600501000061310031110808000020000000c0001600000139393939393930363030313433303137303131393939390011000000000030002553657175656e6365204e6f31323330363039393939393930360003303031

Pack{len=0057, tpdu=6005010000, head=613100311108, msgType=0800, bitMap=0020000000c00016, txLen=89, txBuffer=0057600501000061310031110808000020000000c0001600000139393939393930363030313433303137303131393939390011000000000030002553657175656e6365204e6f31323330363039393939393930360003303031}
Len: 0057
TPDU: 6005010000
Head: 613100311108
MsgType: 0800
BitMap: 0020000000c00016
-------------------------------------------------
[field:11] [000001]
-------------------------------------------------
[field:41] [3939393939393036]
-------------------------------------------------
[field:42] [303031343330313730313139393939]
-------------------------------------------------
[field:60] [len:0011] [000000000030]
-------------------------------------------------
[field:62] [len:0025] [53657175656e6365204e6f3132333036303939393939393036]
-------------------------------------------------
[field:63] [len:0003] [303031]
-------------------------------------------------
----------------------------------------------------------------------------------------------------

->recv:

007960000001386131003111080810003800010AC0001450021122130107200800085500323231333031343931333239303039393939393930363030313433303137303131393939390011000005190030004046F161A743497B32EAC760DF5EA57DF5900ECCE3977731A7EA402DDF0000000000000000CFF1592A

开始解析...
->ok 解析成功!
pinkey:d931648f3de313a4a22c15dca4f4299e
<-ok PIK正确
mackey:ab7c577cc7a180455fc2a085d7208a04
<-ok MAC正确
签到成功!
Len: 0079
TPDU: 6000000138
Head: 613100311108
MsgType: 0810
BitMap: 003800010ac00014
-------------------------------------------------
[field:11] [500211]
-------------------------------------------------
[field:12] [221301]
-------------------------------------------------
[field:13] [0720]
-------------------------------------------------
[field:32] [len:08] [00085500]
-------------------------------------------------
[field:37] [323231333031343931333239]
-------------------------------------------------
[field:39] [3030]
-------------------------------------------------
[field:41] [3939393939393036]
-------------------------------------------------
[field:42] [303031343330313730313139393939]
-------------------------------------------------
[field:60] [len:0011] [000005190030]
-------------------------------------------------
[field:62] [len:0040] [46f161a743497b32eac760df5ea57df5900ecce3977731a7ea402ddf0000000000000000cff1592a]

-------------------------------------------------

附带一个使用retrofit网络库访问的输出结果:

D/OkHttp: --> POST https://1xx.xx.xx.xx:xxxx/ http/1.1
          Content-Type: x-ISO-TPDU/x-auth
D/OkHttp: Content-Length: 89
          User-Agent: Donjin Http 0.1
          Cache-Control: no-cache
          Accept-Encoding: *
D/OkHttp: Host: xxx.xxx.xxx.xx:xxxx
          Connection: Keep-Alive
          --> END POST
D/OkHttp: <-- 200 OK https://xxx.xxx.xxx.xx:xxxx/ (421ms)
D/OkHttp: Allow: POST, PUT
          Content-Type: x-ISO-TPDU/x-auth
          Date: Sat, 30 Jun 2018 09:58:41 GMT
D/OkHttp: Content-Length: 123
D/OkHttp: Server: Access-Guard-1000-Software/1.0
          Connection: close
          <-- END HTTP
D/AA: 成功
      Response{protocol=http/1.1, code=200, message=OK, url=https://xxxxxxxx:xxxxx/}
D/respondAA:: 007960000005016131003111080810003800010ac0001400000117563506300800094900313735363335353837303233303037333738323231343839383431313334313331303031340011000007500030004050bc9eb4774a92544c29dad2c764150bb93eba92d9f11a222efa9c2300000000000000002b580802
I/System.out: 开始解析...
I/System.out: ->ok 解析成功!
I/System.out: pinkey:b1a7ab3cb49c9757390f39a19ce71ae7
I/System.out: <-Er PIK错误

源码如下:
package com.example.yang.myapplication ;

import java.io.UnsupportedEncodingException ;

import static java.lang.System. arraycopy ;
/**
* Created by yangyongzhen on 2018/06/27
* simple 8583 Protocol Analysis
*/
public class Easy8583Ans {

//通信涉及到的一些参数,内部使用
private static long commSn = 1 ; //通讯流水号
private static byte[] piciNum = new byte[ 3] ; //批次号
private static byte[] licenceNum = new byte[ 4] ; //入网许可证编号

//需要外部设置的参数有:商户号,终端号,主秘钥,TPDU(以下的为默认值,并提供set和get方法)
private static String ManNum = "808411341310014" ; //商户号
private static String PosNum = "70782214" ; //终端号
private static String MainKey = "31313131313131313131313131313131" ; //主秘钥
private static String TPDU = "6005010000" ;
/**
* 定义8583的报文协议包结构 Len+Tpdu+Head+MsgType+BitMap+Data
*/
public class Pack {

public byte[] Len ;
public byte[] Tpdu ;
public byte[] Head ;
public byte[] MsgType ;
public byte[] BitMap ;
public byte[] TxBuffer ;
public int TxLen ;

public Pack() {
Len = new byte[ 2] ;
Tpdu = hexStringToBytes( TPDU) ;
Head = new byte[]{ 0x61 , 0x31 , 0x00 , 0x31 , 0x11,0x08} ; //这几项一般固定,不会变动
MsgType = new byte[ 2] ;
BitMap = new byte[ 8] ;
TxBuffer = new byte[ 1024] ;
TxLen = 0 ;
}

@Override
public String toString() {
return "Pack{" +
"Len=" + bytesToHexString( Len) +
", Tpdu=" + bytesToHexString( Tpdu) +
", Head=" + bytesToHexString( Head) +
", MsgType=" + bytesToHexString( MsgType) +
", BitMap=" + bytesToHexString( BitMap) +
", TxBuffer=" + bytesToHexString( TxBuffer) +
", TxLen=" + TxLen +
'}' ;
}
}

/**
* 域定义
*/
public class __8583Fields {
int ishave ; /*是否存在*/
int type ; /*类型(取值范围0-2,组帧时0:表示不计长度 1:表示长度占1字节,2:表示2长度占2字节)*/
int len ; /*域长度(需根据各个域的定义要求正确取值)*/
byte[] data ; /*域内容*/
__8583Fields()
{
ishave = 0 ;
type = 0 ;
len = 0 ;
data = null;
}
}

public __8583Fields[] fieldSend ; //发送的域
public __8583Fields[] fieldRecv ; //接收的域
public Pack pack ;

public Easy8583Ans()
{
fieldSend = new __8583Fields[ 64] ;
fieldRecv = new __8583Fields[ 64] ;
pack = new Pack() ;
init8583Fields( fieldSend) ;
init8583Fields( fieldRecv) ;
}

/**
* 各个域的配置,初始化
* @param field
*/
private void init8583Fields(__8583Fields[] field) {

for( int i = 0 ; i < 64 ; i++)
{
field[i] = new __8583Fields() ;
}
field[ 0]. type = 0 ;
field[ 1]. type = 1 ; //LLVAR

field[ 2]. type = 0 ;
field[ 2]. len = 3 ;

field[ 3]. type = 0 ;
field[ 3]. len = 6 ;

field[ 10]. type = 0 ;
field[ 10]. len = 3 ;

field[ 11]. type = 0 ;
field[ 11]. len = 3 ;

field[ 12]. type = 2 ; //LLLVAR
field[ 13]. type = 2 ;
field[ 14]. type = 2 ;

field[ 21]. type = 2 ;
field[ 22]. type = 2 ;

field[ 24]. type = 0 ;
field[ 24]. len = 1 ;
field[ 25]. type = 0 ;
field[ 25]. len = 1 ;

field[ 31]. type = 1 ; //LLVAR

field[ 36]. type = 0 ;
field[ 36]. len = 12 ;

field[ 37]. type = 0 ;
field[ 37]. len = 6 ;
field[ 38]. type = 2 ;
field[ 39]. type = 1 ;

field[ 40]. type = 0 ;
field[ 40]. len = 8 ;
field[ 41]. type = 0 ;
field[ 41]. len = 15 ;

field[ 43]. type = 1 ;

field[ 47]. type = 2 ;
field[ 48]. type = 0 ;
field[ 48]. len = 3 ;
field[ 51]. type = 0 ;
field[ 51]. len = 8 ;
field[ 52]. type = 0 ;
field[ 52]. len = 8 ;

field[ 54]. type = 2 ;
field[ 58]. type = 2 ;

field[ 59]. type = 2 ;
field[ 60]. type = 2 ;
field[ 61]. type = 2 ;
field[ 62]. type = 2 ;

field[ 63]. type = 0 ;
field[ 63]. len = 8 ;

}
/**
* 该方法不需要外部调用,该方法自动完成各个域的组包和BitMap的形成及报文长度的计算
* 该方法最终组织各个域中的内容到 Pack的TxBuffer中,形成一完整报文
* @param field
* @param pk
*/
private void pack8583Fields( __8583Fields[] field , Pack pk)
{
int j = 0 ;
int len = 22 ;
int tmplen = 0 ;
int seat = 0x80 ;
for( int i = 0 ;i < 64 ; i++) {
seat = (seat >> 1 ) ;
if((i% 8) == 0) {
j++ ;
seat = 0x80 ;
}
if(field[i]. ishave == 1) {
pk. BitMap[j- 1] |= seat ; //根据每个filed中的ishave是否为1,自动计算BitMap
if(field[i]. type == 0){
//根据每个域的数据类型,自动截取长度组包
arraycopy(field[i]. data , 0 ,pk. TxBuffer ,len ,field[i]. len) ; //数据
len += field[i]. len ;
}
else if(field[i]. type == 1){
//域长度
pk. TxBuffer[len] = ( byte)field[i]. len ;
tmplen = Integer. parseInt(String. format( "%02x" ,pk. TxBuffer[len]) , 10) ;
//域数据
if((i== 1)||(i== 31)||(i== 47)||(i== 59)||(i== 60))
{
tmplen = ((tmplen/ 2) + (tmplen% 2)) ;
}
len += 1 ;
arraycopy(field[i]. data , 0 ,pk. TxBuffer ,len ,tmplen) ; //数据
len += tmplen ;
}
else if(field[i]. type == 2){
pk. TxBuffer[len] = ( byte)(field[i]. len>> 8) ;
pk. TxBuffer[len+ 1] = ( byte)field[i]. len ;
tmplen = Integer. parseInt(String. format( "%02x%02x" ,pk. TxBuffer[len] ,pk. TxBuffer[len+ 1]) , 10) ;
if((i== 1)||(i== 31)||(i== 47)||(i== 59)||(i== 60))
{
tmplen = ((tmplen/ 2) + (tmplen% 2)) ;
}
len += 2 ;
arraycopy(field[i]. data , 0 ,pk. TxBuffer ,len ,tmplen) ; //数据
len += tmplen ;
}

}
}
pk. TxLen = len ;
pk. Len[ 0] = ( byte)((len-2) << 8) ;
pk. Len[ 1] = ( byte)(len-2) ;
arraycopy(pk. Len , 0 ,pk. TxBuffer , 0 , 2) ;
arraycopy(pk. Tpdu , 0 ,pk. TxBuffer , 2 , 5) ;
arraycopy(pk. Head , 0 ,pk. TxBuffer , 7 , 5) ;
arraycopy(pk. MsgType , 0 ,pk. TxBuffer , 12 , 2) ;
arraycopy(pk. BitMap , 0 ,pk. TxBuffer , 14 , 8) ;
}

/**
* 签到报文组帧
* @param field
* @param tx
*/
public void frame8583QD( __8583Fields[] field , Pack tx){

//消息类型
tx. MsgType[ 0] = 0x08 ;
tx. MsgType[ 1] = 0x00 ;
//11域,受卡方系统跟踪号BCD 通讯流水
field[ 10]. ishave = 1 ;
field[ 10]. len = 3 ;
String tmp = String. format( "%06d" , commSn) ;
field[ 10]. data = hexStringToBytes(tmp) ;
//41域,终端号
field[ 40]. ishave = 1 ;
field[ 40]. len = 8 ;
field[ 40]. data = PosNum.getBytes() ;
//42域,商户号
field[ 41]. ishave = 1 ;
field[ 41]. len = 15 ;
field[ 41]. data = ManNum.getBytes() ;
//60域
field[ 59]. ishave = 1 ;
field[ 59]. len = 0x11 ;
field[ 59]. data = new byte[ 6] ;
field[ 59]. data[ 0] = 0x00 ;
arraycopy( piciNum , 0 ,field[ 59]. data , 1 , 3) ;
field[ 59]. data[ 4] = 0x00 ;
field[ 59]. data[ 5] = 0x30 ;
//62域
field[ 61]. ishave = 1 ;
field[ 61]. len = 0x25 ;
field[ 61]. data = new byte[ 25] ;
String str = "Sequence No12" ;
arraycopy(str.getBytes() , 0 ,field[ 61]. data , 0 , 13) ;
arraycopy( licenceNum , 0 ,field[ 61]. data , 13 , 4) ;
arraycopy( PosNum.getBytes() , 0 ,field[ 61]. data , 17 , 8) ;
//63域
field[ 62]. ishave = 1 ;
field[ 62]. len = 0x03 ;
field[ 62]. data = new byte[ 3] ;
field[ 62]. data[ 0] = 0x30 ;
field[ 62]. data[ 1] = 0x30 ;
field[ 62]. data[ 2] = 0x31 ;
/*报文组帧,自动组织这些域到Pack的TxBuffer中*/
pack8583Fields(field ,tx) ;
}



private static byte charToByte( char c) {
return ( byte) "0123456789ABCDEF".indexOf(c) ;
}

public static String bytesToHexString( byte[] src){
StringBuilder stringBuilder = new StringBuilder( "") ;
if (src == null || src. length <= 0) {
return null;
}
for ( int i = 0 ; i < src. length ; i++) {
int v = src[i] & 0xFF ;
String hv = Integer. toHexString(v) ;
if (hv.length() < 2) {
stringBuilder.append( 0) ;
}
stringBuilder.append(hv) ;
}
return stringBuilder.toString() ;
}

public static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals( "")) {
return null;
}
hexString = hexString.toUpperCase() ;
int length = hexString.length() / 2 ;
char[] hexChars = hexString.toCharArray() ;
byte[] d = new byte[length] ;
for ( int i = 0 ; i < length ; i++) {
int pos = i * 2 ;
d[i] = ( byte) ( charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])) ;

}
return d ;
}

public static void setPosNum(String posNum) {
PosNum = posNum ;
}
public static void setMainKey(String mainKey) {
MainKey = mainKey ;
}
public static void setTPDU(String TPDU) {
Easy8583Ans. TPDU = TPDU ;
}
public static String getPosNum() {
return PosNum ;
}
public static String getMainKey() {
return MainKey ;
}
public static String getTPDU() {
return TPDU ;
}
public static String getManNum() {
return ManNum ;
}
public static void setManNum(String manNum) {
ManNum = manNum ;
}


}


猜你喜欢

转载自blog.csdn.net/qq8864/article/details/80838764