微信分享:后端参数获取PHP实现

微信分享


文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html

1、微信后台配置

设置与开发/
    公众号设置 / 功能设置 / JS接口安全域名 (配置网页所用的域名, 【需通过备案】)
    基本配置 / IP白名单 (将服务器公网ip配置到此处)

2、后端代码

PHP版 参照官网给出的示例,稍作改造

PHP版本 7.1

目录结构:

./
    JSSDK.php       微信分享必要参数获取基本接口
    JSSDKCache.php  通过继承,实现对access_token、jsapi_ticket数据的缓存
    RedisService.php   Redis缓存服务
    WeixinService.php  配置具体的微信参数,调用JSSDKCache.php
    WeixinController.php   对外提供数据接口

用到的第三方库

# 网络请求
composer require rmccue/requests

# Redis客户端
composer require  predis/predis

JSSDK.php

<?php
namespace app\utils\weixin;

use Requests;


/**
 * 微信分享必要参数获取基本接口
 *
 * Class JSSDK
 * @package app\utils\weixin
 */
class JSSDK
{
    // 开发者ID
    protected $appId;

    // 开发者密码(AppSecret)
    protected $appSecret;

    public function __construct($appId, $appSecret)
    {
        $this->appId     = $appId;
        $this->appSecret = $appSecret;
    }

    public function getSignPackage($jsapi_ticket, $url)
    {
        $timestamp = time();
        $nonceStr  = $this->createNonceStr();

        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket=$jsapi_ticket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";

        $signature = sha1($string);

        $signPackage = array(
            "appId"     => $this->appId,
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => $signature,
            "rawString" => $string
        );

        return $signPackage;
    }

    private function createNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str   = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 获取 Access token
     * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     */
    public function getAccessToken()
    {
        $params = [
            'grant_type' => 'client_credential',
            'appid'      => $this->appId,
            'secret'     => $this->appSecret
        ];

        $url = "https://api.weixin.qq.com/cgi-bin/token?" . http_build_query($params);

        $data = self::httpGet($url);
        
        if ($data) {
            return empty($data['access_token']) ? null : $data['access_token'];
        } else {
            return null;
        }
    }

    /**
     * 获取 jsapi_ticket
     * https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62
     */
    public function getJsapiTicket($access_token)
    {
        $params = [
            'access_token' => $access_token,
            'type'         => 'jsapi',
        ];

        $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?" . http_build_query($params);

        $data = self::httpGet($url);

        if ($data) {
            return empty($data['ticket']) ? null : $data['ticket'];
        } else {
            return null;
        }
    }

    private function httpGet($url)
    {
        $response = Requests::get($url);

        if ($response->success) {
            return json_decode($response->body, true);
        } else {
            return null;
        }
    }
}

JSSDKCache.php

<?php

namespace app\utils\weixin;


use app\service\RedisService;


/**
 * 自定义实现缓存: access_token、jsapi_ticket
 *
 * Class JSSDKCache
 * @package app\utils\weixin
 */
class JSSDKCache extends JSSDK
{
    public function getSignData($url)
    {
        $jsapi_ticket = $this->getJsapiTicketFromCache();

        $data = $this->getSignPackage($jsapi_ticket, $url);

        return $data;
    }


    public function getAccessTokenFromCache()
    {
        $cache_key = 'access_token.' . $this->appId;

        $access_token = RedisService::get($cache_key);

        if (!$access_token) {
            // 获取access_token
            $access_token = $this->getAccessToken();

            // 7200
            RedisService::set($cache_key, $access_token, 7100);
        }

        return $access_token;

    }

    public function getJsapiTicketFromCache()
    {
        $cache_key = 'jsapi_ticket.' . $this->appId;

        $jsapi_ticket = RedisService::get($cache_key);

        if (!$jsapi_ticket) {

            // 获取access_token
            $access_token = $this->getAccessTokenFromCache();

            $jsapi_ticket = parent::getJsapiTicket($access_token);

            // 7200
            RedisService::set($cache_key, $jsapi_ticket, 7100);

        }

        return $jsapi_ticket;
    }
}

<?php


namespace app\service;

use Predis\Client;

/**
 * 使用Redis 缓存
 *
 * Class RedisService
 * @package app\service
 */
class RedisService
{
    // 可能有多个网站,每个网站给一个前缀
    private static $prefix = 'www';

    public static function getClient()
    {
        $client = new Client();
        return $client;
    }

