草庐IT

快速集成快递物流轨迹查询功能

华小睿的笔记 2023-03-28 原文

前沿

近年来,随着互联网的发展,各个行业都在用新的技术、新的观念为自己的发展打下坚实的基础,如今网络已经渗透到了人们的日常生活中,网上购物成了大家喜爱的方式。各类商城、APP、小程序等应用不断涌现,涉及各行各业,都在争抢成为人们网上购物的载体。网上购物物流是必不可少的一环,这些应用是如何解决物流信息系统化的,让消费者方便快捷的了解物品的实时轨迹信息和系统内部流程无阻碍自动流转?我认为有两种途径可达成,第一种是自建物流体系,逐个跟物流公司对接,设计符合自身的产品解决问题,这种方案的优点是灵活性非常高,高度契合公司系统,缺点也很明显,每家公司接口标准都不一样,需要懂物流的人抽象设计,接入难度大、耗时长、成本高、后期的维护成本也高。第二种是找一家第三方聚合物流平台接入即可,如:快递鸟、快递100、快递网等。物流平台对物流和电商两端的需求研究更深入,产品设计得更简单,对接更快捷,更重要的一点是只需一次接入即可使用平台支持的所有快递物流公司,开发周期短,后期维护成本低,缺点是这些平台都是标准接口,灵活性稍低,接口调用量大时需支付接口调用服务费,各家平台的收费标准都不一样,可登录其官网了解详情,下面为大家介绍如何接入快递鸟快递物流查询API。

应用场景

综合类电商平台、垂直类电商平台、小程序、ERP系统、工具等应用都可使用。

如电商平台在订单管理页面可查看物流轨迹信息

小程序通过扫码查看快递行踪

接入步骤

申请账号-->获取用户ID和Key-->申请开通套餐-->按要求对接,联调测试-->正式投产

1、申请账号和开通套餐

访问快递鸟注册页面进行账号注册,按页面提示,完成账号注册和完善资料,账号注册成功后可在用户中心查看获得用户ID和Key,同时在用户中心顶部点击申请开通套餐,这样子对接前期准备工作就完成了,使用ID和Key即可进行调用。

2、技术对接

