一直以来做嵌入式软件开发,跟银联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方法打印出了各个域的信息。
->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错误