/ Javascript / 微信

在你的博客中集成微信分享

如今差不多是全民微信的时代了,朋友圈已经是非常重要的信息获取途径,也是很多人分享信息的重要途径,那不如我们就来聊聊集成微信分享的那点事吧~

为什么要集成微信分享?

其实我们已经很容易通过手机和微信来分享一个网页,只要手机中安装了微信,现在大多的手机浏览器都有分享功能,在浏览网页时都可以选择分享到微信、QQ、推特、微博等等这些常用的社交平台。只是在不做处理的情况下,通常分享出来的样子并不那么令人满意,我们更希望有更吸引人的图片和标题、描述神马的。其实不用想,鹅场当然早就提供了解决这类需求的方案的,那就是JS-SDK。所以本文其实就是介绍如何在最小化改动博客代码的情况下,将这个SDK集成进来。

其实思路很简单,就是将JS-SDK中关于分享的接口封装到一个JS函数或者对象中,然后再通过JS获取网页中的相关信息(标题、描述……),并将这些信息作为参数传递给这个函数,完成对相关分享接口的引用。

那就开始吧

首先,我们来封装JS-SDK,创建一个js文件wxshare.js,为了让这个JS看起来更通用一些,我用了UMD

(function (root, factory) {
        if (typeof define === 'function' && define.amd) {
            define([], factory);
        } else if (typeof module === 'object' && module.exports) {
            module.exports = factory();
        } else {
            root.weixin_share = factory(); // 对象的名称是"weixin_share"
        }
    }
)(this, function () {
    'use strict';

    var isWeixin = navigator.userAgent.toLowerCase().indexOf("micromessenger") >= 0;

    /**
     * 此方法用于加载JS-SDK
     */
    function load_wx_jssdk(sdk) {
        var s = document.createElement('script');

        return new Promise(function (resolve, reject) {
            s.onload = function () {
                resolve(s);
            };
            s.onerror = function () {
                reject('failed to load ' + sdk);
            };
            s.async = 1;
            s.src = sdk;
            document.getElementsByTagName("head")[0].appendChild(s);
        });
    }

    /**
     * 这个方法用于加载JS-SDK的权限验证配置
     * 具体说明,请参考https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
     * 其中的 1.1.3 步骤三:通过config接口注入权限验证配置
     */
    function load_config(config) {
        return new Promise(function (resolve, reject) {
            if (!isWeixin) {
                reject('not weixin');
            }

            if (typeof config === 'object') { // config 可以是json对象,比如{appId:'xxx', signature:'xxx' ...}
                resolve(config);
            } else if (typeof config === 'string') { // 或者是通过网址异步获取,这是本文推荐的方式!
                $.ajax({
                    url: config,
                    data: {
                        l: window.location.href
                    },
                    type: "GET",
                    dataType: "json",
                    success: function (data) {
                        resolve(data);
                    },
                    error: function (r, s) {
                        reject(s);
                    }
                });
            } else {
                reject('unknown config');
            }
        });
    }

    /**
     * 这个方法就是调用JS-SDK的API方法初始化分享属性
     */
    function init_share(options) {
        return new Promise(function (resolve, reject) {
            if (!isWeixin) {
                reject('not weixin');
            }

            load_wx_jssdk('//res.wx.qq.com/open/js/jweixin-1.2.0.js').then(function () {
                wx.config({
                    debug: false,
                    appId: options.appId, //需要后端传入
                    signature: options.signature, //需要后端传入
                    timestamp: options.timestamp, //需要后端传入
                    nonceStr: options.nonceStr, //需要后端传入
                    jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage']
                });

                wx.error(function (res) {
                    reject(res);
                });
                wx.ready(function () {
                    // 对话消息
                    wx.onMenuShareAppMessage({
                        title: options.title, // 分享标题
                        desc: options.desc, // 分享描述
                        link: options.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                        imgUrl: options.imgUrl, // 分享图标
                        //type: '', // 分享类型,music、video或link,不填默认为link
                        //dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
                        success: function () {
                            // 用户确认分享后执行的回调函数
                        },
                        cancel: function () {
                            // 用户取消分享后执行的回调函数
                        }
                    });
                    // 朋友圈
                    wx.onMenuShareTimeline({
                        title: options.title, // 分享标题
                        link: options.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                        imgUrl: options.imgUrl, // 分享图标
                        success: function () {
                            // 用户确认分享后执行的回调函数
                        },
                        cancel: function () {
                            // 用户取消分享后执行的回调函数
                        }
                    });
                });
                resolve(wx);
            }).catch(function (m) {
                reject(m);
            });

        });
    }

    /**
     * 我们的weixin_share对象将有两个方法:config和init,分别对应load_config和init_share
     */
    return {
        config: load_config,
        init: init_share
    };
});

接下来,我们要处理上面代码中提到的JS-SDK的权限验证配置。我的建议是用后端程序写一个接口并单独部署,比如下面这个使用Slim框架的PHP程序:

