最近微信的公众平台接口的变动很大,让人有点晕。今天被“收货地址共享接口”的SHA1算法彻底的玩了一把。 

  1. 众里寻他。按照JS-SDK的常理这个接口应该属于网页服务,可是它偏偏不在其中。最终在哪找到了?在这里左侧菜单'微信支付'->'使用教程'->'支付开发教程'->'使用共享地址共享控件'。让人摸不清头脑的'XXX控件',乍一看还以为跟'添加功能插件'里一样呢,结果点击一个,OH MY GOD!PDF格式的《收货地址共享接口文档》。 

  2. 一个文档三处陷阱。打开文档快速浏览然后找到调用示例,

参数 必填 说明appId 是 公众号 appIDscope 是 填写“jsapi_address”,获得编辑地址权限signType 是 签名方式,目前仅支持 SHA1addrSign 是 签名,由各参数一起参与签名生成timeStamp 是 时间戳nonceStr 是 随机字符串

是不是很熟悉?对了在JS-SDK的“通过config接口注入权限验证配置”这里出现过。 

  • 陷阱一、如果你使用过js的扫一扫或者隐藏右上角按钮等功能的话,肯定是以为这里的签名所需要的accesstoken跟上面的config接口注入时的一样。那你就错了,这里的accesstoken竟然跟获取用户信息时的一样。而且官方的文档里还用红色的字体特殊标明了“这一步很容易出错…”,哎呀,好贴心^_^!!!      * 陷阱二、如果你顺利的获取了正确的accesstoken,该构造签名了吧。红色的“签名过程中所有参数名均为小写字符..”这段字应该不是我们的陷阱吧。坑人的在这里==url==参数的值不是仅仅是当前页面的url,还要加上刚才获取accesstoken时的两个参数code和state,而且必须跟获取token时的一致才可以噢。 

    * 陷阱三、如果你是一个细心的人,上面的这两个还不算事儿。好吧,再看一个参数==timeStamp==,坑在哪里?大写中间的‘S’?不,这个已经不算坑了。真正的坑在==timeStamp==的值是字符串(我就栽了),触发'editAddress'事件后总是提示eidt_ address:fail 错误,于是把文档下面的常见问题看了一遍又一遍,因为文档中这样提到==如果参数签名错误,会报错 eidt_ address:fail 或者 eidtAddress:fail_auth_error。请使用文档的样例数据,验证签名算法正确性。==于是我就用文档中的示例字符串SHA1了一下发现果然不一样。 

我的结果是“B43E348FF8E0A8D04B7B4274CE1BD6C4DB00B1A4”官方文档的结果是“ca604c740945587544a9cc25e58dd090f200e6fb”,忽略大小写也差的太多了吧。在差异微信有自己的SHA1的同时我就走上了弯路。

  1. 生活总是在你迷失放心的时候,上个厕所撒泡尿,所有的谜团就都解开了。其实给我坚定信心的是看了这篇文章 后才发现的==timeStamp==的值是字符串类型的。再次感谢该文章的作者。

至此我的收货地址接口终于出现了edit_address:ok。 

下面把其中用到的几个重要方法贴出来以便大家参考:

/// <summary>
        /// 签名请求参数 sha1加密
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static string GetSignRequest(Dictionary<string, string> parameters)
        {
            // 第一步:把字典按Key的字母顺序排序
            IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
            IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();

            // 第二步:把所有参数名和参数值串在一起
            StringBuilder query = new StringBuilder();
            while (dem.MoveNext())
            {
                string key = dem.Current.Key;
                string value = dem.Current.Value;
                if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
                {
                    query.Append(key).Append("=").Append(value).Append("&");
                }
            }
            string requstString = query.ToString();
            if (requstString.Contains("&"))
            {
              requstString=  requstString.Remove(requstString.Length - 1);
            }

            var sha1 = System.Security.Cryptography.SHA1.Create();
            var sha1Arr = sha1.ComputeHash(Encoding.UTF8.GetBytes(requstString));
            StringBuilder enText = new StringBuilder();
            foreach (var b in sha1Arr)
            {
                enText.AppendFormat("{0:x2}", b);
            }
            return enText.ToString();
        }
        
        public static string GetAddressConfig(string appID, string accesstoken, string url)
       {
           string noncestr = StringHelper.RandomString();
           long timestamp = DateTimeHelper.GetWeixinDateTime(DateTime.Now);
           Dictionary<string, string> paramlist = new Dictionary<string, string>();
           paramlist.Add("appid", appID);
           paramlist.Add("noncestr",noncestr);
           paramlist.Add("accesstoken", accesstoken);
           paramlist.Add("timestamp", timestamp.ToString());
           paramlist.Add("url", url);
           string signnature = StringHelper.GetSignRequest(paramlist);
           logger.Info("appd:" + appID + ",acc:" + accesstoken + ",url:" + url);
           var r = "{appId: '" + appID + "',scope:'jsapi_address',signType:'sha1',timeStamp:'" + timestamp.ToString() + "',nonceStr: '" + noncestr + "',addrSign: '" + signnature + "'}";

           logger.Info(r);
           return r;
       }
       
       public ActionResult address(int id, string code = "", string state = "")
        {
            var appconfig = GongZhongHaoBusiness.GetFromCache(id);
            if (code == "")
            {
                return Redirect(OAuthApi.GetAuthorizeUrl(appconfig.AppID, Core.CoreConfig.HostUrl + "Home/address/" + id, "weixin", OAuthScope.snsapi_base));
            }
            else
            {
                var result = OAuthApi.GetAccessToken(appconfig.AppID, appconfig.AppSecret, code);
                ViewBag.config = JSConfig.GetAddressConfig(appconfig.AppID, result.access_token, Request.Url.ToString());
            }

            return View();
        }