{ "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,原封不动返回,使客户端能识别出哪个请求对应的响应 |
发送打印/预览数据协议(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版本,则会立即返回一条消息表示任务已提交到任务队列,随后当预览文件生成后,会再次返回一个消息并携带文件地址
由于网络协议本身的不可靠性,建议接入时按照以下规范进行,否则可能出现漏打、重复打等情况
打印通知(notifyPrintResult) 0.x版本
此消息总是由菜鸟打印组件向调用方返回
通知协议格式如下:
{ "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 |
获取打印机列表(getPrinters)
请求协议格式如下:
{ "cmd": "getPrinters", "requestID": "123458976", "version": "1.0" }
响应协议格式如下:
{ "cmd": "getPrinters", "requestID": "123458976", "defaultPrinter": "XX快递打印机", "printers": [{ "name": "XX快递打印机" }, { "name": "YY物流打印机" }] }
字段名 |
类型 |
说明 |
defaultPrinter |
string |
默认打印机 |
name |
string |
打印机的名字 |
请求协议格式如下:
{ "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:不自适应 |
请求协议格式如下:
{ "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 |
如果成功,则为空;如果失败,则为失败原因 |
注:如果要保持某个配置不变,应省略对应的配置字段。
请求协议格式如下:
{ "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 |
负责打印的打印机名 |
请求协议格式如下:
{ "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则在字体无法显示时会弹窗报错 |
请求协议格式如下:
{ "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为不需要 |
请求协议格式如下:
{ "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 |
版本号 |
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); } };
c++ Demo使用动态链接库的方式。
PrinterManager提供以下接口,调用约定 stdcall 方式:
int initPrinterManager(const char *url); void setRecvDataCallback(_onMessage_func func); int sendMessage(const char *message); int closePrinterManager();
说明:需要输入url的地址才能正常调用initPrinterManager,否则报错;
参数:const char *url指向WEBSOCKET的地址,如“ws://127.0.0.1:13528”
返回值:整型,成功返回0,返回-1表示初始化失败,返回-2表示重复初始化错误,返回-3表示未输入url
注意:每载入一次dll,只调用一次initPrinterManager(),重复调用返回-1.
void setRecvDataCallback(_onMessage_func func);
功能:设定回调函数,此回调函数作用:如果接收到数据则调用回调函数
返回值:无
传入参数:_onMessage_func func:函数指针,类型为typedef void(_onMessage_func)(const char message),
C++中对应回调函数例子:
void handle_message(const char* message)
{
printf(“>>> %s\n”, message);
}
int sendMessage(const char *message);
功能:发送数据给打印客户端
返回值:整型,成功返回0,返回-1表示websocket未连接
传入参数:const char *message:字符串类型(传入的字符串必须确保已经是UTF-8编码)
int closePrinterManager()
功能:关闭websocket
返回值:整型,成功返回0,返回-1表示websocket在调用前已关闭或未初始化
注意:不要重复关闭,否则返回-1.
以获取打印机列为例:
获取打印机列表命令如下:
{ "cmd":"getPrinters", "requestID":"123458976", "version":"1.0" }
DEMO中采用cJSON开源库作为生成JSON数据的工具:
string getRequestObject() { cJSON *root; root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "cmd", "getPrinters"); cJSON_AddStringToObject(root, "requestID", "123458976"); cJSON_AddStringToObject(root, "version", "1.0"); string str = string(cJSON_Print(root)); cJSON_Delete(root); return str; }
构造打印指令代码如下:
string getPrintRequestObject() { cJSON *root; root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "cmd", "print"); cJSON_AddStringToObject(root, "requestID", "123458976"); cJSON_AddStringToObject(root, "version", "1.0"); cJSON *task = cJSON_CreateObject(); cJSON_AddStringToObject(task, "taskID", "1"); cJSON_AddFalseToObject(task, "preview"); cJSON *documents = cJSON_CreateObject(); cJSON_AddStringToObject(documents, "ducumentID", "9890106027"); cJSON *contents = cJSON_CreateArray(); //data为电子面单数据,TOP接口返回数据可直接获得 cJSON_AddItemToArray(contents, cJSON_CreateString(data.c_str())); cJSON_AddItemToObject(documents, "contents", contents); cJSON_AddItemToObject(task, "documents", documents); cJSON_AddItemToObject(root, "task", task); //调用cJSON_PrintBuffered则去除换行符 string str = string(cJSON_Print(root)); cJSON_Delete(root); return str; }
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) { } }
使用C#官方提供的 websocket 库,根据上面说明的菜鸟打印协议进行开发。