菜鸟打印组件交互协议相关介绍如下:
1、打印组件下载地址
https://page.cainiao.com/waybill/cloud_printing/home.html
2、socket连接商品
如果是http的话,端口是13529
socket = new WebSocket('ws://localhost:13528'); 
如果是https的话,端口是13529
socket = new WebSocket('wss://localhost:13529'); 
3、简介
- 云打印客户端是以独立进程和打印机交互(非作为浏览器插件进行打印)。
 - 浏览器或其他客户端需要通过 WebSocket,协议与云打印客户端进行通信,支持javascript,java,c/c++,python等常用的语言(建议使用对应开发与语言支持的 Websockt 库)。
 - 若 ISV 的 ERP 系统是B/S结构,建议使用如下版本浏览器:
 - chrome 45及以上(建议使用chrome的最新版本);
 - 相关浏览器的极速模式
 
4、请求协议头格式说明
请求协议头示例如下:
{"cmd": "command","requestID": "unique requestID","version": "1.0"
} 
字段说明:
|   字段名  |   类型  |   说明  |   是否必须  | 
|   cmd  |   string  |   请求的命令名称  |   是  | 
|   requestID  |   string  |   请求的ID,用于唯一标识每个请求,每个客户端自己保证生成唯一ID,如UUID  |   是  | 
|   version  |   string  |   协议当前版本,当前为“1.0”  |   是  | 
响应协议头:
{"cmd": "command","requestID": "unique requestID"
} 
字段说明:
|   字段名  |   类型  |   说明  | 
|   cmd  |   string  |   请求的命令名称  | 
|   requestID  |   string  |   发送请求中的ID,原封不动返回,使客户端能识别出哪个请求对应的响应  | 
5、print = 发送打印/预览数据协议
发送打印/预览数据协议(0.x版本)
注:因为打印机质量乘次不齐,建议 1 个 task 使用 一个 document,可以有效避免重打问题;
预览流程0.x:
打印流程(预览流程1.x):
请求协议格式(密文数据,针对菜鸟电子面单)如下:
{"cmd": "print","requestID": "123458976","version": "1.0","task": {"taskID": "7293666","preview": false,"printer": "","previewType": "pdf","firstDocumentNumber": 10,"totalDocumentCount": 100,"documents": [{"documentID": "0123456789","contents": [{"encryptedData":"AES:rU904rj6UH2oqfSUb43+Z+XlOkZaULeerkScS5xbmfjZC78uvsMTa3g6l33hRAz/srsk0TObjJaJI5n4tAPV1uv7szIPQGPDhwD6MK+zvTVIfuQCMC8p+cUB5S4FmqDhNE45LRVAlaoaI5YK8QmWK1WorhwnPxOFH4Ws/ApobtzDLDJaW6uu1AMEdAejEhRTWL3B1fRhhcDxc3gX+DZF9jJUB++fb9JZqmocWRu0Fvi/b1BokQx7Xt/N+FpJVRI0//NNUQ9b/W4tqGFIbf2IM/Ez1S5hBru5gKGdFzs99ZgCKqtWa0DnOzrZDXroU1mhurtlulE8QbipInu63fkIwn3h9ZSK0sMyV5Jrk5x3MIJDHeW9pc/Tw4TnKTAU134jl+GbbpYysa0+jBARWRjombeKIFSVfp/zgp15jClClUU1Nz4alTi22LimY2qteQRG6G/rCHiYxPoBRdrtqZZxNSdnKG5yjSdtA2CEL1DJNg1QkFVSSsOuqcHLdrKl6oMR+aUN6wM3GQikmKSU/CH4hWCCXxFaJXvBYoSxZ63GrM/d+l6D4+9+rCxHJoEVsa2E1TMHLUOnN6CweSM+45lcBK19bbCUJDyky6nb1NbxrZGYhmfkrNzE2GN+Cz4iTAgxJlQxd1gVvS4v5nB7qNfb0Uhy9NTopdumxOS7NXFFg3RFdBfAJ0nLGnxECUvUihBC3pwsLGimrUnIF4174m6J6Ga6cQE+Pp1LXgtKf5zWJdWHkm2vQhazcAsQC8JJZFb1ESp1vIAvpy0d0YmGrLLzxWNciHlOa7vguFCVF3UbTFe8r1Mxyym9rqNrZDXWRtBija9yeliMERVFuOTRjlc0PVAzveexQmuD4ESTzMZPtbO0jos1EITKhHcV35Na7E4I7bEe3L2u5yuFuzDA5cc8OA8v761+xOI70bGXUwvFO2kCCiUFEzI9ksLIDTtydBTA94lf4MYH6m0ziRmAhAgcwm5QJFd2G4JzpFIK4+dLuEZamrYUcnHmWzDIg+HYIXh6g3S2maFU7dUtwYoerptOTiVg8FxRlUTx30NDTgjm7ll8vEJXHj7yd/gAO3Vm9P54OSMv8w+pzX3gtCkvthrkjlToT1jMRNJyuJAeSBf5jruzYLS68inlSE/ehT10zhaiBvaCqojZZ2Ux0JQGhbR/nQ==","signature":"19d6f7759487e556ddcdd3d499af087080403277b7deed1a951cc3d9a93c42a7e22ccba94ff609976c5d3ceb069b641f541bc9906098438d362cae002dfd823a8654b2b4f655e96317d7f60eef1372bb983a4e3174cc8d321668c49068071eaea873071ed683dd24810e51afc0bc925b7a2445fdbc2034cdffb12cb4719ca6b7","templateURL":"http://cloudprint.cainiao.com/template/standard/101/123","ver":"waybill_print_secret_version_1"},{"data": {"value": "测试字段值需要配合自定义区变量名"},"templateURL": "http://cloudprint.cainiao.com/template/customArea/440439"}]}]}
} 
请求协议格式(明文数据)如下:
{"cmd": "print","requestID": "123458976","version": "1.0","task": {"taskID": "7293666","preview": false,"printer": "","previewType": "pdf","firstDocumentNumber": 10,"totalDocumentCount": 100,"documents": [{"documentID": "0123456789","contents": [{"data": {"nick": "张三"},"templateURL": "http://cloudprint.cainiao.com/template/standard/278250/1"},{"data": {"value": "测试字段值需要配合自定义区变量名"},"templateURL": "http://cloudprint.cainiao.com/template/customArea/440439"}]}]}
} 
字段说明:
|   字段名  |   类型  |   说明  |   是否必须  | 
|   taskID  |   string  |   打印机任务ID,每个打印任务会分配不同的且唯一的ID,在0.x中,默认不允许taskID重复,若重复则直接返回错误 在1.5.0中,当task结构中的idempotent设置为true时,不允许taskID重复,默认允许重复  |   是  | 
|   idempotent  |   bool  |   1.5.0版本及以后支持 与taskID搭配使用,当值为true时,taskID不允许重复,默认为false  |   否  | 
|   notifyType  |   array  |   打印通知类型:“render”, “print” [“render”] : 仅渲染响应 notify [“print”] : 仅出纸响应 notify ?“render”, “print” : 渲染完成会响应 notify && 出纸完成后会响应 notify [] : 不允许 注:如果notifyType没有指定,默认为[“render”, “print”] ??在1.x版本中废弃此字段,总是会进行通知  |   否  | 
|   preview  |   bool  |   是否预览.true为预览,false为打印  |   是  | 
|   printType  |   string  |   可选dirctPrint或templatePrint 默认为templatePrint,当设置为dirctPrint时,templateURL可以放入PDF的链接进行PDF打印  |   否  | 
|   previewType  |   string  |   属性取值“pdf” or “image” 预览模式,是以pdf还是image方式预览,二选一,此属性不是必选,默认以pdf预览。  |   否  | 
|   firstDocumentNumber  |   int  |   task 起始 document 序号  |   否  | 
|   totalDocumentCount  |   int  |   task document 总数  |   否  | 
|   printer  |   string  |   打印机名,如果为空,会使用默认打印机  |   否  | 
|   templateURL  |   string  |   模板文件url  |   是  | 
|   signature  |   string  |   模板与数据的签名  |   否  | 
|   documents  |   array  |   文档数组,每个数据表示一页  |   是  | 
|   documentID  |   string  |   文档的唯一ID,对于菜鸟标准面单来讲,就是面单号;如果是自定义模板,需要保证唯一  |   是  | 
|   data  |   Json Object  |   模板需要的打印数据  |   是  | 
菜鸟打印组件响应
协议格式如下:{"cmd":"print","requestID":"123458976","taskID":"1","status":"success", //如果是打印,表示打印任务提交成功,如果是预览,表示预览PDF文件生成成功"previewURL":"http://127.0.0.1/previewxxx.pdf", //如果是预览,会返回这个属性,表示预览PDF文件的URL地址,如果是打印命令,不返回此属性
//如果是预览并且预览模式是previewType:image,会返回这个属性,表示预览图片的URL地址,如果是打印命令,不返回此属性"previewImage": [http://127.0.0.1/preview1.jpg,http://127.0.0.1/preview2.jpg,http://127.0.0.1/preview3.jpg],
//1.x后的菜鸟打印组件版本
"urls"["url1","url2"]
} 
|   字段名  |   类型  |   说明  | 
|   taskID  |   string  |   打印机任务ID,每个打印任务会分配不同的且唯一的ID  | 
|   status  |   string  |   如果是打印,表示打印任务提交成功,如果是预览,表示预览PDF文件生成成功  | 
|   previewURL  |   string  |   可预览的PDF文件URL路径  | 
|   previewImage  |   string[]  |   预览image的URL路径,是一个字符串数组  | 
|   urls  |   string[]  |   1.x后的菜鸟打印组件版本会在预览时返回  | 
注:
 * 如果是打印命令,只是表示将打印任务提交到任务队列,会快速返回。
 * 如果是预览命令,且版本为0.x则需要将预览文件生成,才会返回,需要一段等待时间。如果是1.x版本,则会立即返回一条消息表示任务已提交到任务队列,随后当预览文件生成后,会再次返回一个消息并携带文件地址
