Springboot+Vue实现微信网页自定义分享功能

前言

在这篇文章介绍并记录关于Springboot后端代码整合vue前端代码简单实现微信网页自定义分享朋友和朋友圈功能,
注:部分代码是通过网上搜索和自己结合实现的功能,下面就贴上自己的实现步骤。

1、微信开发文档

[https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#5]
具体详细可先观看微信开发文档的实现步骤,通过查看文档大致可知,
实现此分享功能需要有前提查看目录,

image.png

1.1 JSSDK使用步骤

微信JS-SDK是微信公众平台 面向网页开发者提供的基于微信内的网页开发工具包。此处略。

1.1.1 步骤一:绑定域名

在实现此功能之前必须要有一个服务器和一个已经备案好了的域名,倘若没有服务器和域名,可以通过使用NATAPP内网穿透的方式来实现,具体可上网搜索学习。
在此之前需要申请了一个测试公众号平台来获取appId和appsecret
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
具体不懂得怎么申请可查看本人另外一篇文章有说明。
接下来就是绑定域名了,需要在JS接口安全域名填写自己的域名

image.png

1.1.2 步骤二:引入JS文件

官方说明是可以制醋HTTP和HTTPS方式,在此使用的是HTTP。
由于项目是使用Vue来开发,在此就不是引入JS文件了,而是通过npm的方式。
注:此处本人有个意外的大坑,不知道是不是引错Jssdk,在这处贴上这个错误。

npm install weixin-js-sdk --save    //使用这个npm安装后

Vue引用

import wx from ‘weixin-js-sdk’   //Vue里面引入后会出现下面错误

image.png

最后通过使用了

npm install weixin-jsapi --save //使用这个npm安装后解决问题,正常显示
npm install axios --save   // 在装一个axios

Vue引用

import wx from 'weixin-jsapi'    //使用正常浏览器不会报错
import axios from 'axios'
1.1.3 步骤三:通过config接口注入权限验证配置

通过文档的顺序可知,所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用,这个是必不可缺的。在此我是在created钩子写上代码,也可另外写一个JS比较简洁的代码来调用,此处我使用的方式比较粗暴简单吧。

//此处是需用axios 去后端服务器获取的数据并注入config

        axios.get('http://域名/项目路径/wx/getAccessTokenAndJsapi?url='+encodeURIComponent(location.href.split('#')[0])).then((response)=>{
            var res = response.data;
            console.log(res)
            wx.config({
                debug: true, // 开启调试模式
                appId: res.data.appId, // 必填,公众号的唯一标识
                timestamp: res.data.timestamp, // 必填,生成签名的时间戳
                nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
                signature: res.data.signature,// 必填,签名
                jsApiList: [
                "updateAppMessageShareData",//自定义“分享给朋友”及“分享到QQ”按钮的分享内容
                "updateTimelineShareData",//自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容
                "onMenuShareWeibo",//获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口
                ] // 必填,需要使用的JS接口列表
            })
        }).catch((error)=>{
            console.log(error)
        }); 
1.1.4 步骤四:通过ready接口处理成功验证

由于此处是使用分享朋友和朋友圈功能,需要将代码写在wx.ready(function(){
}) 里面,

        wx.ready(() => {
            wx.updateAppMessageShareData({
                title: '微信自定义分享朋友', // 分享标题
                desc: '微信自定义分享朋友', // 分享描述
                link: location.href.split('#')[0], // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
    imgUrl:'http://sjbz.fd.zol-img.com.cn/t_s240x320c/g5/M00/00/03/ChMkJ1fJVzuIYa52AAabqdbC5xQAAU-CgPnq3YABpvB522.jpg',
                success: (res) => {
                    console.log(res);
                }
            });
            wx.updateTimelineShareData({
                title: '微信自定义分享到朋友圈', // 分享标题
                desc: '微信自定义分享到朋友圈', // 分享描述
                link: location.href.split('#')[0], // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl:'http://sjbz.fd.zol-img.com.cn/t_s240x320c/g5/M00/00/03/ChMkJ1fJVzuIYa52AAabqdbC5xQAAU-CgPnq3YABpvB522.jpg',
               // imgUrl: require('./logo.jpg'), // 分享图标(不能赋相对路径,一定要是绝对路径)
                success: (res) => {
                    console.log(res);
                }
                });
      });
1.1.5 步骤五:通过error接口处理失败验证

简单粗暴点直接写在下面就好,此处是直接使用微信文档提供的。

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

具体的接口调用说明可以仔细观看开发文档


image.png
1.1.6 基本实现代码
import wx from 'weixin-jsapi'
import axios from 'axios'
export default {
    created(){
        // 自定义config
        axios.get('http://域名/项目名称/wx/getAccessTokenAndJsapi?url='+encodeURIComponent(location.href.split('#')[0])).then((response)=>{
            var res = response.data;
            console.log(res)
            wx.config({
                debug: true, // 开启调试模式
                appId: res.data.appId, // 必填,公众号的唯一标识
                timestamp: res.data.timestamp, // 必填,生成签名的时间戳
                nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
                signature: res.data.signature,// 必填,签名
                jsApiList: [
                "updateAppMessageShareData",//自定义“分享给朋友”及“分享到QQ”按钮的分享内容
                "updateTimelineShareData",//自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容
                "onMenuShareWeibo",//获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口
                ] // 必填,需要使用的JS接口列表
            })
        }).catch((error)=>{
            console.log(error)
        }); 
        wx.ready(() => {
            wx.updateAppMessageShareData({
                title: '微信自定义分享朋友', // 分享标题
                desc: '微信自定义分享朋友', // 分享描述
                link: location.href.split('#')[0], // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
    imgUrl:'http://sjbz.fd.zol-img.com.cn/t_s240x320c/g5/M00/00/03/ChMkJ1fJVzuIYa52AAabqdbC5xQAAU-CgPnq3YABpvB522.jpg',
                success: (res) => {
                    console.log(res);
                }
            });
            wx.updateTimelineShareData({
                title: '微信自定义分享到朋友圈', // 分享标题
                desc: '微信自定义分享到朋友圈', // 分享描述
                link: location.href.split('#')[0], // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl:'http://sjbz.fd.zol-img.com.cn/t_s240x320c/g5/M00/00/03/ChMkJ1fJVzuIYa52AAabqdbC5xQAAU-CgPnq3YABpvB522.jpg',
               // imgUrl: require('./logo.jpg'), // 分享图标(不能赋相对路径,一定要是绝对路径)
                success: (res) => {
                    console.log(res);
                }
                });
      });
    wx.error(function(res){
  // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
    console.log(res);
    });  
    },
}

注:前端页面基本完毕

2. 后端代码实现

第一步:需要通过测试公众号的appId和appsecret实现获取access_token。
第二步:使用access_token获取jsapi_ticket。
第三步:使用时间戳,随机数,jsapi_ticket和要访问的url按照签名算法拼接字符串。
第四步:对第三步的字符串进行SHA1加密,得到签名。

只贴上实现代码,具体Springboot框架可以通过网上来进行学习。
代码写完后,需要通过maven命令
mvn install -Dmaven.test.skip=true打成Jar包,也可以通过开发工具打包,
然后丢到阿里云服务器上面,再用nginx域名代理。

2.1.1 添加依赖

再此之前需要添加pom.xml依赖

        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.2.3</version>
            <classifier>jdk15</classifier><!-- jdk版本 -->
    </dependency>
2.2.2 步骤一:获取 access_token
    public static String getAccessToken(String appId, String appSecret){

        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
        String accessToken = null;
        try
        {
            URL urlGet = new URL(url);
            HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
            http.setRequestMethod("GET"); // 必须是get方式请求
            http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            http.setDoOutput(true);
            http.setDoInput(true);
            System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
            System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
            http.connect();
            InputStream is = http.getInputStream();
            int size = is.available();
            byte[] jsonBytes = new byte[size];
            is.read(jsonBytes);
            String message = new String(jsonBytes, "UTF-8");
            JSONObject jsonObj = JSONObject.fromObject(message);
            accessToken = jsonObj.getString("access_token");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return accessToken;
    }
2.2.3 步骤二:获取 jsapi_ticket
    public static String getAccessTicket(String access_token) {
        String ticket = null;
        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ access_token +"&type=jsapi";//这个url链接和参数不能变
        try {
            URL urlGet = new URL(url);
            HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
            http.setRequestMethod("GET"); // 必须是get方式请求
            http.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
            http.setDoOutput(true);
            http.setDoInput(true);
            System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
            System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
            http.connect();
            InputStream is = http.getInputStream();
            int size = is.available();
            byte[] jsonBytes = new byte[size];
            is.read(jsonBytes);
            String message = new String(jsonBytes, "UTF-8");
            JSONObject demoJson = JSONObject.fromObject(message);
            System.out.println("JSON字符串:"+demoJson);
            ticket = demoJson.getString("ticket");
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ticket;
    }
2.2.3 步骤三:SHA1加密
    public static String SHA1(String decript) {
        try {
            MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
            digest.update(decript.getBytes());
            byte messageDigest[] = digest.digest();
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }
2.2.4 整体WxTokenAndJsapiUtil代码
public class WxTokenAndJsapiUtil {

    //测试公众号aapId
     public static final String appId = "";

     //测试公众号密钥appSecret
     public static final String appSecret= "";

     //当前页面,此处我是直接使用域名来访问当前需要分享的页面
     public static final String url  = "www.xxx.top";

     /**
     * 通过appId和appSecret 获取Token
     * @param appId
     * @param appSecret
     * @return
     */
    public static String getAccessToken(String appId, String appSecret){

        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
        String accessToken = null;
        try
        {
            URL urlGet = new URL(url);
            HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
            http.setRequestMethod("GET"); // 必须是get方式请求
            http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            http.setDoOutput(true);
            http.setDoInput(true);
            System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
            System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
            http.connect();
            InputStream is = http.getInputStream();
            int size = is.available();
            byte[] jsonBytes = new byte[size];
            is.read(jsonBytes);
            String message = new String(jsonBytes, "UTF-8");
            JSONObject jsonObj = JSONObject.fromObject(message);
            accessToken = jsonObj.getString("access_token");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return accessToken;
    }

    /**
     * 获得ACCESS_TICKET
     * @param access_token
     * @return
     */
    public static String getAccessTicket(String access_token) {
        String ticket = null;
        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ access_token +"&type=jsapi";//这个url链接和参数不能变
        try {
            URL urlGet = new URL(url);
            HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
            http.setRequestMethod("GET"); // 必须是get方式请求
            http.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
            http.setDoOutput(true);
            http.setDoInput(true);
            System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
            System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
            http.connect();
            InputStream is = http.getInputStream();
            int size = is.available();
            byte[] jsonBytes = new byte[size];
            is.read(jsonBytes);
            String message = new String(jsonBytes, "UTF-8");
            JSONObject demoJson = JSONObject.fromObject(message);
            System.out.println("JSON字符串:"+demoJson);
            ticket = demoJson.getString("ticket");
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ticket;
    }

    /**
     * 加密
     * @param decript
     * @return
     */
    public static String SHA1(String decript) {
        try {
            MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
            digest.update(decript.getBytes());
            byte messageDigest[] = digest.digest();
            // Create Hex String
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }
2.2.5 实现分享朋友和朋友圈 controller控制器
    @GetMapping(value = "getAccessTokenAndJsapi")
    public CommonResponse getAccessTokenAndJsapi(@RequestParam (value = "url",required = false) String url){
        log.info(" url = {} ",url);
        // 1. 获取access_token
        String accessToken = WxTokenAndJsapiUtil.getAccessToken(WxTokenAndJsapiUtil.appId,WxTokenAndJsapiUtil.appSecret);
        log.info(" accessToken = {} ",accessToken);
        // 2. 获取access_ticket
        String accessTicket = WxTokenAndJsapiUtil.getAccessTicket(accessToken);
        log.info(" accessTicket = {} ",accessTicket);
        // 3. 获取时间戳和随机字符串
        String nonceStr = UUID.randomUUID().toString().replace("-", "").substring(0, 16);//随机字符串
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);//时间戳
        log.info("nonceStr = {} ,  timestamp = {}",nonceStr,timestamp);
        // 4. 获取url
        if (!StringUtils.isEmpty(url) && url.length() > 0 ){
        }else {
            url = WxTokenAndJsapiUtil.url;
        }
        // 5. 将参数排序并拼接字符串
        String str = "jsapi_ticket="+accessTicket+"&noncestr="+nonceStr+"&timestamp="+timestamp+"&url="+url;
        // 6. 将字符串进行sha1加密
        String signature = WxTokenAndJsapiUtil.SHA1(str);
        log.info(" signature = {} ",signature);
        Map<String,String> map=new HashMap();
        map.put("appId",WxTokenAndJsapiUtil.appId);
        map.put("timestamp",timestamp);
        map.put("accessToken",accessToken);
        map.put("ticket",accessTicket);
        map.put("nonceStr",nonceStr);
        map.put("signature",signature);
        JSONObject jsonObject = JSONObject.fromObject(map);
        return ResponseVoUtil.success(jsonObject);
    }
2.2.6 统一响应数据实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResponse<T> implements Serializable {

    private Integer code;
    private String message;
    private T data;

    public CommonResponse(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}
2.2.7 统一响应数据封装类
public class ResponseVoUtil {

    public static CommonResponse success(Object object) {
             CommonResponse response = new CommonResponse();
             response.setData(object);
             response.setCode(0);
             response.setMessage("成功");
             return response;
    }

    public static CommonResponse success(){
        return success(null);
    }

    public static CommonResponse error(Integer code,String msg){
        CommonResponse response = new CommonResponse();
        response.setCode(code);
        response.setMessage(msg);
        return response;
    }
}
2.2.8 访问接口结果

http://自己域名/项目名称/wx/getAccessTokenAndJsapi

image.png

注:JS-SDK使用权限签名算法
可参考开发文档的Java的示例代码

http://demo.open.weixin.qq.com/jssdk/sample.zip
此时前端和后端的代码基本完成。

3. 将项目发打包上传服务器

本人使用的打包是使用cmd 的路径加上命令的方式打包

mvn install -Dmaven.test.skip=true

也可以使用开发工具的打包

3.3.1 上传后运行服务器

注:使用的是centos7 系统

java -jar  xxxxxx.jar 
image.png
3.3.2 nginx配置

conf目录下使用 vim nginx.conf

 server {
        listen       80;
        server_name  自己的域名;
        #charset koi8-r;

        #access_log  logs/host.access.log  main;


        # 静态页面
        location / {
             add_header Access-Control-Allow-Origin *;
               root   /usr/local/html/h5;     //Vue打包后的项目名存放的位置
          #    root   /usr/local/html/demo;
          #    root   /usr/local/html/dist;
               index  index.html index.htm;

       }

       # 服务器代理,在此使用了项目路径名称
       # 访问的时候就可以通过域名加/路径
       # 例如 域名/hc/wx/getAccessTokenAndJsapi
       location /hc {
             proxy_pass http://自己的ip地址:项目端口/项目路径;   //将ip地址反向给域名代理
      # 例如   proxy_pass http://115.xxx.xxx.xxx/hc;
      }

4. 分享功能实现展示

在此使用微信点击微信页面点击分享功能

4.4.1 步骤一:微信页面访问域名
image.png
4.4.2 步骤二:进入域名后会提示成功

config


image.png

updateAppMessageShareData


image.png

updateTimelineShareData
image.png
4.4.3 步骤三:选中右上角发送朋友

选中右上角发送朋友后显示


image.png
4.4.4 步骤三:最终效果
image.png

image.png

5.结语

大致就把一个Demo案例分享朋友、朋友圈功能案例实现了,
要是有问题可在留言提问,讨论讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341