物流一站式查询之顺丰接口篇

连载篇提前看

物流一站式单号查询之快递鸟API接口

物流一站式查询之TrackingMore篇

物流一站式查询之顺丰接口篇

物流一站式查询之快递100

前情提要

本篇内容承接上篇《物流包裹一站式查询(TrackingMore) 文末所说,顺丰物流关闭了对第三方的物流接口,导致众多第三方物流平台查询不到顺丰快递的物流信息。但是问题终归是要解决滴,别家不行,咱就直接用顺丰自家的。

原本网上找顺丰物流信息查询发现顺丰开放平台  看了下介绍,因为也是顺丰的平台,也没多想,看到流程还是比较清晰的。

本来想找在线客服咨询下,结果发现在线客服有的只是一个群号,而且还不能加人了,于是乎就按照接入流程开始操作,本地都开发的差不多了,后来意外联系到一个顺丰的IT人员,通过他得知,顺丰物流信息接口已经转到另一个部门和平台操作了,这个开放平台已经几乎没有人维护了。于是再他的协助下,我得到了最新的对接文档。按找新文档,之前的开发的全部得重写,请求接口不一样,数据传输和接收方式不一样,由开放平台的Json格式到现在用XML传输。这里贴一下接入规范文档的目录

顺便提一下 顺丰路由查询接口 就是 查询物流信息的接口,不过再顺丰平台使用此接口有个前提条件,就是必须是顺丰的月结用户。登陆 顺丰平台 可以查看到基本信息

注:①不是顺丰月结卡 用户 或者企业,不能接入路由查询   ② 不是通过顺丰接口下单的运单号,不能接入路由推送接口,换而言之,如果是通过顺丰大客户发货系统或者其他方式进行的打单获取到的快递单号,无法对此单进行订阅推送操作。

 

开发篇

看完基本流程和接入规范之后,就可以按照文档规范进行编码。因为目前我只用到了标红的三个接口,所以接下来对这三个接口一一讲解。(注开发之前本机IP需要得到官方授权,不然会请求会返回IP未授权)

下单接口

1.1. 功能描述

下订单接口根据客户需要,可提供以下三个功能:

1)      客户系统向顺丰下发订单。

2)      为订单分配运单号。

3)      筛单(可选,具体商务沟通中双方约定,由顺丰内部为客户配置)。

此接口也用于路由推送注册。客户的顺丰运单号不是通过此下订单接口获取,但却需要获取BSP的路由推送时,需要通过此接口对相应的顺丰运单进行注册以使用BSP的路由推送接口。

按照接入文档所说 接口通信协议支持WEBSERVICE及HTTP/POST协议,以下我是采用HTTP/POST协议 开发

其中 密匙生成规则:

  • 先把XML报文与checkword前后连接。
  • 把连接后的字符串做MD5编码。
  • 把MD5编码后的数据进行Base64编码,此时编码后的字符串即为校验码。

 

元素的请求和响应内容字段和描述比较多,这里就不一一贴出来了,文末会提供接口文档下载地址。

