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

Posted 潇十一郎

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了物流一站式查询之顺丰接口篇相关的知识,希望对你有一定的参考价值。

连载篇提前看

物流一站式单号查询之快递鸟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; 以上是关于物流一站式查询之顺丰接口篇的主要内容,如果未能解决你的问题,请参考以下文章

顺丰快递怎么一直显示准备送往下一站

如何使用API接口查询物流信息?

快递100的功能

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

顺丰快递商家寄件发货接口API类型和接入流程方案快递100接口Demo

常用物流快递单号查询接口种类及对接方法