访问快递鸟文档中心下载技术对接文档,快递鸟产品很多,选择适合自己的产品,根据文档参数对接即可。需要注意的是,对接过程如果遇到签名校验不过的问题,这时可查看生成验签的方式是否与文档一致,生成的验签是否存在大小写的问题,快递鸟的验签生成方式为:(请求内容(未编码)+ApiKey)进行MD5加密,然后Base64编码,最后进行URL(utf-8)编码。好了,不啰嗦了,直接上代码,如需对接的产品与示例不同只需更改requestType和应用级参数即可。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace KuaiDiChaXun
{
    /// <summary>
    /// 快递查询处理类
    /// </summary>
    public class KuaiDiChaXunBLL
    {
        #region 固定参数,实际项目中可写在配置文件中,方便修改
        string url = "https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx";//请求url,正式环境
        string eBusinessID = "1257157";//用户ID,此ID仅作演示使用,实际调用需换成自己的ID,登录快递鸟官网会员中心获取
        string apiKey = "F201358A-513B-4237-98D6-67DE7EA75BA3";//用户秘钥,此秘钥仅作演示使用,实际调用需换成自己的秘钥,登录快递鸟官网会员中心获取        
        #endregion

        /// <summary>
        /// 查询快递路由轨迹
        /// </summary>
        /// <param name="shipperCode">快递公司编码</param>
        /// <param name="waybillCode">运单号</param>
        /// <param name="phoneNo">收/寄件人手机号后4位,SF必填</param>
        /// <returns></returns>
        public string SearchRoute(string shipperCode, string waybillCode, string phoneNo)
        {
            if (string.IsNullOrEmpty(shipperCode))
            {
                return "请输入快递公司编码";
            }
            if (string.IsNullOrEmpty(waybillCode))
            {
                return "请输入运单号";
            }
            if (string.IsNullOrEmpty(phoneNo) && shipperCode == "SF")
            {
                return "SF必填收/寄件人手机号后4位";
            }
            string result = string.Empty;
            //组装应用级参数,此Demo使用最少参数,更多参数参考技术文档
            string requestData = "{'ShipperCode':'" + shipperCode + "','LogisticCode':'" + waybillCode + "'}";
            if (shipperCode == "SF")//顺丰需收/寄件人手机号后4位
            {
                requestData = "{'ShipperCode':'" + shipperCode + "','LogisticCode':'" + waybillCode + "','CustomerName':'" + phoneNo + "'}";
            }
            //把(jsonStr+APIKey)进行MD5加密
            string md5Str = MD5(requestData + apiKey, "UTF-8");
            //把md5Str 进行Base64编码
            string base64Str = base64(md5Str, "UTF-8");
            //进行URL编码 (utf-8),最终得到签名串
            string dataSign = HttpUtility.UrlEncode(base64Str, Encoding.UTF8);
            //请求接口指令
            string cmd = "8001";
            //组装系统级参数
            string postData = "RequestData=" + HttpUtility.UrlEncode(requestData, Encoding.UTF8) + "&EBusinessID=" + eBusinessID + "&RequestType="+ cmd + "&DataSign=" + dataSign + "&DataType=2";
            //发起请求
            HttpHelper helper = new HttpHelper();
            HttpRequestEntity response = helper.Request(url, postData);
            if (response.IsSuccess == 0)
            {
                result = response.ResponseContent;
                //拿到结果后进行后续业务操作
            }
            else
            {
                result = "请求异常";
                //记录错误信息及后续业务补偿
            }
            return result;
        }

        ///<summary>
        /// 字符串MD5加密
        ///</summary>
        ///<param name="str">要加密的字符串</param>
        ///<param name="charset">编码方式</param>
        ///<returns>密文</returns>
        public string MD5(string str, string charset)
        {
            byte[] buffer = Encoding.GetEncoding(charset).GetBytes(str);
            try
            {
                System.Security.Cryptography.MD5CryptoServiceProvider check;
                check = new System.Security.Cryptography.MD5CryptoServiceProvider();
                byte[] somme = check.ComputeHash(buffer);
                string ret = "";
                foreach (byte a in somme)
                {
                    if (a < 16)
                        ret += "0" + a.ToString("X");
                    else
                        ret += a.ToString("X");
                }
                return ret.ToLower();
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// base64编码
        /// </summary>
        /// <param name="str">内容</param>
        /// <param name="charset">编码方式</param>
        /// <returns></returns>
        public string base64(string str, string charset)
        {
            return Convert.ToBase64String(Encoding.GetEncoding(charset).GetBytes(str));
        }
    }
}

HTTP封装类代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace KuaiDiChaXun
{
    public class HttpHelper : IDisposable
    {
        private bool _disposable = false;
        /// <summary>
        /// 请求编码格式默认utf-8;
        /// </summary>
        public Encoding HtmlEncoding = Encoding.UTF8;
        /// <summary>
        /// 请求时间
        /// </summary>
        public int Timeout = 5000;

        public CookieContainer Cookies = null;
        /// <summary>
        /// 是否记录Cookies
        /// </summary>
        public bool IsRecordCookie = false;

        public string ContentType = "application/x-www-form-urlencoded";

        public string AcceptLanguage = "en-US, en; q=0.8, zh-Hans-CN; q=0.5, zh-Hans; q=0.3";

        public string KeepAlive = "Keep-Alive";

        public string Accept = "*/*";

        private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240";
        
        public HttpHelper()
        {
            //允许最大连接数
            ServicePointManager.DefaultConnectionLimit = 512;
        }        

        /// <summary>
        /// 基本请求方法
        /// </summary>
        /// <param name="requestType"></param>
        /// <param name="url"></param>
        /// <param name="requestData"></param>
        /// <returns></returns>
        private HttpRequestEntity BaseRequest(RequestType requestType, string url, string requestData)
        {
            var result = new HttpRequestEntity { IsSuccess = 0 };

            if (string.IsNullOrEmpty(url))
                throw new ArgumentNullException("请求Url不能为空值");

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();            
            HttpStatusCode statusCode = HttpStatusCode.OK;
            if (IsRecordCookie)
                Cookies = new CookieContainer();
            Stream requestStream = null;
            StreamReader streamReader = null;

            HttpWebRequest webRe = null;
            HttpWebResponse webPos = null;
            try
            {
                if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
                {
                    ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
                    webRe = WebRequest.Create(url) as HttpWebRequest;
                    webRe.ProtocolVersion = HttpVersion.Version10;
                }
                else
                {
                    webRe = (HttpWebRequest)WebRequest.Create(url);
                }

                webRe.Headers.Add("Accept-Language", AcceptLanguage);
                webRe.Headers.Add("Keep-Alive", KeepAlive);
                webRe.UserAgent = UserAgent;
                webRe.Accept = Accept;
                webRe.Timeout = Timeout;
                webRe.ReadWriteTimeout = Timeout;
                webRe.CookieContainer = Cookies;

                if (requestType == RequestType.Post)
                {
                    webRe.ContentType = string.Format("{0}; {1}", ContentType, HtmlEncoding.BodyName);
                    byte[] datas = HtmlEncoding.GetBytes(requestData);
                    webRe.Method = "POST";
                    webRe.ContentLength = datas.Length;
                    webRe.MaximumResponseHeadersLength = -1;
                    requestStream = webRe.GetRequestStream();
                    requestStream.Write(datas, 0, datas.Length);
                    requestStream.Flush();
                    requestStream.Close();
                }
                else
                    webRe.Method = "GET";

                webPos = (HttpWebResponse)webRe.GetResponse();                
                statusCode = webPos.StatusCode;
                result.ResponseLength = webPos.ContentLength;
                result.ResponseEncodingName = webPos.ContentEncoding;

                requestStream = webPos.GetResponseStream();
                if (webPos.StatusCode == HttpStatusCode.OK)
                {
                    result.IsSuccess = 0;

                    if (requestStream != null)
                    {
                        streamReader = new StreamReader(requestStream, HtmlEncoding);
                        result.ResponseContent = streamReader.ReadToEnd();
                    }
                }
            }
            catch (Exception ex)
            {
                result.IsSuccess = 1;
                result.ResponseContent = ex.Message;
            }
            finally
            {
                if (requestStream != null)
                {
                    requestStream.Close();
                    requestStream.Dispose();
                }

                if (streamReader != null)
                {
                    streamReader.Close();
                    streamReader.Dispose();
                }

                webRe.Abort();
                if (webPos != null)
                    webPos.Close();

            }
            stopwatch.Stop();
            double elapseTime = stopwatch.Elapsed.TotalMilliseconds;//计算请求耗时
            return result;
        }

        private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        {
            return true; //总是接受  
        }

        /// <summary>
        /// Get请求
        /// </summary>
        /// <param name="url">请求地址</param>
        /// <returns></returns>
        public HttpRequestEntity Request(string url)
        {
            return BaseRequest(RequestType.Get, url, string.Empty);
        }

        /// <summary>
        /// Post请求
        /// </summary>
        /// <param name="url">请求地址Url</param>
        /// <param name="requestData">请求内容参数</param>
        /// <returns></returns>
        public HttpRequestEntity Request(string url, string requestData)
        {
            return BaseRequest(RequestType.Post, url, requestData);
        }

        ~HttpHelper()
        {
            Dispose(false);
        }

        #region IDisposable 成员

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (this._disposable)
                return;

            if (disposing)
            {

            }

            _disposable = true;
        }

        #endregion
    }

    /// <summary>
    /// HttpHelper请求方式
    /// </summary>
    public enum RequestType
    {
        /// <summary>
        /// Get请求
        /// </summary>
        Get,
        /// <summary>
        /// Post请求
        /// </summary>
        Post
    }

    /// <summary>
    /// HttpHelper请求时返回实体
    /// </summary>
    public class HttpRequestEntity
    {
        /// <summary>
        /// 请求是否成功 0-成功(返回Http状态码200) 1-失败(出现异常)
        /// </summary>
        public int IsSuccess { get; set; }
        /// <summary>
        /// 请求返回内容
        /// </summary>
        public string ResponseContent { get; set; }
        /// <summary>
        /// 请求返回内容长度
        /// </summary>
        public long ResponseLength { get; set; }
        /// <summary>
        /// 请求返回编码类型
        /// </summary>
        public string ResponseEncodingName { get; set; }
    }
}

调用示例代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KuaiDiChaXun
{
    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                Console.WriteLine("请输入快递公司编码");
                string shipperCode = Console.ReadLine();

                Console.WriteLine("请输入运单号");
                string waybillCode = Console.ReadLine();

                Console.WriteLine("请输入收/寄件人手机号后4位,SF必填");
                string phone = Console.ReadLine();

                KuaiDiChaXunBLL bll = new KuaiDiChaXunBLL();
                string result = bll.SearchRoute(shipperCode, waybillCode, phone);
                Console.WriteLine("执行结果如下:");
                Console.WriteLine(result);
                Console.WriteLine("------------------");
                Console.WriteLine("------本次查询结束,按任意键继续,退出请输入exit------------");
                string cmd = Console.ReadLine().ToLower();
                if (cmd == "exit")
                {
                    return;
                }
            }
        }
    }
}