① 编写下单操作实体类

 #region 下单操作实体

    public class OrderService
    {
        /// <summary>
        ///     订单号
        /// </summary>
        public string orderid { get; set; }

        /// <summary>
        ///     运单号 顺丰运单号,一个订单只能有一个母单号,如果是子母单的情况,以半角逗号分隔,主单号在第一个位置,如 “755123456789,001123456789,002123456789” ,
        ///     对于路由推送注册,此字段为必填。
        /// </summary>
        public string mailno { get; set; }

        /// <summary>
        ///     寄件方公司名称,如果需要 生成电子运单,则为必填。
        /// </summary>
        public string j_company { get; set; }

        /// <summary>
        ///     寄件方联系人,如果需要生成电子运单,则为必填。
        /// </summary>
        public string j_contact { get; set; }

        /// <summary>
        ///     寄件方联系电话,如果需要生成电子运单,则为必填。
        /// </summary>
        public string j_tel { get; set; }

        /// <summary>
        ///     寄件方手机
        /// </summary>
        public string j_mobile { get; set; }

        /// <summary>
        ///     寄件方详细地址
        /// </summary>
        public string j_address { get; set; }

        /// <summary>
        ///     到件方公司名称
        /// </summary>
        public string d_company { get; set; }

        /// <summary>
        ///     收件方联系人
        /// </summary>
        public string d_contact { get; set; }

        /// <summary>
        ///     收件人联系电话
        /// </summary>
        public string d_tel { get; set; }

        /// <summary>
        ///     收件人手机
        /// </summary>
        public string d_mobile { get; set; }

        /// <summary>
        ///     收件人详细地址
        /// </summary>
        public string d_address { get; set; }

        /// <summary>
        ///     包裹数(1个包裹对应一个运单号)
        /// </summary>
        public int parcel_quantity { get; set; }

        /// <summary>
        ///     快件产品类别(只有再商务上与顺丰约定的类别方可使用)
        /// </summary>
        public string express_type { get; set; }

        /// <summary>
        ///     顺丰月结卡号
        /// </summary>
        public string custid { get; set; }

        /// <summary>
        ///     备注
        /// </summary>
        public string remark { get; set; }

        /// <summary>
        ///     订单元素
        /// </summary>
        public OrderCargo OrderCargos { get; set; }
    }

    public class OrderCargo
    {
        /// <summary>
        ///     货物名称
        /// </summary>
        public string name { get; set; }
    }

    #endregion
View Code

② 定义三个全局属性,因为再几个请求我们都会使用到这三个

        //开发环境URL(文档中有提供)
        private static readonly string Posturl = "http://bsp-ois.sit.sf-express.com:9080/bsp-ois/sfexpressService";
        //开发环境编码(文档中有提供)
        private static readonly string Bianma = "" + ConfigHelper.GetKey("bianma") + "";
        //开发环境密匙(文档中有提供)
        private static readonly string Checkword = "" + ConfigHelper.GetKey("checkword") + "";

③ 下单操作方法

       /// <summary>
        /// 下单操作方法
        /// </summary>
        /// <param name="model">下单操作实体</param>
        /// <returns> </returns>
        public static string GetHttpBack(OrderService model)
        {
            //得到下单XML请求体
            var xml = Getxml(model);
            //生成密匙
            var pass = Convert.ToBase64String(MD5(xml + Checkword));
            //下单请求
            var str = GethttpBack(Posturl, "xml=" + xml + "&verifyCode=" + pass);
            return str;
        }

下单XML请求体如下

/// <summary>
        /// 构建下单XML请求体
        /// </summary>
        /// <param name="model">下单操作实体</param>
        /// <returns></returns>
        private static string Getxml(OrderService model)
        {
            string[] xmls =
            {
                "<Request service='OrderService' lang='zh-CN'>",
                "<Head>" + ConfigHelper.GetKey("bianma") + "</Head>",
                "<Body>",
                "<Order",
                "orderid='" + model.orderid + "'",
                "j_company='" + model.j_company + "'",
                "j_contact='" + model.j_contact + "'",
                "j_tel='" + model.j_tel + "'",
                "j_mobile='" + model.j_mobile + "'",
                "j_address='" + model.j_address + "'",
                "d_company='" + model.d_company + "'",
                "d_contact='" + model.d_contact + "' ",
                "d_tel='" + model.d_tel + "'",
                "d_mobile='" + model.d_mobile + "'",
                "d_address='" + model.d_address + "'",
                "parcel_quantity='" + model.parcel_quantity + "'",
                "express_type='" + model.express_type + "'",
                "custid='" + model.custid + "'",
                "remark='" + model.remark + "'>",
                "<Cargo name='" + model.OrderCargos.name + "'></Cargo>",
                "</Order>",
                "</Body>",
                "</Request>"
            };
            var xml = "";
            foreach (var s in xmls)
                if (xml == "")
                    xml = s;
                else
                    xml += "\r\n" + s;
            return xml;
        }
View Code

MD5编码方法如下

      private static byte[] MD5(string str)
        {
            var result = Encoding.UTF8.GetBytes(str);
            MD5 md5 = new MD5CryptoServiceProvider();
            var output = md5.ComputeHash(result);
            return output;
        }

下单请求方法如下

