zoukankan      html  css  js  c++  java
  • 手把手带你使用JS-SDK自定义微信分享效果

    前言

    刚进入一家新公司,接到的第一个任务就是需要需要自定义微信分享的效果(自定义缩略图,标题,摘要),一开始真是一脸懵逼,在网上搜索了半天之后大概有了方案。值得注意的是一开始搜索到的解决方案全是调用微信的自带的JS-SDK,然而腾讯是不会让广大吃瓜群众这么轻而易举的调用他们的东西的。微信开发团队已经把调用的权限收回,现在无法直接在页面直接调用JS-SDK了。话不多说,直接上干货。

    预期效果

    原始的分享效果:

    使用微信JS-SDK的分享效果:

    可以看出缩略图,标题,摘要样式良好,给用户的体验很好。

    准备工作

    微信官方开发者文档地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115

    现在的思路已经很明确了,就是通过调用微信的JS-SDK实现自定义分享效果。但是这个调用过程比较繁琐,需要提前准备如下东西:

    (1)微信服务号一个,并且已经通过了实名认证;

       没有实名认证的话,一些接口没有调用权限。

    (2)一个ICP备案的域名;

    这个域名需要设置为微信公众号后台的JS接口安全域名,否则微信仍然不允许调用它的接口。

    这时大家应该就犯难了,这样的话岂不是不能在本地测试,只能部署到生产环境才能测试?不用着急,解决方案告诉大家:花生壳的内网穿透服务(收费,20元以内)

    花生壳官网:http://hsk.oray.com/price/#personal

    选择个人免费版就可以了,虽然说是免费版,但是其实注册过程中还是要收几块钱的,因为我自己买了域名和流量所以花的钱更多一些,但也在20元以内。不建议大家购买流量,送的流量可以用很久了。

    当准备好上面提到的就可以开始敲代码了。

    (3)安装微信开发者工具,用于本地调试。

    下载地址:https://mp.weixin.qq.com/debug/cgi-bin/webdebugger/download?from=mpwiki&os=x64

    官方使用教程:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140

    具体步骤

    (1)查看AppId,AppSecret以及绑定域名

    进入微信后台,找到下面的菜单

    获取AppID和AppSecret

    设置JS接口安全域名

     

     

    注意第三步,如果微信服务器不能在我们的服务器上访问到这个txt文件,域名是无法设置成功的,这里先告诉大家在哪里设置,想要成功设置域名还需要使用花生壳的服务,让微信服务器访问我们本地工程中的的txt文件才行。

    hkh3321313.vicp.io是在花生壳上购买的域名,免费送的域名是在太难记了,完全不能忍。

    (2)引入JS文件

    这里需要注意是http还是https,如果生产环境是https,务必前缀是https,都则会出现mix content这样的错误,导致引入失败。

    <script typet="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>

    (3)通过AppId和AppSecret请求accessToken,然后通过accessToken获取jsapi_ticket,生成config接口所需参数

    因为获取这两个参数的次数是有限制的(accessToke 每日2000次,jsapi_ticket 每日100000次),有效期是7200秒,每两小时请求一次就行啦,把获取的accessToke和jsapi_ticket保存在后台,所以accessToken和jsapi_ticket这两个参数的获取是通过ajax方式请求后台,而不是实时去获取的。

    config几个参数需要详细说明一下:

    1. timestamp  生成签名的时间戳  create_nonce_str()
    2. nonceStr  随机生成的字符串 create_timestamp()
    3. signature  按照微信文档签名算法生成的签名 makeWXTicket()

    附上signature算法的官方说明:

    https://mp.weixin.qq.com/wiki?action=doc&id=mp1421141115&t=0.15697429783636763#buzhou3

    在附录1中可以找到详细说明。

    此外,官方提供了一个签名算法的校验工具:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign

    下面只附上了主要的方法:

    复制代码
    //获取accessToken
    private JSONObject getAccessToken(){
        //String accessTokenUrl= https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
        String requestUrl = accessTokenUrl.replace("APPID",appId).replace("APPSECRET",appSecret);
        log.info("getAccessToken.requestUrl====>"+requestUrl);
        JSONObject result = HttpUtil.doGet(requestUrl);
        return result ;
    }
    
    //获取ticket
    private JSONObject getJsApiTicket(){
        //String apiTicketUrl= https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
        String requestUrl = apiTicketUrl.replace("ACCESS_TOKEN", accessToken);
        log.info("getJsApiTicket.requestUrl====>"+requestUrl);
        JSONObject result = HttpUtil.doGet(requestUrl);
        return result;
    }
    
    //生成微信权限验证的参数
    public Map<String, String> makeWXTicket(String jsApiTicket, String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonceStr = createNonceStr();
        String timestamp = createTimestamp();
        String string1;
        String signature = "";
    
        //注意这里参数名必须全部小写,且必须有序
        string1 = "jsapi_ticket=" + jsApiTicket +
                "&noncestr=" + nonceStr +
                "&timestamp=" + timestamp +
                "&url=" + url;
        log.info("String1=====>"+string1);
        try
        {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
            log.info("signature=====>"+signature);
        }
        catch (NoSuchAlgorithmException e)
        {
            log.error("WeChatController.makeWXTicket=====Start");
            log.error(e.getMessage(),e);
            log.error("WeChatController.makeWXTicket=====End");
        }
        catch (UnsupportedEncodingException e)
        {
            log.error("WeChatController.makeWXTicket=====Start");
            log.error(e.getMessage(),e);
            log.error("WeChatController.makeWXTicket=====End");
        }
    
        ret.put("url", url);
        ret.put("jsapi_ticket", jsApiTicket);
        ret.put("nonceStr", nonceStr);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);
        ret.put("appid", appId);
    
        return ret;
    }
    //字节数组转换为十六进制字符串
    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash)
        {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }
    //生成随机字符串
    private static String createNonceStr() {
        return UUID.randomUUID().toString();
    }
    //生成时间戳
    private static String createTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    复制代码

    HttpUtil代码

    复制代码
    public class HttpUtil {
        public static Log logger = LogFactory.getLog(HttpUtil.class);
        //get请求
        public static com.alibaba.fastjson.JSONObject doGet(String requestUrl) {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = null;
            String responseContent  = null;
            com.alibaba.fastjson.JSONObject result = null;
            try {
                //创建Get请求,
                HttpGet httpGet = new HttpGet(requestUrl);
                //执行Get请求,
                response = httpClient.execute(httpGet);
                //得到响应体
                HttpEntity entity = response.getEntity();
                //获取响应内容
                responseContent  = EntityUtils.toString(entity,"UTF-8");
                //转换为map
                result = JSON.parseObject(responseContent);
            } catch (IOException e) {
                logger.error("HttpUtil=====Start");
                logger.error(e.getMessage(),e);
                logger.error("HttpUtil=====End");
            }
            return result;
        }
    }
    复制代码

    (4)通过config接口注入权限验证配置

    官方示例:

    复制代码
    wx.config({
         debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
         appId: '', // 必填,公众号的唯一标识     
       timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串
    signature: '',// 必填,签名,见附录1 jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 });
    复制代码

    自己的代码:

    其中的url不能硬编码写在后台,必须通过动态传递。

    复制代码
    $(function(){
        var url = location.href.split('#').toString();//url不能写死
        $.ajax({
            type : "get",
            url : "/wechatParam",
            dataType : "json",
            async : false,
            data:{url:url},
            success : function(data) {
                wx.config({
                    debug: false,////生产环境需要关闭debug模式
                    appId: data.appid,//appId通过微信服务号后台查看
                    timestamp: data.timestamp,//生成签名的时间戳
                    nonceStr: data.nonceStr,//生成签名的随机字符串
                    signature: data.signature,//签名
                    jsApiList: [//需要调用的JS接口列表
                        'checkJsApi',//判断当前客户端版本是否支持指定JS接口
                        'onMenuShareTimeline',//分享给好友
                        'onMenuShareAppMessage'//分享到朋友圈
                    ]
                });
            },
            error: function(xhr, status, error) {
                //alert(status);
                //alert(xhr.responseText);
            }
        })
    });
    复制代码

     

    (5)通过ready接口处理成功验证

    官方示例:

    复制代码
    wx.ready(function(){
        // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
    
    });
    复制代码

    自己的代码:

    复制代码
    wx.ready(function () {
            var link = window.location.href;
            var protocol = window.location.protocol;
            var host = window.location.host;
            //分享朋友圈
            wx.onMenuShareTimeline({
                title: '这是一个自定义的标题!',
                link: link,
                imgUrl: protocol+'//'+host+'/resources/images/icon.jpg',// 自定义图标
                trigger: function (res) {
                    // 不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回.
                    //alert('click shared');
                },
                success: function (res) {
                    //alert('shared success');
                    //some thing you should do
                },
                cancel: function (res) {
                    //alert('shared cancle');
                },
                fail: function (res) {
                    //alert(JSON.stringify(res));
                }
            });
            //分享给好友
            wx.onMenuShareAppMessage({
                title: '这是一个自定义的标题!', // 分享标题
                desc: '这是一个自定义的描述!', // 分享描述
                link: link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl: protocol+'//'+host+'/resources/images/icon.jpg', // 自定义图标
                type: 'link', // 分享类型,music、video或link,不填默认为link
                dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
                success: function () {
                    // 用户确认分享后执行的回调函数
                },
                cancel: function () {
                    // 用户取消分享后执行的回调函数
                }
            });
            wx.error(function (res) {
                alert(res.errMsg);
            });
        });
    复制代码

    到这里所有的代码都已经分享完毕了。

     

    (6)启动花生壳的内网穿透服务,设置JS接口安全域名

    这个基本是傻瓜式的,只要下载他们的客户端就可以了。

    官网教程:http://hsk.oray.com/news/4345.html

    添加一个映射就可以了

    把之前下载的txt文件放在工程目录webapp下,然后本地启动工程,确定通过域名可以访问本地项目后,设置JS安全域名

    现在访问 域名:端口号(例如:hkh3321313.vicp.io:8080)就可以访问本地项目啦。

    (7)使用微信开发者工具测试

    微信开发者工具其实就是微信的浏览器,其中集成了chrome的调试工具,前面提到wx.config中的debug模式这里就发挥作用了,浏览器会自动弹出调用微信接口的返回结果。

    成功返回的话结果应该是ok什么的,图就不上了。提醒大家,上生产环境一定要把debug改为false~

    后记

     虽然已经给了主要的代码,大家一定还是不想写接口,下面附上完整的代码,如果你觉得解了燃眉之急,就点个顶吧,哈哈哈~

    复制代码
    @Controller
    public class WeChatController {
        private final Logger log = LoggerFactory.getLogger(this.getClass());
        //获取相关的参数,在application.properties文件中
        @Value("${wechat.appId}")
        private String appId;
        @Value("${wechat.appSecret}")
        private String appSecret;
        @Value("${wechat.url.accessToken}")
        private String accessTokenUrl;
        @Value("${wechat.url.apiTicket}")
        private String apiTicketUrl;
    
        //微信参数
        private String accessToken;
        private String jsApiTicket;
        //获取参数的时刻
        private Long getTiketTime = 0L;
        private Long getTokenTime = 0L;
        //参数的有效时间,单位是秒(s)
        private Long tokenExpireTime = 0L;
        private Long ticketExpireTime = 0L;
    
        //获取微信参数
        @RequestMapping("/wechatParam")
        @ResponseBody
        public Map<String, String> getWechatParam(String url){
            //当前时间
            long now = System.currentTimeMillis();
            log.info("currentTime====>"+now+"ms");
    
            //判断accessToken是否已经存在或者token是否过期
            if(StringUtils.isBlank(accessToken)||(now - getTokenTime > tokenExpireTime*1000)){
                JSONObject tokenInfo = getAccessToken();
                if(tokenInfo != null){
                    log.info("tokenInfo====>"+tokenInfo.toJSONString());
                    accessToken = tokenInfo.getString("access_token");
                    tokenExpireTime = tokenInfo.getLongValue("expires_in");
                    //获取token的时间
                    getTokenTime = System.currentTimeMillis();
                    log.info("accessToken====>"+accessToken);
                    log.info("tokenExpireTime====>"+tokenExpireTime+"s");
                    log.info("getTokenTime====>"+getTokenTime+"ms");
                }else{
                    log.info("====>tokenInfo is null~");
                    log.info("====>failure of getting tokenInfo,please do some check~");
                }
    
            }
    
            //判断jsApiTicket是否已经存在或者是否过期
            if(StringUtils.isBlank(jsApiTicket)||(now - getTiketTime > ticketExpireTime*1000)){
                JSONObject ticketInfo = getJsApiTicket();
                if(ticketInfo!=null){
                    log.info("ticketInfo====>"+ticketInfo.toJSONString());
                    jsApiTicket = ticketInfo.getString("ticket");
                    ticketExpireTime = ticketInfo.getLongValue("expires_in");
                    getTiketTime = System.currentTimeMillis();
                    log.info("jsApiTicket====>"+jsApiTicket);
                    log.info("ticketExpireTime====>"+ticketExpireTime+"s");
                    log.info("getTiketTime====>"+getTiketTime+"ms");
                }else{
                    log.info("====>ticketInfo is null~");
                    log.info("====>failure of getting tokenInfo,please do some check~");
                }
            }
    
            //生成微信权限验证的参数
            Map<String, String> wechatParam= makeWXTicket(jsApiTicket,url);
            return wechatParam;
        }
    
        //获取accessToken
        private JSONObject getAccessToken(){
            //String accessTokenUrl = https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
            String requestUrl = accessTokenUrl.replace("APPID",appId).replace("APPSECRET",appSecret);
            log.info("getAccessToken.requestUrl====>"+requestUrl);
            JSONObject result = HttpUtil.doGet(requestUrl);
            return result ;
        }
    
        //获取ticket
        private JSONObject getJsApiTicket(){
            //String apiTicketUrl = https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
            String requestUrl = apiTicketUrl.replace("ACCESS_TOKEN", accessToken);
            log.info("getJsApiTicket.requestUrl====>"+requestUrl);
            JSONObject result = HttpUtil.doGet(requestUrl);
            return result;
        }
    
        //生成微信权限验证的参数
        public Map<String, String> makeWXTicket(String jsApiTicket, String url) {
            Map<String, String> ret = new HashMap<String, String>();
            String nonceStr = createNonceStr();
            String timestamp = createTimestamp();
            String string1;
            String signature = "";
    
            //注意这里参数名必须全部小写,且必须有序
            string1 = "jsapi_ticket=" + jsApiTicket +
                    "&noncestr=" + nonceStr +
                    "&timestamp=" + timestamp +
                    "&url=" + url;
            log.info("String1=====>"+string1);
            try
            {
                MessageDigest crypt = MessageDigest.getInstance("SHA-1");
                crypt.reset();
                crypt.update(string1.getBytes("UTF-8"));
                signature = byteToHex(crypt.digest());
                log.info("signature=====>"+signature);
            }
            catch (NoSuchAlgorithmException e)
            {
                log.error("WeChatController.makeWXTicket=====Start");
                log.error(e.getMessage(),e);
                log.error("WeChatController.makeWXTicket=====End");
            }
            catch (UnsupportedEncodingException e)
            {
                log.error("WeChatController.makeWXTicket=====Start");
                log.error(e.getMessage(),e);
                log.error("WeChatController.makeWXTicket=====End");
            }
    
            ret.put("url", url);
            ret.put("jsapi_ticket", jsApiTicket);
            ret.put("nonceStr", nonceStr);
            ret.put("timestamp", timestamp);
            ret.put("signature", signature);
            ret.put("appid", appId);
    
            return ret;
        }
        //字节数组转换为十六进制字符串
        private static String byteToHex(final byte[] hash) {
            Formatter formatter = new Formatter();
            for (byte b : hash)
            {
                formatter.format("%02x", b);
            }
            String result = formatter.toString();
            formatter.close();
            return result;
        }
        //生成随机字符串
        private static String createNonceStr() {
            return UUID.randomUUID().toString();
        }
        //生成时间戳
        private static String createTimestamp() {
            return Long.toString(System.currentTimeMillis() / 1000);
        }
    }
    复制代码
  • 相关阅读:
    Resharper的使用
    SQL Server 占用CPU较高的解决方法
    014 FPGA千兆网UDP通信
    012 PCIe总线的基础知识
    008 PCI设备BAR空间的初始化
    016 基于FPGA的网口通信实例设计【转载】
    015 FPGA千兆网TCP通信【转载】
    006 PCI总线的桥与配置(一)
    004 PCI Express体系结构(四)
    007 PCI总线的桥与配置(二)
  • 原文地址:https://www.cnblogs.com/ygunoil/p/12930421.html
Copyright © 2011-2022 走看看