最佳实践
由于网络协议本身的不可靠性,建议接入时按照以下规范进行,否则可能出现漏打、重复打等情况
- 发送指令前先检查websock链接的可用性,若不可用则重连
 - 发送打印指令后等待【任务已提交】的响应,此时可以告知用户任务已提交打印
 - 保持发送的链接存活(不主动关闭),持续监听notifyPrintResult消息,当接收到失败或者成功后才关闭链接
 - 当接收到成功后,应修改业务系统中打印任务的状态并提示用户某任务已完成
 - 当接收到失败后,应修改业务系统中打印任务的状态并提示用户某任务已失败
 - 调整任务的taskID和idempotent配置以符合业务预期
 
6、notifyPrintResult = 打印通知
此消息总是由菜鸟打印组件向调用方返回
通知协议格式如下:
{"cmd":"notifyPrintResult","printer":"中通打印机A","taskID":"1","taskStatus":"printed","printStatus":[{"documentID”:”9890000112011”,"status":"success","msg":"if failed,some tips, if success ,nothing”,"detail":"错误信息的补充描述"}]
} 
字段解释:
|   字段名  |   类型  |   说明  | 
|   documentID  |   string  |   文档的唯一ID,对于菜鸟标准面单来讲,就是面单号;如果是自定义模板,需要保证唯一  | 
|   taskStatus  |   string  |   任务状态: failed : 失败; rendered: 渲染完成 printed : 出纸完成 ?注:当打印出纸之后才会发送通知并且只通知一次  | 
|   status  |   string  |   任务状态:success成功;failed 失败,canceled 取消 (当一个任务中的一个文档打印失败,任务中其他的文档打印状态为“canceled”状态)  | 
|   msg  |   string  |   如果任务状态为成功或挂起为空,如果任务状态为失败,则为失败原因概要。  | 
|   detail  |   string  |   错误信息的补充描述  | 
|   printer  |   string  |   负责打印的打印机名  | 
|   taskID  |   string  |   任务ID,每个打印任务会分配不同的且唯一的ID  | 
注:判断是否打印成功请根据“cmd=notifyPrintResult”、“taskStatus的状态”组合判断
7、 getPrinters = 获取打印机列表
请求协议格式如下:
{"cmd": "getPrinters","requestID": "123458976","version": "1.0"
} 
响应协议格式如下:
{"cmd": "getPrinters","requestID": "123458976","defaultPrinter": "XX快递打印机","printers": [{"name": "XX快递打印机"},{"name": "YY物流打印机"}]
} 
|   字段名  |   类型  |   说明  | 
|   defaultPrinter  |   string  |   默认打印机  | 
|   name  |   string  |   打印机的名字  | 
8、getPrinterConfig = 获取打印机配置
请求协议格式如下:
{"cmd":"getPrinterConfig","printer":"菜鸟打印机","version":"1.0","requestID":"123456789"
} 
响应协议格式如下:
{"cmd": "getPrinterConfig","requestID": "123456789","status": "success/failed","msg": "如果出错,错误原因","printer": {"name": "打印机名称","needTopLogo": false,"needBottomLogo": false,"horizontalOffset": 1,"verticalOffset": 2,"forceNoPageMargins": true,"autoPageSize": false,"orientation": 0,"autoOrientation": false,"paperSize": {"width": 100,"height": 180}}
} 
|   字段名  |   类型  |   说明  | 
|   status  |   string  |   标示命令成功或失败,取值“success”或者“failed”  | 
|   msg  |   string  |   如果出错,错误原因  | 
|   printer.name  |   string  |   打印机名称  | 
|   printer.needTopLogo  |   bool  |   是否需要模板上联的快递logo true为需要 false为不需要  | 
|   printer.needBottomLogo  |   bool  |   是否需要模板下联的快递logo true为需要 false为不需要  | 
|   printer.horizontalOffset  |   float  |   水平偏移量  | 
|   printer.verticalOffset  |   float  |   垂直偏移量  | 
|   printer.forceNoPageMargins  |   bool  |   强制设置页面无空边 true为强制设置页面无空边 false为由打印机驱动决定  | 
|   printer.paperSize.width  |   int  |   打印机纸张的宽度,单位是毫米  | 
|   printer.paperSize.height  |   int  |   打印机纸张的高度,单位是毫米  | 
|   printer. autoPageSize  |   bool  |   true:自适应纸张大小 false:不自适应  | 
|   printer. orientation  |   int  |   0:纵向 1: 横向  | 
|   printer. autoOrientation  |   bool  |   true:按照 orientation 适应纸张方向 false:不自适应  | 
9、setPrinterConfig = 设置打印机配置
请求协议格式如下:
{"cmd": "setPrinterConfig","requestID": "123458976","version": "1.0","printer": {"name": "菜鸟打印机","needTopLogo": true,"needBottomLogo": false,"horizontalOffset": 0.5,"verticalOffset": 0.7,"forceNoPageMargins": true,"autoPageSize": false,"orientation": 0,"autoOrientation": false,"paperSize": {"width": 100,"height": 180}}
} 
 