因为下单成功会返回订单号和运单号 以及筛单结果,所以我们先定义一个返回的响应报文模型容器

  public class SfOrderResponse
    {
        /// <summary>
        ///     订单号
        /// </summary>
        public string orderid { get; set; }

        /// <summary>
        ///     运单号
        /// </summary>

        public string mailno { get; set; }

        /// <summary>
        ///     筛单结果 1 人工确认 2 可收派 3 不可以收派
        /// </summary>
        public string filter_result { get; set; }
    }
View Code
 private static string GethttpBack(string add, string post)
        {
            var we = new WebClient();
            var sMessage = "";
            #region 下单
            try
            {
                SfOrderResponse sfresponse;
                if (post != "")
                {
                    //编码,尤其是汉字,事先要看下抓取网页的编码方式
                    var postData = Encoding.UTF8.GetBytes(post);
                    we.Headers.Clear();
                    //采取POST方式必须加的header,如果改为GET方式的话就去掉这句话即可
                    we.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
                    sMessage = Encoding.UTF8.GetString(we.UploadData(add, "POST", postData));
                    //读取XML资源中的指定节点内容
                    if (Convert.ToString(GetNodeValue(sMessage, "Head")) == "ERR")
                        sMessage = XElement.Parse(sMessage).Value;
                    else
                        sfresponse = new SfOrderResponse
                        {
                            //获取xml中orderid、mailno、destcode等节点值
                            orderid = GetXmlNodeValue(sMessage, "OrderResponse", "orderid"),
                            mailno = GetXmlNodeValue(sMessage, "OrderResponse", "mailno"),
                            filter_result = GetXmlNodeValue(sMessage, "OrderResponse", "filter_result")
                        };
                }
                else
                {
                    sMessage = Encoding.UTF8.GetString(we.DownloadData(add));
                    if (Convert.ToString(GetNodeValue(sMessage, "Head")) == "ERR")
                        sMessage = XElement.Parse(sMessage).Value;
                    else
                        sfresponse = new SfOrderResponse
                        {
                            //获取xml中orderid、mailno、destcode等节点值
                            orderid = GetXmlNodeValue(sMessage, "OrderResponse", "orderid"),
                            mailno = GetXmlNodeValue(sMessage, "OrderResponse", "mailno"),
                            filter_result = GetXmlNodeValue(sMessage, "OrderResponse", "destcode")
                        };
                }
            }
            catch
            {
                if (sMessage.IndexOf("8001") > 0) sMessage = "IP未授权";
            }
            #endregion
            //释放资源
            we.Dispose();
            return sMessage;
        }
View Code

其中读取XML资源中指定节点内容的方法如下

 /// <summary>
        ///     读取XML资源中的指定节点内容
        /// </summary>
        /// <param name="source">XML资源</param>
        /// <param name="nodeName">节点名称</param>
        /// <returns>节点内容</returns>
        public static object GetNodeValue(string source, string nodeName)
        {
            if (source == null || nodeName == null || source == "" || nodeName == "" ||
                source.Length < nodeName.Length * 2) return null;

            var start = source.IndexOf("<" + nodeName + ">") + nodeName.Length + 2;
            var end = source.IndexOf("</" + nodeName + ">");
            if (start == -1 || end == -1)
                return null;
            if (start >= end)
                return null;
            return source.Substring(start, end - start);
        }
View Code

获取XML任意节点中某个属性值的方法如下

 /// <summary>
        ///  获取xml任意节点中某个属性值
        /// </summary>
        /// <param name="strXml">xml</param>
        /// <param name="strNodeName">节点名称</param>
        /// <param name="strValueName">属性名称</param>
        /// <returns></returns>
        public static string GetXmlNodeValue(string strXml, string strNodeName, string strValueName)
        {
            try
            {
                var xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(strXml);
                var xNode = xmlDoc.SelectSingleNode("//" + strNodeName + "");
                var strValue = xNode.Attributes[strValueName].Value;
                return strValue;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }
View Code

这样我们就大概写完了下单的请求逻辑代码,现在我们可以写一条测试数据进行测试:

   SfExpress.GetHttpBack(new OrderService()
            {
                orderid = ConfigHelper.GetKey("orderID"),
                j_company = "深圳市*******有限公司",
                j_contact = "潇十一郎",
                j_mobile = "123456789",
                j_address = "这是发货地址",
                d_company = "收件公司名称",
                d_contact = "收件人",
                d_tel = "13237157517",
                d_mobile = "78946561",
                d_address = "收货地址",
                parcel_quantity = 1,
                express_type = "1",
                custid = "9999999999",
                remark = "下单测试",
                OrderCargos = new OrderCargo() { name = "显示屏" }
            });

请求过程全过程如下(若每个订单号只能下单一次,若重复下单则会返回Err 重复下单,下面演示中,第一次演示的是重复下单,后来修改了订单号重新跑了一次,就返回了OK,顺利拿到了运单号)

 

 

路由查询

有了上一个下单作为铺垫,那我们路由查询就比较好处理了。

1.1. 功能描述

客户可通过此接口查询顺丰运单路由,BSP会在响应XML报文返回当时点要求的全部路由节点信息。

此路由查询接口支持两类查询方式:

1)      根据顺丰运单号查询:查询请求中提供接入编码与运单号,BSP将验证接入编码与所有请求运单号的归属关系,系统只返回具有正确归属关系的运单路由信息。