运行查看效果

以上是接入快递鸟快递查询接口示例代码,源码下载


如果对您有帮助劳烦帮忙点个赞,收藏关注一下,可以相互学习共同进步。

有关快速集成快递物流轨迹查询功能的更多相关文章

  1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  2. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

    我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

  3. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

  4. ruby-on-rails - 我如何将 Hoptoad 与 DelayedJob 和 DaemonSpawn 集成? - 2

    我一直很高兴地使用DelayedJob习惯用法:foo.send_later(:bar)这会调用DelayedJob进程中对象foo的方法bar。我一直在使用DaemonSpawn在我的服务器上启动DelayedJob进程。但是...如果foo抛出异常,Hoptoad不会捕获它。这是任何这些包中的错误...还是我需要更改某些配置...或者我是否需要在DS或DJ中插入一些异常处理来调用Hoptoad通知程序?回应下面的第一条评论。classDelayedJobWorker 最佳答案 尝试monkeypatchingDelayed::W

  5. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  6. jenkins部署1--jenkins+gitee持续集成 - 2

    前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon

  7. sql - 查询忽略时间戳日期的时间范围 - 2

    我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

  8. ruby-on-rails - solr 清理查询 - 2

    我在Rails上使用带有ruby​​的solr。一切正常,我只需要知道是否有任何现有代码来清理用户输入,比如以?开头的查询。或* 最佳答案 我不知道执行此操作的任何代码,但理论上可以通过查看parsingcodeinLucene来完成并搜索thrownewParseException(只有16个匹配!)。在实践中,我认为您最好只捕获代码中的任何solr异常并显示“无效查询”消息或类似信息。编辑:这里有几个“sanitizer”:http://pivotallabs.com/users/zach/blog/articles/937-s

  9. ruby-on-rails - rails 功能测试 - 2

    在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建

  10. ruby-on-rails - Rails 3 在一个查询中包含多个表 - 2

    我正在为锦标赛开发一个Rails应用程序。我在这个查询中使用了三个模型:classPlayertruehas_and_belongs_to_many:tournamentsclassTournament:destroyclassPlayerMatch"Player",:foreign_key=>"player_one"belongs_to:player_two,:class_name=>"Player",:foreign_key=>"player_two"在tournaments_controller的显示操作中,我调用以下查询:Tournament.where(:id=>params

随机推荐