物流一站式查询之TrackingMore篇
连载篇提前看
物流一站式单号查询之快递鸟API接口
物流一站式查询之TrackingMore篇
物流一站式查询之顺丰接口篇
物流一站式查询之快递100篇
快递查询接口
目前提供快递查询的接口平台有:
不同接口的区别:
(1)Trackingmore支持380家快递公司,其中有55家为国内的快递,其余325家为国际快递。具体的价格为0.6分钱/单号左右,新注册用户可以免费测试12小时。
(2)快递100属于在国内做得比较早的平台,可以申请每天最多2000次的API调用,但需要给快递100做一个友链。超过2000次收费,每次0.06~0.1元不等。
(3)快递网可以申请每天最多500次的API调用,但同样需要做一个友链。超过部分,每次0.05元。
(4) 快递鸟是专业的第三方物流接口服务商,为开发者聚合全球600+物流接口一次性对接服务,免费版有使用限制。
快递API的应用场景与用途
1. 最常见的应用场景如下:
(1)电商网站:例如B2C、团购、B2B、批发分销站、C2C、本地生活交易等网站。
(2)管理系统:订单处理平台、订货平台、发货平台、分销系统、渠道管理系统、客户管理系统、ERP等。
2. 快递API的用途如下:
(1)让顾客登录网站后,直接在“我的订单”页面内就能看到订单的物流状态。
(2)自动筛选出“已签收”、“疑难件”等状态的单号,减轻物流跟单人员的压力。
(3)改变订单的状态和交易流程,例如单号变为“已签收”,就能让订单变为可以确认退换货等。
(4)评估选择快递公司,根据“已签收”的运单数,可以算出销售人员的业绩,且便于应对货到付款的结算。
(5)邮件、短信提醒用户运单的最新状态,可以安抚用户,也可以利用邮件短信二次营销。
对接示例
这里以Trackingmore为例,不同的接口的对接方式比较类似,都需要注册,并生成自己的API key。以下以Trackingmore的实时查询API为例。
接口支持的消息接收方式为HTTP POST
请求方法的编码格式为 utf-8
请求body部分的参数的数据格式为json 格式
接口参数
接口请求地址
http://api.trackingmore.com/v2/trackings/realtime
请求头部信息参数
参数名称 | 类型 | 说明 | 是否必须 |
|
|
定义请求头部的数据格式 | 是 |
|
string | Trackingmore 后台获取的API | 是 |
请求body参数说明
参数说明 | 类型 | 说明 | 是否必须 |
|
string | 查询快递的快递单号 | 是 |
|
string | trackingmore定义的快递商简码,比如china ems 就是china-ema | 是 |
carrier_code 参数是trackingmore 自己定义的快递商家的简码,具体的可以在这里查看。
还有需要注意的就是body部分这两个参数需要时json数据格式。大概样子就是这样的
1 { 2 "tracking_number": "LK664578623CN", 3 "carrier_code": "china-ems" 4 }
返回参数定义
参数名称 | 参数类型 | 参数说明 | 是否一定要返回该项值 |
code |
数字 | 返回码 | 成功返回200,失败有其他队列的错误码 |
type | string | 接口类型 | 成功返回Success |
message | string | 返回信息说明 | 成功返回Succes,失败返回队列的错误信息 |
data | json | 查询到的物流信息 | 成功返回物流信息,失败返回空 |
其他的状态响应简码可以在这里看到。
这里把Trackingmore平台状态响应简码简单封装了下,代码如下:
/// <summary> /// 典型的服务器响应 状态枚举 /// </summary> public enum TrackMoreServerResponse { [Display(Name = "请求已成功 (一些 API 调用可能会相反返回 201)")] OK = 200, [Display(Name = "请求成功,已创建了资源")] Created = 201, [Display(Name = "请求已成功,但超过了数量限制")] CreatedFail = 202, [Display(Name = "身份验证失败或用户没有所请求的操作的权限")] Unauthorized = 401, [Display(Name = "无效的 API 密钥。请确保自己的申请API key的账户与调用API的服务器同时在国内或国外。即如果你的服务器在国外,你需要FQ登录trackingmore网站进而获取API key")] UnauthorizedAPIInvalid = 4001, [Display(Name = "API 密钥已被删除")] UnauthorizedAPIDelete = 4002, [Display(Name = "请求不能理解或缺少必需的参数")] BadRequest = 4012, [Display(Name = "Tracking_number 是必需的")] BadRequest1 = 4013, [Display(Name = "Tracking_number 的值是无效的")] BadRequestInvalid = 4014, [Display(Name = "Carrier_code的值是无效的")] BadRequest3 = 4015, [Display(Name = "跟踪已存在")] BadRequest4 = 4016, [Display(Name = "跟踪并不存在")] BadRequest5 = 4017, [Display(Name = "由于过载风险此功能需要自定义激活。与 service@trackingmore.org 联系更多的信息。")] BadRequest6 = 4018, [Display(Name = "数量限制一次 200")] BadRequest7 = 4020, [Display(Name = "你的余额不够,所以你不能调用API请求数据")] BadRequest8 = 4021, [Display(Name = "请求已成功,但响应为空")] BadRequest9 = 4031, [Display(Name = "无法检测到快递")] NoContent = 4032, [Display(Name = "付款要求")] PaymentRequired = 402, [Display(Name = "访问被拒绝")] Forbidden = 403, [Display(Name = "找不到资源")] NotFound = 404, [Display(Name = "指定的资源不支持请求的方法")] MethodNotAllowed = 405, [Display(Name = "由于冲突,无法完成请求")] Conflict = 409, [Display(Name = "超过 API 限制。请求暂停,等待两分钟,然后再试")] TooManyRequests = 429, [Display(Name = "内部异常")] ServerError = 500, [Display(Name = "服务是临时不可用 (例如预定的平台维护)稍后再试")] ServiceUnavailable = 503, }
然后我们可以获取枚举描述值来快速知道返回了什么状态,而不用去官网查状态码的意思。这里也把获取枚举描述值的代码贴出来如下:
/// <summary> /// 枚举辅助类 /// </summary> public static class EnmHelper { /// <summary> /// 获取枚举值的描述 /// </summary> /// <param name="sourceValue"></param> /// <returns></returns> public static string GetEnmName(this Enum sourceValue) { DisplayAttribute[] attributes = null; if (sourceValue != null) { FieldInfo field = sourceValue.GetType().GetField(sourceValue.ToString()); if (field != null) { attributes =field .GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[]; } } if (attributes==null|| attributes.Length < 1) return sourceValue.ToString(); return attributes[0].Name; } /// <summary> /// 获取枚举集合列表 /// </summary> /// <param name="enmType"></param> /// <returns></returns> public static List<ModItem> GetEnmList(this Type enmType) { List<EnmItem> result = new List<EnmItem>(); Array array= Enum.GetValues(enmType); foreach (var item in array) { DisplayAttribute[] attributes = (DisplayAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(DisplayAttribute), false); EnmItem enmItem = new EnmItem(); if (attributes.Length > 0) { enmItem.Name = attributes[0].Name; enmItem.Value = Convert.ToInt32(item).ToString(); enmItem.OrderIndex = attributes[0].GetOrder().GetValueOrDefault(0); result.Add(enmItem); } } return result.OrderBy(x=>x.OrderIndex).Select(x=>x as ModItem).ToList(); } /// <summary> /// 把字符串转换为枚举 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="source"></param> /// <returns></returns> public static T ConvertToEnm<T>(this string source) { return (T) ConvertToEnm(source,typeof(T)); } /// <summary> /// 把字符串转换为枚举 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="source"></param> /// <returns></returns> public static object ConvertToEnm(this string source,Type enmType) { ModItem mod = enmType.GetEnmList().FirstOrDefault(x => x.Name == source); if (mod != null) { source = mod.Value; } return Enum.Parse(enmType, source); } } class EnmItem:ModItem { public int OrderIndex { set; get; } }
一个枚举帮助类,其中ModItem类是自定义的一个类,里面就两个属性,Name、Value。
public class ModItem { public string Name { set; get; } public string Value { set; get; } }
像一般的调用接口获取物流信息,官网也有提供调用示例,点我去看示例
完整的物流订阅、查询状态有更新触发回调url 示例
现在假设你已经在More平台注册过,并生成创建了一个API Key。
订阅篇
①创建一个接口的访问类(此类官方已提供)
/// <summary> /// TrackingMore 每次发送必须带有Key 否则请求无效 /// </summary> public class TrackingMoreAPI { private string ApiKey = ConfigHelper.GetKey("APIKey"); public string getOrderTracesByJson(string requestData, string urlStr, string method) { string result = null; if (method.Equals("post")) { string ReqURL = "http://api.trackingmore.com/v2/trackings/post"; string RelUrl = ReqURL + urlStr; result = sendPost(ReqURL, requestData, "POST"); } else if (method.Equals("get")) { string ReqURL = "http://api.trackingmore.com/v2/trackings/get"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "GET"); } else if (method.Equals("batch")) { string ReqURL = "http://api.trackingmore.com/v2/trackings/batch"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "POST"); } else if (method.Equals("codeNumberGet")) { string ReqURL = "http://api.trackingmore.com/v2/trackings"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "GET"); } else if (method.Equals("codeNumberPut")) { string ReqURL = "http://api.trackingmore.com/v2/trackings"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "PUT"); } else if (method.Equals("codeNumberDel")) { string ReqURL = "http://api.trackingmore.com/v2/trackings"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "DELETE"); } else if (method.Equals("realtime")) { string ReqURL = "http://api.trackingmore.com/v2/trackings/realtime"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "POST"); } return result; } private string sendPost(string url, string requestData, string method) { string result = ""; byte[] byteData = null; if (requestData != null) { byteData = Encoding.GetEncoding("UTF-8").GetBytes(requestData.ToString()); } try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.ContentType = "application/x-www-form-urlencoded"; request.Timeout = 30 * 1000; request.Method = method; request.Headers["Trackingmore-Api-Key"] = ApiKey; if (byteData != null) { Stream stream = request.GetRequestStream(); stream.Write(byteData, 0, byteData.Length); stream.Flush(); stream.Close(); } HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream backStream = response.GetResponseStream(); StreamReader sr = new StreamReader(backStream, Encoding.GetEncoding("UTF-8")); result = sr.ReadToEnd(); sr.Close(); backStream.Close(); response.Close(); request.Abort(); } catch (Exception ex) { result = ex.Message; } return result; } }
②创建一个TrackMoreModel模型类用于传输快递单号和快递公司编码
public class TrackMoreModel { /// <summary> /// 快递单号 /// </summary> public string tracking_number { get; set; } /// <summary> /// 快递公司编码 /// </summary> public string carrier_code { get; set; } }
③创建一个TrackMore快递信息跟踪类,里面可以包含你需要的一些访问接口的方法,如:创建快递单项目,查询单个物流信息、创建多个物流项目,查询多个物流信息、实时查询物流信息等。这里以创建单个快递单项目为例
/// <summary> /// 快递跟踪信息 /// </summary> public class TrackMore { /// <summary> /// 创建快递单号项目 /// </summary> /// <param name="input"></param> /// <returns></returns> public static OperationResult TrackExpressSubscibe(TrackMoreModel model) { #region 创建物流跟踪信息 string urlstr = null; string requestdata = "{\"tracking_number\": \"" + model.tracking_number + "\",\"carrier_code\":\"" + model.carrier_code + "\"}"; string result = new TrackingMoreAPI().getOrderTracesByJson(requestdata, urlstr, "post"); var code = TarckMoreExpressCompany.GetValueFromJson(result, "code"); if ( code!="200") { string message = EnmHelper.GetEnmName((TrackMoreServerResponse)int.Parse(code)); if (!message.Equals("跟踪已存在")) { return new OperationResult(message); } } #endregion return new OperationResult(); } }
其中GetValueFromJson 方法作用是从json字符串中获取字段值 ,在这里方法也贴一下:
/// <summary> /// 从json字符串中获取字段值 /// </summary> /// <param name="json">json字符串</param> /// <param name="field">要解析出值的字段</param> /// <returns></returns> public static string GetValueFromJson(string json, string field) { int start = json.IndexOf(field + "\":"); start += field.Length + 2; int end = json.IndexOf(",", start); if (end < 0) { end = json.IndexOf("}", start); } return json.Substring(start, end - start).Trim('"'); }
OperationResult 类是自定义的业务操作结果类,可以根据项目需要自定义添加,这里我贴三个出来
/// <summary> /// 默认操作成功 /// </summary> public OperationResult() { IsSuccess = true; } /// <summary> /// 以操作失败信息实例操作结果 /// </summary> /// <param name="failMessage">操作失败信息</param> public OperationResult(string failMessage) { IsSuccess = false; FailMessage = failMessage; } /// <summary> /// 业务操作是否成功 /// </summary> public bool IsSuccess { set; get; }
④有了跟踪物流信息类,那剩下的就是调用了 ,调用比较简单,主要是根绝项目业务 去做一些操作,比如从数据库查询订单,过滤一些条件,快递没有订阅的,排除掉一些订单类型,有些充值啊这些不需要快递,查询到的集合
可以循环去创建物流单项目,调用的代码就一句,
OperationResult result = TrackMore.TrackExpressSubscibe(new TrackMoreModel() { tracking_number =快递单号, carrier_code =快递公司简码) });
然后可以根据返回的结果做一些其他操作
if (result.IsSuccess) { //TODO 比如 更新数据库此条订单物流订阅状态,订阅时间等 } else { //TODO 比如 日志记录失败原因 }
回调更新物流信息
上面只是再More物流平台创建了此条物流信息记录,但是我们并不知道当前这个快递状态是如何,也不能每次都去快递平台查询,所以,回调的作用就凸显出来了,物流平台每次只要有物流更新的时候,会访问到你再平台上设定的回调URL。
此时,你只用接收发过来的数据(包括meta请求头状态,data 数据
verifyInfo 签名部分
),具体代码如下:
⑴先再More平台设置回调地址
当然官方也提供了回调测试的 页面 在填回调地址的下面 点击 format of inbound webhooks 即可进入,界面如下图所示:
⑵设置完了回调地址,那我们就可以进行编码了,首先我们要对返回的数据 进行分析,上面也提到了 Body返回的主体部分是Json格式的,其实这里有一个坑,就是提交那边是form,但是接收是的数据类型是Json的
关于这种请求数据类型区别和使用,可以看这篇博客:
ASP.NET MVC学习系列(二)-WebAPI请求
ok,既然是Json格式数据,那我们就把返回的Json格式数据转成实体模型。这里有几种方式推荐,①将官方示例中的json全部复制下来,然后随便百度一个Json转实体,不过我更推荐第二种,② 号称宇宙最强IED 的VS也帮我们实现Json转实体的快捷操作,具体操作方式如下:
有了模型容器,我们就可以来接收数据了 创建一个和回调URL一样的方法名称UpdateTrack
/// <summary> /// TrackingMore 回调 同步物流信息 /// </summary> /// <param name="form"></param> /// <returns></returns> public ActionResult UpdateTrack([FromBody]Root dataJson) { var code = dataJson.meta.code;//请求状态 var expressNumber = dataJson.data.tracking_number;//快递单号 var status = dataJson.data.status;//物流状态 var trackinfo = dataJson.data.origin_info.trackinfo;//物流信息集合 var expressMessage = ""; //所有物流信息 foreach (var item in dataJson.data.origin_info.trackinfo) { expressMessage += item.Date + " " + item.StatusDescription + "<br/>"; } var track = dataJson.data.origin_info.trackinfo; var LastExpressMessage = track.Count > 0 ? track[0].Date + " " + track[0].StatusDescription : "";//最近一条物流信息 //TODO 比如:先根据传过来的快递单号查寻 项目的订单表 确认是否有此订单,再根据数据表中的订单状态 //看是否需要修改一些其他信息,主要就是拿到第一条物流信息和所有的物流信息,也可以再数据表中记录此时这个快递单 回调的时间 和返回的状态 用于后期数据分析 }
注:此方法接受回调使用了FormBody ,快递100平台接收回调是FormCollection即可。
外部调用
所谓外部调用,就是再自己项目中,根据不同单号 借助物流平台查询快递实时信息。物流平台对此操作也有讲解,这里简单提下。
官方给出的效果如下:
实现步骤
①将下面这句引用代码放在标签开始之前的位置
<script type="text/javascript" src="//cdn.trackingmore.com/plugins/v1/pluginsCss.js?time=20170913"></script><script type="text/javascript" src="//cdn.trackingmore.com/plugins/v1/plugins.min.js"></script>
②将下面代码放在 页面与标签之间的位置
<div style="width:600px;margin-left:0px;text-align:left;"> <form id="trackingmoreId" role="form" action="//track.trackingmore.com" method="get" onsubmit="return false"> <div class="search-box"> <div class="input-box"> <input id="button_express_code" type="hidden" value="" name="button_express_code"><input style="border-color: #428bca;" type="text" autocomplete="off" maxlength="26" placeholder="请输入快递单号" id="button_tracking_number" class="inp-metro" name="button_tracking_number"> <button style="background-color: #428bca" class="button-query" type="submit" onclick="return doTrack()" id="query">查询</button> </div> </div> <input type="hidden" name="lang" value="cn" /> </form> <div id="TRNum"></div> </div> <script type="text/javascript"> function doTrack() { var num=document.getElementById("button_tracking_number").value; var expCode=document.getElementById("button_express_code").value; var width = document.getElementById("query").parentNode.offsetWidth; TRACKINGMORE.trackMynumber({ TR_ElementId:"TRNum", //必须,指定悬浮位置的元素ID。 TR_Width:width, //可选,指定查询结果宽度,最小宽度为600px,默认撑满容器。 TR_Height:600, //可选,指定查询结果高度,最大高度为800px,默认撑满容器。 TR_ExpressCode:expCode, //可选,指定运输商,默认为自动识别。 TR_Lang:"cn", //可选,指定UI语言,默认根据浏览器自动识别。 TR_Num:num //必须,指定要查询的单号。 }); return false; } </script>
温馨提示:不要短时间内频繁调用 More平台会检测当前IP,过短调用会提示I异常 需要输入验证码 最终效果展示如下:
PS:这种每次点击订单请求快递数据并不是最优的做法。因为上面我们也有提到过快递订阅这个功能,其实,最佳的做法是 订阅之后,快递平台推送到回调地址的最新物流信息,我们可以将这些物流信息保存再对应订单的快递物流字段里面。这样前台取的时候可以先判断
物流信息字段是否为空,如果不为空 就直接取物流信息字段,这样既可以减少网络流量消耗,也可以加快系统反应时间,带来更好的用户体验。如果不喜欢用官方自带的这个显示框,也可以改成Bootstrap的样式框显示,具体做法如下:
//查看物流(订单id,this,物流信息,快递单号,快递公司简码) function viewSenderMessage(id, obj, message, number, company) { debugger; if (!isNullEmptyUndefined(message)) { var d = dialog({ title: '物流最新跟踪', content: "<div style='line-height:20px;'>" + message + "</div>", okValue: '确定', ok: true, }); d.show(obj); } else if (!isNullEmptyUndefined(number) && !isNullEmptyUndefined(company)) { // doTrack(number, company); ajaxGetContent("@Url.Action("ViewExpress")" + "?companyNumber=" + company + "&expressNumber=" + number, function (data) { var d = dialog({ title: '物流最新跟踪', content: data, okValue: '确定', ok: true, }); d.show(obj); }); } else { var d = dialog({ title: '物流最新跟踪', content: "无物流跟踪信息", okValue: '确定', ok: true, }); d.show(obj); } }
按照逻辑,如果物流信息为空 此时我们就需要从Action ViewExpress中获取单个快递信息。ViewExpress代码如下:
public ActionResult ViewExpress(string expressNumber, string companyNumber) { string urlstr = null; string requestdata = "carrier_code=" + companyNumber + "&tracking_number=" + expressNumber; //此处是获取单个物流信息 采用get请求,method传入codeNumberGet string result = new TrackingMoreAPI().getOrderTracesByJson(requestdata, urlstr, "codeNumberGet"); if (!result.Contains("code")) return Content("暂无物流信息,请稍后重试"); var code = TarckMoreExpressCompany.GetValueFromJson(result, "code"); if (code != "200") { message = EnmHelper.GetEnmName((TrackMoreServerResponse)int.Parse(code)); //这里多加一层判断 4031表示请求成功,但是响应为空,意思就是 TrackMore平台上还没有这个快递单数据,所以要再次调之前创建物流项目这个接口 if (code == "4031") { OperationResult resMessage = TrackMore.TrackExpressSubscibe(new TrackMoreModel() { carrier_code = companyNumber, tracking_number = expressNumber }); if (resMessage.IsSuccess) { return ViewExpress(expressNumber, companyNumber); } else { message = resMessage.FailMessage; } } } else { Root root = JsonConvert.DeserializeObject<Root>(result); if (root.Data.origin_info.trackinfo.Count == 0) message = "暂无物流信息,请稍后重试";
//此处将返回的物流信息以 时间+空格+物流消息 格式循环输出 foreach (var item in root.Data.origin_info.trackinfo) { message += item.Date + " " + item.StatusDescription + "\r\n <br/>"; } }
return Content(message); }
获取单个物流信息采用的是get请求,所以再发送http请求的时候不能再用post 的流方式,需要多加一个get请求的,代码如下:
public string HttpGet(string Url, string postDataStr, string method) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == "" ? "" : "?") + postDataStr); request.Method = "GET"; request.ContentType = "application/x-www-form-urlencoded"; request.Timeout = 30 * 1000; request.Method = method; request.Headers["Trackingmore-Api-Key"] = ApiKey; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream myResponseStream = response.GetResponseStream(); StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8")); string retString = myStreamReader.ReadToEnd(); myStreamReader.Close(); myResponseStream.Close(); return retString; }
最终效果展示
注:顺丰即日取消了对外第三方接口,故不能通过第三方物流平台获取到顺丰的物流信息,需要和顺丰开放平台(目前据顺丰IT人员透露,此平台已无人维护,已物流服务已转至顺丰官方的企业服务平台)对接 才能查到最新顺丰的物流信息
顺丰的接口对接请转场至下文《物流一站式查询之顺丰接口篇》
- 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载!
- 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。
- 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入
- 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。
- 我的博客:http://www.cnblogs.com/zhangxiaoyong/