2)      根据客户订单号查询:查询请求中提供接入编码与订单号,BSP将验证接入编码与所有请求订单号的归属关系,对于归属关系正确的订单号,找到对应的运单号,然后返回订单对应运单号的路由信息。适用于通过BSP下单的客户订单。

①编写请求对应的模型容器

/// <summary>
    ///     路由请求实体
    /// </summary>
    public class RotueSehachService
    {
        /// <summary>
        ///     查询号类别 1运单号查询  2 订单号查询
        /// </summary>
        public int tracking_type { get; set; } = 1;

        /// <summary>
        ///     查询号 tracking_type=1 则此值是运单号 tracking_type为1=2 此值是订单号
        /// </summary>
        public string tracking_number { get; set; }

        /// <summary>
        ///     1 标准路由查询 2定制路由查询
        /// </summary>
        public int method_type { get; set; } = 1;
    }
View Code

 

 

②根据响应报文编写对应模型容器

 public class RouteResponse
    {
        public string mailno { get; set; }
        public Route Route { get; set; }
        public string failMessage { get; set; } = string.Empty;
    }

    public class Route
    {
        public string accept_time { get; set; }

        public string accept_address { get; set; }

        public string remark { get; set; }

      public string opcode { get; set; }
    }

③路由查询

 public static List<RouteResponse> GetHttpRotueSheach(RotueSehachService model)
        {
            //返回路由查询XML请求体
            var xml = GetRoutexml(model);
            //MD5编码
            var pass = Convert.ToBase64String(MD5(xml + Checkword));
            var strData = "xml=" + xml + "&verifyCode=" + pass;
            var result = GetRouteack(Posturl, "xml=" + xml + "&verifyCode=" + pass);
            return result;
        }

其中,构建查询XML请求体方法如下

 /// <summary>
        /// 构建路由查询XML请求体
        /// </summary>
        /// <param name="model">路由查询请求模型</param>
        /// <returns></returns>
        private static string GetRoutexml(RotueSehachService model)
        {
            string[] xmls =
            {
                "<Request service='RouteService' lang='zh-CN'>",
                "<Head>" + ConfigHelper.GetKey("bianma") + "</Head>",
                "<Body>",
                "<RouteRequest",
                "tracking_type='" + model.tracking_type + "'",
                "method_type='" + model.method_type + "'",
                "tracking_number='" + model.tracking_number + "'/>",
                "</Body>",
                "</Request>"
            };
            var xml = "";
            foreach (var s in xmls)
                if (xml == "")
                    xml = s;
                else
                    xml += "\r\n" + s;
            return xml;
        }
View Code

