105

物流一站式查询之顺丰接口篇 - 潇十一郎

 6 years ago
source link: https://www.cnblogs.com/zhangxiaoyong/p/8317229.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

连载篇提前看

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

物流一站式查询之TrackingMore篇

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

物流一站式查询之快递100

前情提要

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

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

762349-20180119154226412-718304213.png

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

762349-20180119154943240-483775484.png

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

762349-20180119155702146-1172462260.png

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

开发篇

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

下单接口

1.1. 功能描述

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

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

2)      为订单分配运单号。

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

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

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

其中 密匙生成规则:

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

 

762349-20180119160646553-901046857.png

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

① 编写下单操作实体类

ContractedBlock.gifExpandedBlockStart.gif

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请求体如下

ContractedBlock.gifExpandedBlockStart.gif

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;
        }

下单请求方法如下

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

ContractedBlock.gifExpandedBlockStart.gif

View Code

ContractedBlock.gifExpandedBlockStart.gif

View Code

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

ContractedBlock.gifExpandedBlockStart.gif

View Code

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

ContractedBlock.gifExpandedBlockStart.gif

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,顺利拿到了运单号)

762349-20180119170855131-636412040.gif

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

1.1. 功能描述

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

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

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

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

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

ContractedBlock.gifExpandedBlockStart.gif

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请求体方法如下

ContractedBlock.gifExpandedBlockStart.gif

View Code

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

ContractedBlock.gifExpandedBlockStart.gif

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

请求全过程如下

762349-20180119180608553-84723130.gif

不知道大家留意看没,再请求成功后往集合里面添加了第一条数据后,我返到上面查看了下收到的数据,有一个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 中,我们再去处理操作码和文字描述的问题。首先我们需要找到操作码,操作码模样如下

762349-20180119180944178-800031105.png

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

762349-20180119181107943-1195578234.png

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

762349-20180119181255021-1537701645.png

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

ContractedBlock.gifExpandedBlockStart.gif

View Code

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

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

最后的效果如下:

762349-20180119182633646-1422917823.gif

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

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。操作代码如下

①定义推送模型容器

ContractedBlock.gifExpandedBlockStart.gif

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 物流接口详解。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK