物流包裹一站式查询(TrackingMore)

小说:学习能力个案:1年级 淘气包大变身!作者:平董更新时间:2019-03-23字数:60653

物流包裹一站式查询(TrackingMore)


快递查询接口

目前提供快递查询的接口平台有:

  • 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元。

 

快递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

 

请求头部信息参数

参数名称 类型 说明 是否必须
Content-Type: 
application/json
定义请求头部的数据格式
Trackingmore-Api-Key: 
string Trackingmore 后台获取的API

 

 

 

 

 请求body参数说明

参数说明 类型 说明 是否必须
tracking_number
string 查询快递的快递单号
carrier_code
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,

    }
View Code

然后我们可以获取枚举描述值来快速知道返回了什么状态,而不用去官网查状态码的意思。这里也把获取枚举描述值的代码贴出来如下:

  /// <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; }
    }
View Code

一个枚举帮助类,其中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;
        }
    }
View Code

②创建一个TrackMoreModel模型类用于传输快递单号和快递公司编码

 public class TrackMoreModel
    {
        /// <summary>
        /// 快递单号
        /// </summary>
        public string tracking_number { get; set; }
        /// <summary>
        /// 快递公司编码
        /// </summary>
        public string carrier_code { get; set; }

    }
View Code

③创建一个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();
        }
    }
View Code

其中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(""");
        }    
View Code

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,默认撑满容器。
希望邓森山的死能够引起天下父母的反思 《头脑特工队》为何中国内地遇冷? 罗李华谈:属兔的人2016年运程 最好的时光 好声音被曝黑幕?认真,你就输了! 爱上我,还是爱上我优越的条件 春节团聚时需关注的儿童心理 股市解套的几种方法--必看 罗李华谈:属鼠的人2016年运程 经典原创禅诗:【禅.十字回文诗】

他们为何恐惧至不顾生命:涉嫌吸毒者跳楼致死事件反思 我把老公叫“爸爸”--称呼背后隐藏的秘密 人民币贬值对我们意味着什么? 女博士:不是我们难嫁,是你娶不起 回趟国才知道中国物价有多高 既然选择了远方,那只能风雨兼程 回趟国才知道中国物价有多高 一点儿都不蠢 五只猴子的故事 孩子的鲜血,能换回父母的觉醒吗? 为孩子去学心理学,必要么? 既然选择了远方,那只能风雨兼程 名字,孩子成长中不能忽视 我女儿喜欢了富二代该怎么办? 没吃到香蕉的猴子 当面训老公,“伤”了你孩子 为孩子参加选秀拉票 好不好? 新时代的养育观——你知道么? 青春期——改变人生的最后机会

编辑:陵顺通

我要说两句: (0人参与)

发布