注:参数说明参考 获取打印机配置(getPrinterConfig)
响应协议格式如下:
{"cmd":"setPrinterConfig","requestID":"123458976","status":"success","msg":"if failed ,some tips, if success,nothing"
} 
|   字段名  |   类型  |   说明  | 
|   status  |   string  |   消息处理结果。success:成功;failed:失败  | 
|   msg  |   string  |   如果成功,则为空;如果失败,则为失败原因  | 
注:如果要保持某个配置不变,应省略对应的配置字段。
10、getTaskStatus = 获取任务打印任务状态
请求协议格式如下:
{"cmd":"getTaskStatus","requestID":"123458976","version":"1.0","taskID":["12311","12312"]
} 
|   字段名  |   类型  |   说明  |   是否必须  | 
|   taskID  |   json数组  |   打印机任务ID列表  |   是  | 
响应协议格式如下:
{"cmd":"getTaskStatus","requestID":"123458976","printStatus":[{"taskID":"12312", "detailStatus":[{"documentID":"9890000112011","status":"success","msg":"if failed ,some tips, if success or pending nothing","printer":"中通打印机A"}]}]
} 
|   字段名  |   类型  |   说明  | 
|   taskID  |   string  |   打印机任务ID,每个打印任务会分配不同的且唯一的ID  | 
|   documentID  |   string  |   文档的唯一ID,对于菜鸟标准面单来讲,就是面单号;如果是自定义模板,需要保证唯一  | 
|   status  |   string  |   任务状态:success成功;failed失败;pending,提交到打印机打印队列  | 
|   msg  |   string  |   如果任务状态为成功或挂起为空,如果任务状态为失败,则为失败原因。  | 
|   printer  |   string  |   负责打印的打印机名  | 
11、getGlobalConfig = 获取全局配置
请求协议格式如下:
{"cmd":"getGlobalConfig","requestID":"12345678901","version":"1.0"
}
 
