微信网页分享(配合微信公众平台)


源码下载地址

一. 准备工作
  • 准备一个域名(微信分享出去的合法链接都是挂载在域名下的,服务器的ip名是不行的),能用内网穿透的也可以(我测试阶段就是用的内网穿透的方法)。内网穿透方法可以见章节末尾参考的链接。

  • 在微信公众平台准备一个账号

    • 设置公众号的js安全域名(把域名放进去,不用加http的前缀)在这里插入图片描述
      在这里插入图片描述

保存之前要把文件下载下来放到项目根目录下,如果是开发环境的springboot项目可以参照这个教程做

`https://www.cnblogs.com/pxblog/p/13445128.htm`
  • 设置公众号开发信息(拿到AppIDAppSecret)

在这里插入图片描述

同时在`IP`白名单设置相关`ip`地址,最后才能成功获取`access_token`
注:开发阶段白名单添加本机所在ip地址,生产阶段添加云服务器所在ip地址

在这里插入图片描述

  • 微信公众平台没有相关账号的,可以用微信号使用测试账号进行开发
    https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login&token=840340790&lang=zh_CN
    测试账号的测试详情可参考一下大佬的流程:https://zhuanlan.zhihu.com/p/134461089
    注:使用测试账号接口,获取access_token这一步是可以正常操作的,但到后面分享朋友及朋友圈阶段就会报错,还是得用公众平台账号。
