菜鸟打印组件交互协议相关介绍如下:
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');
// 打开Socket
socket.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);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
//获取打印机列表
String getPrinterListCmd = "{\"requestID\":\"12345678901234567890\",\"verson\":\"1.0\",\"cmd\":\"getPrinters\"}";
webSocket.send(getPrinterListCmd);
//发送打印任务
String printCmd = "打印任务报文,内容过长此处不粘贴";
webSocket.send(printCmd);
}
//WebSocket回调函数
@Override
public void onMessage(String message) {
//TODO 对打印服务返回的数据进行处理
System.out.println(message);
}
@Override
public void onClose(int i, String s, boolean b) {
}
@Override
public void onError(Exception e) {
}
}