响应协议格式如下:
{"cmd":"getGlobalConfig","requestID":"12345678901","status":"success","msg":"return nothing when success, return some tips when failed","notifyOnTaskFailure":true,//忽略字体无法显示的问题"ignoreFontCanNotDisplay":true
} 
字段解释:
|   字段名  |   类型  |   说明  | 
|   status  |   string  |   表示命令成功或失败,取值“success”或者“failed”  | 
|   msg  |   string  |   如果出错,错误原因  | 
|   notifyOnTaskFailure  |   bool  |   打印任务失败时是否需要通知(弹出对话框提醒用户打印失败原因并默认暂停当前打印机的打印),true为需要,false为不需要  | 
|   ignoreFontCanNotDisplay  |   bool  |   true表示忽略字体无法显示的问题 false则在字体无法显示时会弹窗报错  | 
12、setGlobalConfig = 设置全局配置
请求协议格式如下:
{"cmd":"setGlobalConfig","requestID":"12345678901","version":"1.0","notifyOnTaskFailure":true,//忽略字体无法显示的问题"ignoreFontCanNotDisplay":true
} 
响应协议格式如下:
{"cmd":"setGlobalConfig","requestID":"12345678901","status":"success","msg":"return nothing when success, return some tips when failed"
} 
字段解释:
|   字段名  |   类型  |   说明  | 
|   status  |   string  |   表示命令成功或失败,取值“success”或者“failed”  | 
|   msg  |   string  |   如果出错,错误原因  | 
|   notifyOnTaskFailure  |   bool  |   打印任务失败时是否需要通知(弹出对话框提醒用户打印失败原因并默认暂停当前打印机的打印),true为需要,false为不需要  | 
13、getAgentInfo = 获取客户端版本信息
请求协议格式如下:
{"cmd":"getAgentInfo","requestID":"12345678901","version":"1.0"
} 
响应协议格式如下:
{"cmd":"getAgentInfo","requestID":"12345678901","status":"success","msg":"return nothing when success, return some tips when failed","version":"0.2.8.3" 
} 
字段解释:
|   字段名  |   类型  |   说明  | 
|   status  |   string  |   表示命令成功或失败,取值“success”或者“failed”  | 
|   msg  |   string  |   如果出错,错误原因  | 
|   version  |   string  |   版本号  | 
注意事项
- Websocket 建议使用长连接,不要每次发送交互请求去创建一个对象。
 - 在同打印组件交互过程中的json报文,如果文本中包含了特殊字符,比如常见的回车,引号等,需要对特殊字符做转义,详细请参考: JSON 。
 