MD5方法下单请求中已经贴出,其中GetRouteack请求方法如下

 /// <summary>
        /// 路由查询
        /// </summary>
        /// <param name="add">URL地址</param>
        /// <param name="post">编码后的请求体</param>
        /// <returns></returns>
        private static List<RouteResponse> GetRouteack(string add, string post)
        {
            var client = new WebClient();
            var sXml = "";
            var rutoelist = new List<RouteResponse>();
            try
            {
                //编码,尤其是汉字,事先要看下抓取网页的编码方式
                var postData = Encoding.UTF8.GetBytes(post);
                client.Headers.Clear();
                //采取POST方式必须加的header,如果改为GET方式的话就去掉这句话即可
                client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
                //sXml = Encoding.UTF8.GetString(client.UploadData(add, "POST", postData));
                sXml =
                    "<?xml version='1.0' encoding='UTF-8'?><Response service='RouteService'><Head>OK</Head><Body>" +
                    "<RouteResponse mailno='444032636081'><Route remark='顺丰速运 已达到武汉航空总站' accept_time='2018-01-19 06:50:55' accept_address='武汉市' opcode='50'/></RouteResponse>" +
                    "<RouteResponse mailno='444032636081'><Route remark='顺丰速运 快递正在发往武汉航空总站' accept_time='2018-01-18 18:18:55' accept_address='深圳市' opcode='50'/></RouteResponse>" +
                    "<RouteResponse mailno='444032636081'><Route remark='顺丰速运 快递达到深圳中转站' accept_time='2018-01-18 15:12:55' accept_address='深圳市' opcode='50'/></RouteResponse>" +
                    "<RouteResponse mailno='444032636081'><Route remark='顺丰速运 已收取快件' accept_time='2018-01-18 10:10:55' accept_address='深圳市' opcode='50'/></RouteResponse>" +
                    "</Body></Response>";
                //判断返回响应体是否是ERR
                if (Convert.ToString(GetNodeValue(sXml, "Head")) == "ERR")
                {
                    sXml = XElement.Parse(sXml).Value;
                    rutoelist.Add(new RouteResponse {failMessage = XElement.Parse(sXml).Value});
                }

                //将xml字符串转换为XML文档
                var xmlDoc = XDocument.Parse(sXml);
                //获取 文档中子代元素 RouteResponse 的所有集合
                var nodelist = xmlDoc.Descendants("RouteResponse");
                if (nodelist != null)
                {
                    //获取源集合中每个元素和子元素的集合
                    var pieceareas = nodelist.Elements();
                    foreach (var item in pieceareas)
                        //循环添加到路由响应容器集合中
                        rutoelist.Add(new RouteResponse
                        {
                            //获取XML mailno 属性值
                            mailno = GetXmlNodeValue(sXml, "RouteResponse", "mailno"),
                            Route = new Route
                            {
                                opcode = item.Attribute("opcode").Value,
                                accept_address = item.Attribute("accept_address").Value,
                                accept_time = item.Attribute("accept_time").Value,
                                remark = item.Attribute("remark").Value
                            }
                        });
                }
            }
            catch (Exception e)
            {
                client.Dispose();
                rutoelist.Add(new RouteResponse {failMessage = XElement.Parse(sXml).Value});
                return rutoelist;
            }

            client.Dispose();
            return rutoelist;
        }
View Code

