微信公众平台——分享接口踩坑记

前言

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

开发分享接口在目录中: 微信网页开发 --> 微信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>
原文地址:https://www.cnblogs.com/V587Chinese/p/10539788.html