14、JavaScript使用示例
function doConnect()
{socket = new WebSocket('ws://localhost:13528');//如果是https的话,端口是13529//socket = new WebSocket('wss://localhost:13529');// 打开Socketsocket.onopen = function(event){// 监听消息socket.onmessage = function(event){console.log('Client received a message',event);};// 监听Socket的关闭socket.onclose = function(event){console.log('Client notified socket has closed',event);};};
}
/**** * 获取请求的UUID,指定长度和进制,如 * getUUID(8, 2)   //"01001010" 8 character (base=2)* getUUID(8, 10) // "47473046" 8 character ID (base=10)* getUUID(8, 16) // "098F4D35"。 8 character ID (base=16)*   */
function getUUID(len, radix) {var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');var uuid = [], i;radix = radix || chars.length; if (len) {for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];} else {var r;uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';uuid[14] = '4';for (i = 0; i < 36; i++) {if (!uuid[i]) {r = 0 | Math.random()*16;uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];}}}return uuid.join('');
}
/**** 构造request对象*/
function getRequestObject(cmd){var request  = new Object();request.requestID=getUUID(8, 16);request.version="1.0";request.cmd=cmd;return request;
}
/**** 获取自定义区数据以及模板URL* waybillNO 电子面单号*/
function getCustomAreaData(var waybillNO){//获取waybill对应的自定义区的JSON object,此处的ajaxGet函数是伪代码var jsonObject = ajaxGet(waybillNO);var ret = new Object();ret.templateURL=jsonObject.content.templateURL;ret.data=jsonObject.content.data;return ret;
}
/**** 获取电子面单Json 数据* waybillNO 电子面单号*/
function getWaybillJson(var waybillNO){//获取waybill对应的json object,此处的ajaxGet函数是伪代码var jsonObject = ajaxGet(waybillNO);return jsonObject;
}
/*** 请求打印机列表demo* */
var request  = getRequestObject("getPrinters");
webSocket.send(JSON.stringify(request));
/*** 弹窗模式配置打印机* */
var request  = getRequestObject("printerConfig");
webSocket.send(JSON.stringify(request));
/*** 打印电子面单* printer 指定要使用那台打印机* waybillArray 要打印的电子面单的数组*/
function doPrint(var printer,var waybillArray)
{var request = getRequestObject("print");    request.task = new Object();request.task.taskID = getUUID(8,10);request.task.preview = false;request.task.printer = printer;var documents = new Array();for(i=0;i<waybillArray.length;i++) {var doc = new Object();doc.documentID = waybillArray[i];var content = new Array();var waybillJson = getWaybillJson(waybillArray[i]);var customAreaData = getCustomAreaData(waybillArray[i]);content.push(waybillJson,customAreaData);doc.content = content;documents.push(doc);}request.task.documents=documents;socket.send(JSON.stringify(request));
}
/*** 响应请求demo* */
websocket.onmessage = function(event){   var response = eval(event.data);if (response.cmd == 'getPrinters') {getPrintersHandler(response);//处理打印机列表} else if (response.cmd == 'printerConfig') {printConfigHandler(response);} 
}; 
15、JAVA使用示例
java使用websocket需要引入第三方库 下载地址 。
<dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.3.0</version>
</dependency> 
自己创建一个websocket管理类,需要继承自第三方类库的WebSocketClient:
import java.net.URI;
import java.net.URISyntaxException;import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;public class WebSocketClientManager extends WebSocketClient {static WebSocketClientManager webSocket = null;public static void main(String[] args) throws URISyntaxException {String uri = "ws://127.0.0.1:13528";webSocket = new WebSocketClientManager(new URI(uri), new Draft_17());//建立连接webSocket.connect();}public WebSocketClientManager(URI serverUri, Draft draft) {super(serverUri, draft);}@Overridepublic void onOpen(ServerHandshake serverHandshake) {//获取打印机列表String getPrinterListCmd = "{\"requestID\":\"12345678901234567890\",\"verson\":\"1.0\",\"cmd\":\"getPrinters\"}";webSocket.send(getPrinterListCmd);//发送打印任务String printCmd = "打印任务报文,内容过长此处不粘贴";webSocket.send(printCmd);}//WebSocket回调函数@Overridepublic void onMessage(String message) {//TODO 对打印服务返回的数据进行处理System.out.println(message);}@Overridepublic void onClose(int i, String s, boolean b) {}@Overridepublic void onError(Exception e) {}
}