因为开发测试环境,没有真实返回路由数据,所以我这里构造了几个返回数据,写到这里,我们路由查询的大部分逻辑已经完成,剩下就是调用和获取到路由返回集合,循环拼接输出我们想要的物流信息,具体操作可参考如下

      #region 物流查询
            var message = "";
            //顺丰单独处理
            if (companyNumber == "sf-express")
            {
                #region 获取顺丰物流信息
                //先进行路由查询,若返回未下单则再进行下单操作。
                List<RouteResponse> result = SfExpress.GetHttpRotueSheach(new RotueSehachService()
                {
                    tracking_number = ConfigHelper.GetKey("orderID")
                });
                if (result.Count >0)
                {
                    //循环返回的路由集合
                    foreach (var item in result)
                    {
                        //判断错误信息不为空
                        if (item.failMessage == string.Empty)
                        {
                            //将所有路由信息 按时间+路由地址拼接
                            message += item.Route.accept_time + "   " + item.Route.accept_address + "\r\n <br/>";
                        }
                        else
                        {
                            message += item.failMessage + "\r\n <br/>  ";
                        }
                    }
                }
                else
                {
                    return Content("暂无物流信息,请稍后重试");
                }

            #endregion

请求全过程如下

不知道大家留意看没,再请求成功后往集合里面添加了第一条数据后,我返到上面查看了下收到的数据,有一个opcode=50 的,顺便说下,这个opcode是返回的路由操作码,顺丰有个专门的文档记录这些返回操作码,那现在问题就来了,只是返回这个操作码,我们并不能马上知道操作码对应的文字描述。拿到操作码去官方文档上一个一个对,也不是我们程序员的思维。所以,此时我们应该想办法解决,当我们收到这个请求码的时候,就自动获取它的描述,这样就一目了然,方便我们后面操作。

 

单独处理路由操作码

其实改起来也不麻烦,我们需要把之前定义的Route 模型稍微改下 让它继承我们定义的操作码和对应的操作码描述类即可 代码如下:

    public class Route: SfErrorEntity
    {
        public string accept_time { get; set; }

        public string accept_address { get; set; }

        public string remark { get; set; }

    //  public string opcode { get; set; }
    }

在SfErrorEntity 中,我们再去处理操作码和文字描述的问题。首先我们需要找到操作码,操作码模样如下

 

 我们可以再项目中新建一个资源文件,名字叫SFcode.resx,然后添加资源我们选择添加文本文件叫 sfResourse

接下来我们把文档中的操作码和文字描述 复制进去

最后一步 就是编写SfErrorEntity 类如下

  public class SfErrorEntity
    {
        /// <summary>
        /// 定义一个字典存放操作码和对应描述
        /// </summary>
        private static Dictionary<int, string> _errorDic;

        /// <summary>
        /// 定一个操作码集合
        /// </summary>
        public static Dictionary<int, string> ErrList
        {
            get
            {
                //获取时先判断字典中是否存在数据,若存在则直接返回
                if (_errorDic != null && _errorDic.Count > 0)
                    return _errorDic;
                //不存在,首先实例化当前操作字典
                _errorDic = new Dictionary<int, string>();
                //读取资源文件中的文本文件 先按行分隔
                var temp = SFCode.sfResourse.Split(new char[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
                //拿到所有行,再按空格分隔
                foreach (var result in temp.Select(m => m.Split('\t')))
                {
                    //将对应的操作码和描述添加到当前字典中
                    _errorDic.Add(int.Parse(result[0]), result[1]);
                }

                return _errorDic;
            }
        }
        /// <summary>
        /// 存放操作码描述
        /// </summary>
        public string ErrDescription { get; set; }

        /// <summary>
        /// 操作码
        /// </summary>
        private int _opcode;

        public int Opcode
        {
            get { return _opcode; }
            set
            {
                //写入操作码时,就拿当前写入的值和操作码集合中的数据做比较,同时返回对应的操作码描述
                _opcode = value;
                ErrDescription = ErrList.FirstOrDefault(m => m.Key == value).Value;
            }
        }
    }
View Code

注:.Net Core中获取资源方式有所更改,用反射可以获取到:

ResourceManager resManagerA = new ResourceManager("解决方案名.资源名", typeof(ExpressCodeBase).Assembly);
var res= resManagerA.GetObject("资源文件名");

最后的效果如下:

路由推送

上文中也提到过,只有是通过顺丰接口下的订单,才能走路由推送这个接口,还是先看一下关于路由推送的描述

1.1. 功能描述

该接口用于当路由信息生产后向客户主动推送要求的顺丰运单路由信息。推送方式为增量推送,对于同一个顺丰运单的同一个路由节点,不重复推送。

客户需提供一个符合以下规范的HTTP URL,以接收BSP推送的信息。

1)      请求方法为:“application/x-www-form-urlencoded; charset=UTF-8”。

Key:content

2)      信息以URL编码(字符集为UTF-8)的XML格式,通过HTTP POST方式推送给客户。

3)      客户在接收到信息后,需要先对其进行URL解码,得到相应的XML。

4)      在客户处理XML信息后,向BSP返回响应XML报文,响应XML报文结果只能为OK/ERR(参见XML报文说明),BSP将重新推送此次交易的所有信息。

路由推送整体和上面两个类似,这个路有推送返回更简单,只有OK,或者Err。操作代码如下