    public static function getKey($key)
    {
        return self::$prefix . '.' . $key;
    }


    public static function get($key)
    {
        $client = self::getClient();
        $key    = self::getKey($key);
        return $client->get($key);
    }

    public static function set($key, $value, $expire = null)
    {
        //https://learnku.com/laravel/t/3563/using-predis-to-operate-the-redis-database-how-to-set-the-key-life-cycle-expires
        $client = self::getClient();
        $key    = self::getKey($key);

        if ($expire) {
            // SET key value [EX seconds] [PX milliseconds] [NX|XX]
            $client->set($key, $value, 'EX', $expire);
        } else {
            $client->set($key, $value);
        }
    }

}

WeixinService.php

<?php

namespace app\service;

use app\utils\weixin\JSSDKCache;


class WeixinService
{
    // 公众号 开发者ID
    private static $APP_ID = '';

    // 开发者密码(AppSecret)
    private static $APP_SECRET = '';


    public static function getWeixinConfig($url)
    {
        $jssdk = new JSSDKCache(self::$APP_ID, self::$APP_SECRET);

        return $jssdk->getSignData($url);
    }
}

WeixinController.php

<?php

namespace app\api\controller;

use app\BaseController;

use app\service\WeixinService;

class WeixinController extends BaseController
{

    public function getWeixinConfig()
    {
        $url = input('url');
        return WeixinService::getWeixinConfig($url);
    }
}

3、前端代码weixin-share.html

方式一:后端渲染

<!--微信分享-->
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>


<script>
    wx.config({
        debug: false, // 开启调试模式
        appId: '{$appId}', // 必填,公众号的唯一标识
        timestamp: '{$timestamp}', // 必填,生成签名的时间戳
        nonceStr: '{$nonceStr}', // 必填,生成签名的随机串
        signature: '{$signature}',// 必填,签名
        // 必填,需要使用的JS接口列表
        jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'], 
        // 可选,获取开放标签权限
        openTagList: ['wx-open-launch-app'] 
    });

    // 自定义“分享给朋友”及“分享到QQ”按钮的分享内容
    wx.ready(function () {   //需在用户可能点击分享按钮前就先调用
        wx.updateAppMessageShareData({
            title: '{$title}', // 分享标题
            desc: '{$summary}', // 分享描述
            link: '{$url}', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
            imgUrl: "{$cover_icon}", // 分享图标
            success: function () {
                // 设置成功
            }
        });

        // 分享到朋友圈
        wx.updateTimelineShareData({
            title: '{$title}', // 分享标题
            link: '{$url}', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
            imgUrl: "{$cover_icon}", // 分享图标
            success: function () {
                // 设置成功
            }
        })
    });

    wx.error(function(res){
        // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
        console.log("config验证失败")
    });
</script>

方式二:前端通过接口获取

<!-- Axios -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<!--微信分享-->
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>


<script>
    url = window.location.href.split('#')[0];
    axios.post('/api/getWeixinConfig', {url: url}).then(res=>{
        let data = res.data.data;

        wx.config({
            debug: false, // 开启调试模式
            appId: data.appId, // 必填,公众号的唯一标识
            timestamp: data.timestamp, // 必填,生成签名的时间戳
            nonceStr: data.nonceStr, // 必填,生成签名的随机串
            signature: data.signature,// 必填,签名
            // 必填,需要使用的JS接口列表
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'],
            // 可选,获取开放标签权限
            // openTagList: ['wx-open-launch-app']
        });

        // 自定义“分享给朋友”及“分享到QQ”按钮的分享内容
        wx.ready(function () {   //需在用户可能点击分享按钮前就先调用
            wx.updateAppMessageShareData({
                title: '{{ detail.title }}', // 分享标题
                desc: '{{ detail.summary }}', // 分享描述
                link: data.url, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl: '{{ detail.head_image }}', // 分享图标
                success: function () {
                    // 设置成功
                }
            });

            // 分享到朋友圈
            wx.updateTimelineShareData({
                title: '{{ detail.title }}', // 分享标题
                link: data.url, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl: '{{ detail.head_image }}', // 分享图标
                success: function () {
                    // 设置成功
                }
            })
        });

        wx.error(function(res){
            // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
            console.log("config验证失败")
        });
    })


</script>



上一篇:Objective-C学习篇05—Foundation框架简介


下一篇:SignalR2.0开发实例之——设置时间、后台其他地方使用集线器、使用自己的连接ID