<?php
// Routes
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

$app->get('/path/to/wxconfig', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
    $settings = $this->get('settings');
    $app_id = '你的微信公众号开发者ID:AppID';
    $app_secret = '你的微信公众号开发者密码:AppSecret';
    $timestamp = time();
    $url = $request->getQueryParam('l');
    $noncestr = md5(time());
    try {
        if (($access_token = get_wxmp_access_token($settings['datafile']['access_token'], $app_id, $app_secret))
                && ($ticket = get_wxmp_jsapi_ticket($settings['datafile']['jsapi_ticket'], $access_token))) {
            return $response->withJson(array(
                'appId'=>$app_id,
                'timestamp'=>$timestamp,
                'nonceStr'=>$noncestr,
                'signature'=>sha1(sprintf("jsapi_ticket=%s&noncestr=%s&timestamp=%d&url=%s", $ticket, $noncestr, $timestamp, $url))
            ));
        }
    } catch (\Exception $e) {
        if ($settings['displayErrorDetails']) {
            return $response->withJson(array('error'=>$e->getCode(), 'message'=>$e->getMessage(), 'trace'=>$e->getTraceAsString()));
        }
    }
    return $response->withJson(array());
});

/**
 * 获取access_token
 * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183
 */
function get_wxmp_access_token($datafile, $app_id, $app_secret) {
    if ($fp = fopen($datafile, 'w+')) {
        if ($filesize = filesize($datafile) && $data = fread($fp, $filesize) && $token_data = explode($data, '#')) {
            if ($token_data[1] > time()) {
                fclose($fp);
                return $token_data[0];
            }
        }
        if (($result = curl_json("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$app_id}&secret={$app_secret}"))
                && isset($result['access_token'], $result['expires_in'])) {
            $access_token = $result['access_token'];
            $expires_in = $result['expires_in'];
            fwrite($fp, implode('#', array($access_token, time() + $expires_in - min(300, $expires_in))));
            fclose($fp);
            return $access_token;
        }
        fclose($fp);
    }
    return null;
}

/**
 * 获取jsapi_ticket
 * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115#f11
 */
function get_wxmp_jsapi_ticket($datafile, $access_token) {
    if ($fp = fopen($datafile, 'w+')) {
        if ($filesize = filesize($datafile) && $data = fread($fp, $filesize) && $ticket_data = explode($data, '#')) {
            if ($ticket_data[1] > time()) {
                fclose($fp);
                return $ticket_data[0];
            }
        }
        if (($result = curl_json("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$access_token}&type=jsapi"))
                && isset($result['ticket'], $result['expires_in'])) {
            $ticket = $result['ticket'];
            $expires_in = $result['expires_in'];
            fwrite($fp, implode('#', array($ticket, time() + $expires_in - min(300, $expires_in))));
            fclose($fp);
            return $ticket;
        }
        fclose($fp);
    }
    return null;
}

function curl_json($url){
    $ci = curl_init();
        
    curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($ci, CURLOPT_TIMEOUT, 30);
    curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ci, CURLOPT_HEADER, FALSE);
    curl_setopt($ci, CURLOPT_POST, FALSE);
        
    curl_setopt($ci, CURLOPT_URL, $url);
    curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE);

    $response = curl_exec($ci);

    curl_close($ci);

    return json_decode($response, true) ?: $response;
}

然后,我们要在页面中调用上面定义的weixin_share,其中path/to/wxconfig就是上述权限验证配置的部署路径:

(function (root) {
    var s = document.createElement('script');
    s.onload = function () {
        root.weixin_share.config("/path/to/wxconfig").then(function (c) {
            root.weixin_share.init($.extend({
                title: $('meta[property="og:title"]').attr('content'),
                desc: $('meta[property="og:description"]').attr('content'),
                link: $('meta[property="og:url"]').attr('content'),
                imgUrl: $('meta[property="og:image"]').attr('content')
            }, c));
        });
    };
    s.async = 1;
    s.src = '/path/to/wxshare.js';
    document.getElementsByTagName("head")[0].appendChild(s);
})(this);

大家可能注意到了,我是从页面的meta元素中提取相关的标题、描述以及图片等信息,这样做的目的主要是尽可能小的改动博客代码。当前主流的博客系统貌似都使用了这种叫Open Graph protocol的东西。当然,如果不嫌麻烦,也可将上述提取meta的代码进行改造一番(比如,使用后端程序直接将相关信息输出到页面代码中)。

好了,一切就绪!下面,你要确认一下,你的微信开发者接口权限了,在没有通过微信认证的情况下,上述分享功能的接口的权限都是“未获得”!而不论你是个人还是企业,这认证都是要每年花¥300元的,呵呵~

Devy

独立开发者' 全栈' 80前' 在理想与现实之间寻找平衡'

Read More