①定义推送模型容器

 /// <summary>
    ///     路由推送模型
    /// </summary>
    public class RoutePushService:SfErrorEntity
    {
        /// <summary>
        ///     路由节点信息编号,每一个id 代表一条不同的路由节点信息
        /// </summary>
        public int id { get; set; }

        /// <summary>
        ///     顺丰运单号
        /// </summary>
        public string mailno { get; set; }

        /// <summary>
        ///     客户订单号
        /// </summary>
        public string orderid { get; set; }

        /// <summary>
        ///     路由节点产生的时间,格式:YYYY-MM-DDHH24:MM:SS
        /// </summary>
        public DateTime acceptTime { get; set; }

        /// <summary>
        ///     路由节点发生的城市
        /// </summary>
        public string acceptAddress { get; set; } = "深圳";

        /// <summary>
        ///     路由节点具体描述
        /// </summary>
        public string remark { get; set; } = "上门收件";

        /// <summary>
        ///     路由节点操作码
        /// </summary>
       // public string opCode { get; set; } = "50";
    }
View Code
   #region 路由推送
        public static string RoutePushService(RoutePushService model)
        {
            var xml = GetPushxml(model);
            var pass = Convert.ToBase64String(MD5(xml + Checkword));
            var strData = "xml=" + xml + "&verifyCode=" + pass;
            var strHeaders = "Content-Type: application/x-www-form-urlencoded\r\n";
            var bytePost = Encoding.UTF8.GetBytes(strData);
            var byteHeaders = Encoding.UTF8.GetBytes(strHeaders);
            var str = GetPushBack(Posturl, "xml=" + xml + "&verifyCode=" + pass);
            return str;
        }

        /// <summary>
        /// 构建路由推送XML
        /// </summary>
        /// <param name="model">推送容器</param>
        /// <returns></returns>
        private static string GetPushxml(RoutePushService model)
        {
            string[] xmls =
            {
                "<Request service='RouteService' lang='zh-CN'>",
                "<Body>",
                "<WaybillRoute",
                "id='" + model.id + "'",
                "mailno='" + model.mailno + "'",
                "acceptTime='" + model.acceptTime.ToString("yyyy-MM-dd HH:mm:ss") + "'",
                "acceptAddress='" + model.acceptAddress + "'",
                "remark='" + model.remark + "'",
                "opCode='50'/>",
                "orderid='" + model.orderid + "'/>",
                "</Body>",
                "</Request>"
            };
            var xml = "";
            foreach (var s in xmls)
                if (xml == "")
                    xml = s;
                else
                    xml += "\r\n" + s;
            return xml;
        }

       /// <summary>
       /// 推送
       /// </summary>
       /// <param name="add">URL</param>
       /// <param name="post">数据</param>
       /// <returns></returns>
        private static string GetPushBack(string add, string post)
        {
            var pusthclient = new WebClient();
            var sXml = "";
            try
            {
                //编码,尤其是汉字,事先要看下抓取网页的编码方式
                var postData = Encoding.UTF8.GetBytes(post);
                pusthclient.Headers.Clear();
                //采取POST方式必须加的header,如果改为GET方式的话就去掉这句话即可
                pusthclient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
                sXml = Encoding.UTF8.GetString(pusthclient.UploadData(add, "POST", postData));
                //获取Head是否是ERR,若不是,直接返回OK
                if (Convert.ToString(GetNodeValue(sXml, "Head")) == "ERR")
                    sXml = XElement.Parse(sXml).Value;
                else
                    sXml = "OK";
            }
            catch (Exception e)
            {
                sXml = e.Message;
            }
            pusthclient.Dispose();
            return sXml;
        }
        #endregion

这个路由推送 ,可以写再服务里面,例如:客户下单了,是代发货状态,那么可以筛选出来,写一个服务,调用这个推送的接口,返回OK,则往数据库一个标示字段注明订阅成功,否则反。

有了路由推送,然后就是回调了,推送成功之后,顺丰会根据当前物流单,若有物流信息发生改变,则会主动推向提前约定好的回调地址上,此时只需要再回调方法中做一些业务上的处理即可。

 做完以上的开发,剩下的就是给顺丰那边接口对接人员发邮件申请进入联调测试阶段,联调测试通过之后再那那边签协议接入正式环境投入使用。

注:联调本机开发环境即可,无需部署线上。

 

关于顺丰接口大概就这么多。下一篇 关于快递100 物流接口详解。

 

posted @ 2018-01-19 18:38  潇十一郎  阅读(12074)  评论(14编辑  收藏  举报