二. SpringBoot前后端不分离版本
  1. 属性文件application.yml中配置AppIDAppSecret
    在这里插入图片描述

  2. 初始化JSSDK配置信息,参考官方文档https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#3 JSSDK使用步骤,最重要的是要生成签名signature
    在这里插入图片描述

    获取signature流程如下:

    • 前端将要分享的url请求给后端

    • 获取 access_token,然后根据 access_token向微信官方Api 获取jsapi_ticket

    • 排序 noncestr(随机字符串), 有效的jsapi_tickettimestamp(时间戳),url(当前网页的URL,不包含#及其后面部分)4个参数拼接,例如:noncestr=XX&jsapi_ticket=XX&timestamp=XX&url=XX

    • 然后通过sha1加密拼接的4个参数得到signature

    注:

    • 如上图所示,JSSDK配置信息需要有:noncestr(随机字符串)、有效的signature(签名)、timestamp(时间戳)、appid(公众号appid),后端生成所需的配置信息并返回给前端,前端拿到配置信息并定义分享功能处理。

    • 有些人可能会说,我直接在微信中打开要分享的链接,点击发送朋友或朋友圈不也直接可以实现分享吗?可以是可以,但样式会很丑,如下所示,只有标题+链接,苍白无力,没有样式;

      在这里插入图片描述

      而进行了权限配置,则可以实现如下自定义的分享链接,即可以定制标题+简介+图片的效果。

    在这里插入图片描述

  3. 获取 access_token
    步骤2获取signature流程中可知,首先要获取access_token:

    String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
    tokenUrl = tokenUrl + "&appid=" + appid + "&secret=" + secret;
    JSONObject tokenJson = new JSONObject();
    tokenJson = getUrlResponse(tokenUrl);
    log.info(tokenJson.toString());
    log.info("tokenJson:"+tokenJson.toString());
    String token="";
    try {
        /**
        * TODO:access_token应该存入缓存,设置有效期为7200s
        */
        token = tokenJson.getString("access_token");
    } catch (JSONException e) {
        e.printStackTrace();
        log.error("报错了");
        return null;
    }
    
    
  4. 获取 jsapi_ticket

    String jsapiTicketUrl="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
    JSONObject jsapiTickeJson = new JSONObject();
    log.info("getJsapiTicket:获取token:"+token);
    if(StringUtils.isNotBlank(token)){
        jsapiTicketUrl = jsapiTicketUrl.replace("ACCESS_TOKEN",token);
        jsapiTickeJson=getUrlResponse(jsapiTicketUrl);
        log.info("tokenJson:"+jsapiTickeJson.toString());
        try {
            return (String) jsapiTickeJson.get("ticket");
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        }
    }else{
        return null;
    }
    
  5. 获取排序 noncestr(随机字符串)

    public class RandomStr {
        private static char ch[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
                'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
                'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
                'x', 'y', 'z', '0', '1' };//最后又重复两个0和1,因为需要凑足数组长度为64
    
        private static Random random = new Random();
        //生成指定长度的随机字符串
        public static String createRandomString(int length) {
            if (length > 0) {
                int index = 0;
                char[] temp = new char[length];
                int num = random.nextInt();
                for (int i = 0; i < length % 5; i++) {
                    temp[index++] = ch[num & 63];//取后面六位,记得对应的二进制是以补码形式存在的。
                    num >>= 6;//63的二进制为:111111
                    // 为什么要右移6位?因为数组里面一共有64个有效字符。为什么要除5取余?因为一个int型要用4个字节表示,也就是32位。
                }
                for (int i = 0; i < length / 5; i++) {
                    num = random.nextInt();
                    for (int j = 0; j < 5; j++) {
                        temp[index++] = ch[num & 63];
                        num >>= 6;
                    }
                }
                return new String(temp, 0, length);
            }
            else if (length == 0) {
                return "";
            }
            else {
                throw new IllegalArgumentException();
            }
        }
    
        public static void main(String[] args) {
            System.out.println(createRandomString(16));
        }
    }
    
  6. 通过Sha1加密获取signature

    long timestamp = System.currentTimeMillis() / 1000;
    String noncestr = RandomStr.createRandomString(16);
    String str = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "&timestamp=" + timestamp + "&url=" + url;
    String signature = Sha1.encode(str);
    
    public class Sha1 {
        private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
                '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    
        private static String getFormattedText(byte[] bytes) {
            int len = bytes.length;
            StringBuilder buf = new StringBuilder(len * 2);
            // 把密文转换成十六进制的字符串形式
            for (int j = 0; j < len; j++) {
                buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
                buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
            }
            return buf.toString();
        }
    
        public static String encode(String str) {
            if (str == null) {
                return null;
            }
            try {
                MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
                messageDigest.update(str.getBytes());
                return getFormattedText(messageDigest.digest());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    完成以上配置信息的生成后,后端将JSSDK配置信息 (携带分享页面信息的签名signature等)及其他自定义信息打包返回给前端

  7. 前端JS接口配置
    在这里插入图片描述

    因前后端不分离,所以页面还得引入JqueryJs文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>政策详情页面</title>
    </head>
    <body>
        <p class="MsoNormal" style="mso-para-margin-top: .5gd; mso-para-margin-right: 0cm; mso-para-margin-bottom: .5gd; mso-para-margin-left: 0cm; text-align: center; line-height: 150%; margin: 7.8pt 0cm 7.8pt 0cm;" align="center"><strong style="mso-bidi-font-weight: normal;"><span style="font-size: 12.0pt; line-height: 150%; font-family: '微软雅黑','sans-serif';">中共北京市委 北京市人民政府印发《关于促进中医药传承创新发展的实施方案》的通知</span></strong></p>
    </body>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
    <script type="text/javascript">
        $(function () {
            //当前页面的url地址
            var currUrl = decodeURIComponent(location.href.split('#')[0]);
            var shareLink = window.location.href.toString();//分享链接
            $.ajax({
                url: "/get_wx_config",
                dataType : "json",
                data: {
                    'url': currUrl
                },
                error: function (res) {
                    console.log(res);
                    alert("发生错误");
                },
                success: function (res) {
                    console.log(res);
                    var appId = res.appId;
                    var nonceStr = res.nonceStr;
                    var timestamp = res.timestamp;
                    var signature = res.signature;
                    //自定义返回内容
                    var shareImgUrl = res.backImgUrl;
                    var backTitle = res.backTitle;
                    var backDesc = res.backDesc;
                    wx.config({
                        debug: false, //开启调试模式,开发阶段可以改成true,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                        appId: appId, //必填,公众号的唯一标识
                        timestamp: timestamp, // 必填,生成签名的时间戳
                        nonceStr: nonceStr, //必填,生成签名的随机串
                        signature: signature, // 必填,签名,见附录1
                        jsApiList: [            //必填,需要使用的JS接口列表,所有JS接口列表 见附录2
                            'updateAppMessageShareData',
                            'updateTimelineShareData'
                        ]
                    });
    
    
                    wx.ready(function () {      //需在用户可能点击分享按钮前就先调用
                        //分享给朋友”及“分享到QQ”
                        wx.updateAppMessageShareData({
                            title: '朋友我是标题', // 分享标题
                            desc: '朋友 我是描述', // 分享描述
                            link: shareLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                            imgUrl: shareImgUrl, // 分享图标
                            success: function (res) {
                                // 设置成功
                            }
                        })
    
                        //分享到朋友圈”及“分享到QQ空间
                        wx.updateTimelineShareData({
                            title: '朋友我是标题', // 分享标题
                            desc: '朋友 我是描述', // 分享描述
                            link: shareLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                            imgUrl: shareImgUrl, // 分享图标
                            success: function (res) {
                                // 设置成功
                            }
                        })
    
    
                    });
    
                    wx.error(function (res) {
    
                    });
                }
            });
    
        });
    
    </script>
    </html>  
    
  8. 测试
    打开微信开发者工具,在地址栏输入http://域名/to_detail,点击分享,能够出现以下界面,说明已经可以实现分享了!

在这里插入图片描述

以上主要参考微信官方文档和结合以下两位大佬的做法:

  • https://www.cnblogs.com/a876459952/p/13294124.html
  • https://www.cnblogs.com/pxblog/p/12881454.html
三. SpringBoot+Vue前后端分离版本
  1. 后端变化不大,主要将share.html改造为Vue页面,同时加入了一个二维码扫码分享功能
    在这里插入图片描述

    点击如图所示微信图标,弹出二维码,微信扫一扫点开即可分享:
    在这里插入图片描述

  2. 改造Vue前端页面
    首先创建一个wxShare.js

    // 要用到微信API
    function getJSSDK(url, dataForWeixin) {
        // 调用后台接口换取参数
        axios.get('http://域名/get_wx_config', {
            params: {
                url,
            },
        }).then((res) => {
            console.log(res)
            wx.config({
                debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                appId: res.appId, // 必填,公众号的唯一标识
                timestamp: res.timestamp, // 必填,生成签名的时间戳
                nonceStr: res.nonceStr, // 必填,生成签名的随机串
                signature: res.signature, // 必填,签名
                jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'] // 必填,需要使用的JS接口列表
            });
            wx.ready(function () {
                //分享给朋友
                wx.updateAppMessageShareData({
                    title: dataForWeixin.title,
                    desc: dataForWeixin.desc,
                    link: dataForWeixin.linkurl,
                    imgUrl: dataForWeixin.img,
                    success: function () {
                        // 用户确认分享后执行的回调函数
                        // alert('分享成功');
                    },
                    cancel: function () {
                        // 用户取消分享后执行的回调函数
                    },
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    }
                });
                //分享到朋友圈
                wx.updateTimelineShareData({
                    title: dataForWeixin.title,
                    desc: dataForWeixin.desc,
                    link: dataForWeixin.linkurl,
                    imgUrl: dataForWeixin.img,
                    success: function (res) {
                        // 设置成功
                        // alert("分享成功!")
                        // console.log("分享成功!")
                    },
                    cancel: function () {
                        // alert("已取消")
                    },
                    fail: function (res) {
                        alert(JSON.stringify(res));
                    }
                })
                wx.error(function (res) {
                    // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
                    // alert("errorMSG:" + res);
                });
            });
        });
    }
    
    export default {
        // 获取JSSDK
        getJSSDK,
    }
    

    其次创建一个分享页面wxShare.vue,其中,titledesclinkurlimg这四个为分享的设置,linkurl应动态获取当前前端页面地址

    <template>
      <div>
        <div v-html="title"></div>
        <div class="wx"style="text-align: center">
          <img src="../assets/wx.jpg" alt="点击分享" title="点击分享"/>
        </div>
        ......
      </div>
    </template>
    <script>
    import share from '@/assets/js/share'
    import vueQr from 'vue-qr'
    export default {
      name: "wxShare",
      components: {
        vueQr
      },
      mounted() {
        const url = location.href.split('#')[0];
        console.log(url)
        const dataForWeixin = {
          title: '中共北京市委 北京市人民政府印发《关于促进中医药传承创新发展的实施方案》的通知',    // 分享标题
          desc: '为贯彻落实《中共中央、国务院关于促进中医药传承创新发展的意见》精神,结合本市实际,制定如下实施方案',            // 内容描述
          linkurl: window.location.href.toString(), // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
          img: 'http://wx.qlogo.cn/mmopen/ciaIftfPzwlo0coPuwwLS5Fw9UwGMlxY2ziaWpqXzevJI8dKeDvk4n3NxtZS4D8dNHSYUhbiaA6IIGnFsiagEbRlaExselicC3pEA/64',        // 分享内容显示的图片(图片必须是正方形的链接)
        };
        share.getJSSDK(url,dataForWeixin)
      },
      methods: {
    
      },
      data() {
        return {
          dialogVisible: false,
          shareData: {
            // url: 'http://cxyabc.vaiwan.com/to_detail',  //需要转化成二维码的网址
            url: window.location.href.toString(),  //需要转化成二维码的网址
            icon: require('@/assets/img.png')  //二维码中间的图片,可以不设置
          },
          title: '<p class="MsoNormal" style="mso-para-margin-top: .5gd; mso-para-margin-right: 0cm; mso-para-margin-bottom: .5gd; mso-para-margin-left: 0cm; text-align: center; line-height: 150%; margin: 7.8pt 0cm 7.8pt 0cm;" align="center"><strong style="mso-bidi-font-weight: normal;"><span style="font-size: 12.0pt; line-height: 150%; font-family: \'微软雅黑\',\'sans-serif\';">中共北京市委北京市人民政府印发《关于促进中医药传承创新发展的实施方案》的通知</span></strong></p>\n',
        }
      }
    }
    </script>
    
  3. vue工程文件build打包为静态文件,用nginx做代理进行前后端分离配置
    注:实际上线或开发过程可能会出现{"errMsg":"translateVoice:fail, the permission value is offline verifying"}情况,除了百度能搜到的解决方法外,我在实际过程中遇到了以下几种解决方法:

    • 后端打包返回给前端的JSSDK配置信息格式有误,下面截图位置可以不转为String,直接返回,因为有+@ResponseBody注解,框架会自动返回json对象
      在这里插入图片描述

    • 后端接收前端带域名的当前网页url这里,刚开始我的工程不会报错,后面一直报上面的错误,取消替换'#'的操作,则不会报错,具体原因还在探究,至少我的项目是这样解决了报错原因的。
      在这里插入图片描述

前后端分离主要结合了https://zhuanlan.zhihu.com/p/135179184这位大佬的文章

源码下载地址

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bynn_csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值