zoukankan      html  css  js  c++  java
  • 微信公众平台——分享接口踩坑记

    前言

    本以为有微信公众平台开发文档,自定义分享样式还不简单?带着这样的想法,经历了两天半的踩坑经历,我写下了这篇文章。

    开发分享接口在目录中: 微信网页开发 --> 微信SDK说明文档中

    流程摘要

    一、按照文档,第一步要绑定安全域名,即“JS接口安全域名”(此处标记1号坑)

    二、第二步引入JS文件,需要下载并将其上传到服务器。这里要注意,在第一步中绑定的域名(路径)要能够访问到这个文件

    三、第三步通过config接口注入权限验证配置。这里面的参数全部都要从后端获取,我的方案是服务端渲染。如果验证不通过最有可能的原因是签名算法错误。此步骤后端有很多的工作要做:先获取access_token,再根据它获取jsapi_ticket,然后再写签名算法进行计算得出签名,才将这些数据渲染到前端页面上。(此处标记踩坑点)

    四、第四步ready和error接口,并且将分享接口写在ready内。

    正文

    一、关于JS接口安全域名

      最理想的是绑定网站地址,例如www.baidu.com。如果暂时不知道如何办成,可以先绑一个路径,如www.baidu.com/mp。

      为什么这里是1号坑?因为你如果绑定的是路径,你要分享的链接一定要在绑定的这个路径下,即使是同一个域名,不在这个路径下,对不起,签名非法。

      所以如果你想让该域名下所有的链接都可以用上自定义分享样式,就要将其绑定网站地址。

    二、关于config接口。主要的工作在后端:

      1.获取access_token。(二号大坑,内含若干小坑)

        微信公众平台有两种access_token,一种可以用来第三方登录,另一种就是JS-SDK使用。获取它需要你的开发者ID和密码。

        此处要注意,(一)服务号和企业号是不一样的!

        (1)ID和密码不一样。服务号是appID和appSecret,而企业号叫做corpid和corpsecret

        (2)获取token的链接不一样,前者是api开头,后者是qyapi开头。我做的是服务号(虽然认证主体也是企业),地址是https://api.weixin.qq.com/cgi-bin/token

        (二)两种access_token要区别清楚

        (1)请求地址和参数不一样,前者地址是https://api.weixin.qq.com/sns/oauth2/access_token  后者是https://api.weixin.qq.com/cgi-bin/token。此处我们用的是后者

        (2)请求次数不一样。前者可以无限次获取,后者获取次数非常有限(具体次数不详),官方建议自己设缓存,且有效期7200秒。

        (三)如果access_token获取不对,下一步会报错,官方有提供测试获取token

      2.获取jsapi_ticket(三号大坑)

        确保token获取正确后,我们来获取ticket。同样地,

        (1)要区别服务号和企业号,因为请求地址都不一样,此处是https://api.weixin.qq.com/cgi-bin/ticket/getticket(不含参数)

        (2)获取次数有限,有效期同7200S,需设缓存。

      3.签名算法

        这里不多说,官方有提供签名算法的验证检测。

    三、一切都完成以后

    调试没有报错,测试分享成功,本以为大功告成,可又出了一些问题?

    1. IOS端分享正常,Android端却还是自定义之前的老样子
    2. 无论是IOS端还是Android端,点击第一次分享出来的链接,再次分享,又回到了老样子

    解决:

    1. 问题出在微信平台。分享接口有很多个,我只用了updateAppMessageShareData和updateTimelineShareData,后面即将废弃的几个接口既没有声明也没有使用。这看上去没问题,可这就是问题所在(测试人员的微信都是7.0版本,JS-SDK版本是1.4.0)。解决办法是将所有分享接口都在config中声明。
    2. 二次分享问题。原因在第一次分享以后腾讯在原url上加了一些参数,导致新的url与原来的不符,与计算签名时的url不同,签名无效。解决办法是前端第一行js代码判断当前url是否是原url,如果不是则跳转到原url。

    四、代码贡上

    Python Django后端:

    def get_accesstoken():
        "获取access_token"
        appid = '************'
        appsecret = '***********'
    
        def http_get_token(conn, now_time):
            "向服务器获取token"
            try:
                url = u'https://api.weixin.qq.com/cgi-bin/token'
                params = {
                    'appid': appid,
                    'secret': appsecret,
                    'grant_type': 'client_credential'
                }
                res = requests.get(url, params=params).json()
                access_token = res['access_token']
                # 存redis缓存
                conn.hmset("access_token", {"token": access_token, "timestamp": now_time})
                return access_token
            except Exception as e:
                traceback.print_exc()
                return HttpResponse(json.dumps('access_token获取失败:%s' % e))
    
        # 首先取缓存
        conn = redis.StrictRedis(host='127.0.0.1', port='6379')
        redis_token = conn.hgetall("access_token")
        now_time = time.time()
        if redis_token:  # 如果有token缓存
            redis_time = float(str(redis_token[b"timestamp"], encoding="utf-8"))
            if now_time - redis_time < 6000:  # 如果token没过期
                access_token = str(redis_token[b"token"], encoding="utf-8")
            else:  # 如果已过期,重新获取
                access_token = http_get_token(conn, now_time)
        else:  # 如果没有缓存,创建一个
            access_token = http_get_token(conn, now_time)
    
        return access_token
    
    def get_jsapiticket(access_token):
        "获取 jsapi_ticket"
    
        def http_get_jsapi(conn, now_time):
            try:
                url2 = u'https://api.weixin.qq.com/cgi-bin/ticket/getticket'
                params = {
                    'access_token': access_token,
                    'type': 'jsapi'
                }
                res2 = requests.get(url2, params=params).json()
                if res2["errmsg"] == "ok":
                    jsapi_ticket = res2["ticket"]
                    conn.hmset("jsapi", {"ticket": jsapi_ticket, "timestamp": now_time})
                    return jsapi_ticket
            except Exception as e:
                traceback.print_exc()
                return HttpResponse(json.dumps('jsapi获取失败:%s' % e))
    
        # 首先取缓存
        conn = redis.StrictRedis(host='127.0.0.1', port='6379')
        redis_jsapi = conn.hgetall("jsapi")
        now_time = time.time()
        if redis_jsapi:  # 如果有token缓存
            redis_time = float(str(redis_jsapi[b"timestamp"], encoding="utf-8"))
            if now_time - redis_time < 6000:  # 如果token没过期
                jsapi_ticket = str(redis_jsapi[b"ticket"], encoding="utf-8")
            else:  # 如果已过期,重新获取
                jsapi_ticket = http_get_jsapi(conn, now_time)
        else:  # 如果没有缓存,创建一个
            jsapi_ticket = http_get_jsapi(conn, now_time)
    
        return jsapi_ticket
    
    def share(url):
        "自定义分享"
        appid = '***********'
    
        # 获取access_token
        access_token = get_accesstoken()
        # 获取jsapi_ticket
        jsapi_ticket = get_jsapiticket(access_token)
    
        if jsapi_ticket is not None:
            # 签名算法
            class Sign:
                def __init__(self, jsapi_ticket, url):
                    self.ret = {
                        'nonceStr': self.__create_nonce_str(),
                        'jsapi_ticket': jsapi_ticket,
                        'timestamp': self.__create_timestamp(),
                        'url': url
                    }
    
                def __create_nonce_str(self):
                    return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(15))
    
                def __create_timestamp(self):
                    return int(time.time())
    
                def sign(self):
                    string = '&'.join(['%s=%s' % (key.lower(), self.ret[key]) for key in sorted(self.ret)]).encode(
                        encoding="utf-8")
                    self.ret['signature'] = hashlib.sha1(string).hexdigest()
                    return self.ret
    
            sign = Sign(jsapi_ticket, url)
            we_share = sign.sign()
            we_share.setdefault("appid", appid)
    
            return we_share
        else:
            print("jsapi为空,share没有返回值")

    前端:

    <script>
        // 自定义数据
        var title = "{{ news.0.N_Title }}";
        var link = window.location.href.split("?")[0];
        var imgUrl = "http://www.smcic.cn/static/static_img/share_icon.png";
        var desc = $(".post-content").text().replace(/s+/g, "").replace(/[
    ]/g,"").substr(0,50);
        if(link  !== window.location.href) // 二次分享处理
        {
            window.location.href = link;
        }
        
        wx.config({
            debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
            appId: "{{ we_share.appid }}", // 必填,公众号的唯一标识
            timestamp: parseInt("{{ we_share.timestamp }}"), // 必填,生成签名的时间戳
            nonceStr: "{{ we_share.nonceStr }}", // 必填,生成签名的随机串
            signature: "{{ we_share.signature }}", // 必填,签名
            jsApiList: [ //使用的JS接口列表,如果需要其他的功能,再添加对应api
                'updateAppMessageShareData',
                'updateTimelineShareData',
                'onMenuShareTimeline',
                'onMenuShareAppMessage',
                'onMenuShareQQ',
                'onMenuShareQZone',
                'onMenuShareWeibo',
            ]
        });
        
        wx.ready(function () {
            wx.updateAppMessageShareData({
                title: title,   // 分享标题
                link: link,     // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                desc: desc,
                imgUrl: imgUrl, // 分享图标
                success: function () {
                    // 用户确认分享后执行的回调函数
                },
                cancel: function () {
                    // 用户取消分享后执行的回调函数
                }
            });
            wx.updateTimelineShareData({
                title: title, // 分享标题
                desc: desc, // 分享描述
                link: link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl: imgUrl, // 分享图标
                type: '', // 分享类型,music、video或link,不填默认为link
                dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
                success: function () {
                    // 用户确认分享后执行的回调函数
                },
                cancel: function () {
                    // 用户取消分享后执行的回调函数
                }
            });
        });
    
        wx.error(function (res) {
            console.log("失败",res);
        });
    </script>
  • 相关阅读:
    非局部均值(NL-means)
    图像对比度的理解
    汇编语言之计算器设计
    基于DnCNN模型的图像去噪论文详解(Beyond a Gaussian Denoiser: Residual Learning of Deep CNN for Image Denoising)
    P2024 [NOI2001]食物链(洛谷)
    P2256 一中校运会之百米跑(洛谷)
    P1037 产生数(洛谷)
    Java 换行和回车
    html 一些常用字符
    update
  • 原文地址:https://www.cnblogs.com/V587Chinese/p/10539788.html
Copyright © 2011-2022 走看看