天天看點

微信自定義分享連結内容,wx.updateAppMessageShareData、wx.updateTimelineShareData、wx.onMenuShareTimeline目錄

微信分享自定義連結

  • 目錄
    • 問題與需求
    • 準備
    • 公衆号設定
    • 代碼
      • 後端代碼(完整代碼在附錄)
        • application.properties檔案配置
        • (1)擷取accessToken
        • (2)擷取jsapiTicket
        • (3)生成簽名
        • 生成wx.config
        • 接口傳回wx.config
      • 前端代碼(完整代碼在附錄)
    • 測試
    • 附錄
      • 後端完整代碼
        • 微信工具類.java
        • 網絡工具類.java
        • controler互動
        • js
      • 常見問題
        • 擷取JSSDK權限成功但分享無效果
        • 本地調試無效果
        • 微信頁面緩存

目錄

問題與需求

問題:

微信自定義分享連結内容,wx.updateAppMessageShareData、wx.updateTimelineShareData、wx.onMenuShareTimeline目錄

需求:

微信自定義分享連結内容,wx.updateAppMessageShareData、wx.updateTimelineShareData、wx.onMenuShareTimeline目錄

準備

認證的公衆号或者服務号(記住開發者ID、開發者密碼)

微信自定義分享連結内容,wx.updateAppMessageShareData、wx.updateTimelineShareData、wx.onMenuShareTimeline目錄

備案過的域名

公衆号設定

設定-開發-基本配置-IP白名單-添加網站伺服器IP位址

設定-公衆号設定-功能設定-設定業務域名與JS接口安全域名(即網站的域名)(這裡需要加入認證TXT檔案到項目)

微信自定義分享連結内容,wx.updateAppMessageShareData、wx.updateTimelineShareData、wx.onMenuShareTimeline目錄

代碼

後端代碼(完整代碼在附錄)

根據官方文檔,可以得知大步驟為->綁定域名->引入JS檔案->通過config接口注入權限驗證配置。

也就是說後端隻要傳回wx.config所需參數即可。

wx.config({
  debug: true, // 開啟調試模式,調用的所有api的傳回值會在用戶端alert出來,若要檢視傳入的參數,可以在pc端打開,參數資訊會通過log打出,僅在pc端時才會列印。
  appId: '', // 必填,公衆号的唯一辨別
  timestamp: , // 必填,生成簽名的時間戳
  nonceStr: '', // 必填,生成簽名的随機串
  signature: '',// 必填,簽名
  jsApiList: [] // 必填,需要使用的JS接口清單
});
           

除了簽名,其他參數都可直接擷取或生成,查閱官方簽名文檔,可知需要先生成jsapi_ticket,然後再根據文檔需求生成簽名。

根據文檔可知生成簽名步驟:擷取accessToken -> 生成jsapi_ticket ->簽名算法 -> sha1 加密

application.properties檔案配置

#微信配置
WX_APPID=xxxx			#appId
WX_APPSECRET=xxxxx #密鑰
WX_GRANTTYPE=client_credential
           
微信自定義分享連結内容,wx.updateAppMessageShareData、wx.updateTimelineShareData、wx.onMenuShareTimeline目錄

(1)擷取accessToken

@Scheduled(initialDelay = 1000, fixedDelay = 100*60*1000)
    public String getAccessToken() {
        Map<String,String> resultMap=new HashMap<>();
        //擷取access_token
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}";
        //設定參數
        Map<String, Object> map = new HashMap<>();
        map.put("appid", WX_APPID);
        map.put("secret", WX_APPSECRET);
        //發送get請求
        String accessTokenResult = ((Map<String, String>) JSON.parse(httpUtil.GETclient(url, map))).get("access_token");
        tokenAll = accessTokenResult;
        return accessTokenResult;
    }
           

(2)擷取jsapiTicket

@Scheduled(initialDelay = 1000, fixedDelay = 100*60*1000)
    public String getJsapiTicket() {
        Map<String,Object> map=new HashMap<>();
        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={ACCESS_TOKEN}&type=jsapi";
        //設定參數
        map.put("ACCESS_TOKEN", getAccessToken());
        //發送get請求
        String ticket = ((Map<String, String>) JSON.parse(httpUtil.GETclient(url, map))).get("ticket");
        ticketAll = ticket;
        return ticket;
    }
           

(3)生成簽名

這個方法是參考官方的簽名算法文檔

/**
     * 簽名
     * @return
     */
    public Map<String, String> sign(String jsapi_ticket, String url){
        Map<String,String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String stringAppend;
        String signature = "";

        //注意這裡參數名必須全部小寫,且必須有序
        stringAppend = "jsapi_ticket=" + jsapi_ticket +
                "&noncestr=" + nonce_str +
                "&timestamp=" + timestamp +
                "&url=" + url;
        System.out.println(stringAppend);

        try
        {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(stringAppend.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);
        ret.put("appId", WX_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;
    }
/**
     * 随機字元串
     */
    public String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    /**
     * 時間戳
     * @return
     */
    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
           

生成wx.config

public Map<String,String> wxConfig(String url) {
        String jsapi_ticket = ticketAll;

        // 注意 URL 一定要動态擷取,不能 hardcode
        Map<String, String> ret = sign(jsapi_ticket, url);
        for (Map.Entry entry : ret.entrySet()) {
            System.out.println(entry.getKey() + ", " + entry.getValue());
        }
        return ret;
    }
           

接口傳回wx.config

/**
     * 擷取wechatConfig資訊
     * @param url   通路頁面的位址
     * @return
     */
    @GetMapping("/getWechatConfig")
    public Map<String,String> getWechatConfig(String url){
        return wechatUtil.wxConfig(url);
    }
           

前端代碼(完整代碼在附錄)

分享接口文檔

引入JS檔案

JS代碼

//将url 存進緩存下
var url = "https://" + window.location.host; //伺服器的url
var urlCureent = encodeURIComponent(location.href); //目前頁面的url
$.ajax({
                async:true,
                dataType:"json",
                type:"GET",
                url: url + "/verification/getWechatConfig?url=" + urlCureent,
                contentType: "application/json; charset=utf-8",
                success:function(data){
                    wx.config({
                        debug: 0,
                        appId: data.appId,
                        timestamp: data.timestamp,
                        nonceStr: data.nonceStr,
                        signature: data.signature,
                        jsApiList: ["updateAppMessageShareData", "updateTimelineShareData","onMenuShareTimeline","onMenuShareAppMessage"]
                    });
                    wx.ready(function () {   //需在使用者可能點選分享按鈕前就先調用
                        wx.updateAppMessageShareData({
                            title: cardVo.cardName + "個人名片", // 分享标題
                            desc: cardVo.cardNameEn + "'s card", // 分享描述
                            link: location.href, // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
                            imgUrl: url + cardVo.cardImg, // 分享圖示
                            success: function () {
                                console.log("success");// 設定成功
                            }
                        });

                        wx.updateTimelineShareData({
                            title: cardVo.cardName + "個人名片", // 分享标題
                            link: location.href, // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
                            imgUrl: url + cardVo.cardImg, // 分享圖示
                            success: function () {
                                console.log("success2");// 設定成功
                            }
                        });

                        wx.onMenuShareTimeline({
                            title: cardVo.cardName + "個人名片", // 分享标題
                            desc: cardVo.cardNameEn + "'s card", // 分享描述
                            link: location.href, // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
                            imgUrl: url + cardVo.cardImg, // 分享圖示
                        });

                        wx.onMenuShareAppMessage({
                            title: cardVo.cardName + "個人名片", // 分享标題
                            link: location.href, // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
                            imgUrl: url + cardVo.cardImg, // 分享圖示
                        });
                    });
                }
            });
           

測試

微信自定義分享連結内容,wx.updateAppMessageShareData、wx.updateTimelineShareData、wx.onMenuShareTimeline目錄

可以看到已經成功擷取JSSDK 權限

附錄

後端完整代碼

微信工具類.java

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.Formatter;


/**
 * @program:gzdm
 * @author:wihenne
 * @creatTime:2021/05/05
 **/
@Component
@PropertySource({"classpath:application.properties"})
public class WechatUtil {
    @Value("${WX_APPID}")
    String WX_APPID;
    @Value("${WX_APPSECRET}")
    String WX_APPSECRET;
    @Value("${WX_GRANTTYPE}")
    String WX_GRANTTYPE;

    @Autowired
    HttpUtil httpUtil;
    public static String tokenAll;            //微信公衆号的accessToken對象,由于請求次數有限制,這裡使用全局靜态變量儲存起來
    public static String ticketAll;//使用全局靜态變量存儲ApiTicket對象,當然如果使用緩存架構儲存當然更好,這邊隻是做一個簡單示例
    //用于下面傳回随機字元串的函數
    private final static String string = "0123456789";
    final private static char[] chars = string.toCharArray();

    /**
     * 擷取公衆号的ACCESS_TOKEN
     *
     * @return string
     */
    //重新整理access_token 100分鐘重新整理一次,伺服器啟動的時候重新整理一次(access_token有效期是120分鐘,我設定的是每100分鐘重新整理一次)
    @Scheduled(initialDelay = 1000, fixedDelay = 100*60*1000)
    public String getAccessToken() {
        Map<String,String> resultMap=new HashMap<>();
        //擷取access_token
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}";
        //設定參數
        Map<String, Object> map = new HashMap<>();
        map.put("appid", WX_APPID);
        map.put("secret", WX_APPSECRET);
        //發送get請求
        String accessTokenResult = ((Map<String, String>) JSON.parse(httpUtil.GETclient(url, map))).get("access_token");
        tokenAll = accessTokenResult;
        return accessTokenResult;
    }

    /**
     * 擷取jsapiTicket
     *
     * @return map
     */
    @Scheduled(initialDelay = 1000, fixedDelay = 100*60*1000)
    public String getJsapiTicket() {
        Map<String,Object> map=new HashMap<>();
        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={ACCESS_TOKEN}&type=jsapi";
        //設定參數
        map.put("ACCESS_TOKEN", getAccessToken());
        //發送get請求
        String ticket = ((Map<String, String>) JSON.parse(httpUtil.GETclient(url, map))).get("ticket");
        ticketAll = ticket;
        return ticket;
    }

    /**
     * 生成wx.config
     *
     * @return map
     */
    public Map<String,String> wxConfig(String url) {
        String jsapi_ticket = ticketAll;

        // 注意 URL 一定要動态擷取,不能 hardcode
        Map<String, String> ret = sign(jsapi_ticket, url);
        for (Map.Entry entry : ret.entrySet()) {
            System.out.println(entry.getKey() + ", " + entry.getValue());
        }
        return ret;
    }


    /**
     * 簽名
     * @return
     */
    public Map<String, String> sign(String jsapi_ticket, String url){
        Map<String,String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String stringAppend;
        String signature = "";

        //注意這裡參數名必須全部小寫,且必須有序
        stringAppend = "jsapi_ticket=" + jsapi_ticket +
                "&noncestr=" + nonce_str +
                "&timestamp=" + timestamp +
                "&url=" + url;
        System.out.println(stringAppend);

        try
        {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(stringAppend.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);
        ret.put("appId", WX_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;
    }

    //Sha1加密
    public static String getSha1(String str){
        if(str==null||str.length()==0){
            return null;
        }
        char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9',
                'a','b','c','d','e','f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));

            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j*2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {

            return null;
        }
    }


    /**
     * 随機字元串
     */
    public String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    /**
     * 時間戳
     * @return
     */
    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}

           

網絡工具類.java

import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * @program:gzdm
 * @author:wihenne
 * @creatTime:2021/05/06
 **/
@Component
public class HttpUtil {
    //發起GET請求
    public String GETclient(String url, Map<String, Object> map) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        String result = restTemplate.getForObject(url, String.class, map);
        return result;
    }
    //發起POST請求,擷取圖檔位元組
    public byte[] getCodeImgBytes(String url,Map<String,Object> param){
        RestTemplate restTemplate = new RestTemplate();
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        HttpEntity requestEntity = new HttpEntity(param, headers);
        ResponseEntity<byte[]> entity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, byte[].class, new Object[0]);
        byte[] result = entity.getBody();
        return result;
    }

    /**
     * 獲得目前通路的URL路徑
     * @param request
     * @return
     */
    public static String getLocation(HttpServletRequest request) {
        UrlPathHelper helper = new UrlPathHelper();
        StringBuffer buff = request.getRequestURL();
        String uri = request.getRequestURI();
        String origUri = helper.getOriginatingRequestUri(request);
        buff.replace(buff.length() - uri.length(), buff.length(), origUri);
        String queryString = helper.getOriginatingQueryString(request);
        if (queryString != null) {
            buff.append("?").append(queryString);
        }
        try {
            return new String(buff.toString().getBytes(), "iso-8859-1");
        } catch (UnsupportedEncodingException e) {
            return buff.toString();
        }
    }
}
           

controler互動

/**
     * 擷取wechatConfig資訊
     * @param url   通路頁面的位址
     * @return
     */
    @GetMapping("/getWechatConfig")
    public Map<String,String> getWechatConfig(String url){
        return wechatUtil.wxConfig(url);
    }
           

js

//将url 存進緩存下
var url = "https://" + window.location.host;
var urlCureent = encodeURIComponent(location.href);
$.ajax({
                async:true,
                dataType:"json",
                type:"GET",
                url: url + "/verification/getWechatConfig?url=" + urlCureent,
                contentType: "application/json; charset=utf-8",
                success:function(data){
                    wx.config({
                        debug: 0,
                        appId: data.appId,
                        timestamp: data.timestamp,
                        nonceStr: data.nonceStr,
                        signature: data.signature,
                        jsApiList: ["updateAppMessageShareData", "updateTimelineShareData","onMenuShareTimeline","onMenuShareAppMessage"]
                    });
                    wx.ready(function () {   //需在使用者可能點選分享按鈕前就先調用
                        wx.updateAppMessageShareData({
                            title: cardVo.cardName + "個人名片", // 分享标題
                            desc: cardVo.cardNameEn + "'s card", // 分享描述
                            link: location.href, // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
                            imgUrl: url + cardVo.cardImg, // 分享圖示
                            success: function () {
                                console.log("success");// 設定成功
                            }
                        });

                        wx.updateTimelineShareData({
                            title: cardVo.cardName + "個人名片", // 分享标題
                            link: location.href, // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
                            imgUrl: url + cardVo.cardImg, // 分享圖示
                            success: function () {
                                console.log("success2");// 設定成功
                            }
                        });

                        wx.onMenuShareTimeline({
                            title: cardVo.cardName + "個人名片", // 分享标題
                            desc: cardVo.cardNameEn + "'s card", // 分享描述
                            link: location.href, // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
                            imgUrl: url + cardVo.cardImg, // 分享圖示
                        });

                        wx.onMenuShareAppMessage({
                            title: cardVo.cardName + "個人名片", // 分享标題
                            link: location.href, // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
                            imgUrl: url + cardVo.cardImg, // 分享圖示
                        });
                    });
                }
            });
           

常見問題

擷取JSSDK權限成功但分享無效果

官方建議使用wx.updateAppMessageShareData、wx.updateTimelineShareData,但是我當時無效果,再加上舊版本接口wx.onMenuShareTimeline、wx.onMenuShareAppMessage就可以了。

本地調試無效果

本地調試需要ngrok等外網穿透再做白名單

微信頁面緩存

微信緩存機制,在js後面加個版本号即可。

<script type="text/javascript">
    var js = document.getElementById('cardJs');
    js.src = './gzdm/card.js?v='+ new Date().getTime();

    var css = document.getElementById('cardCss');
    css.href = './css/card.css?v='+ new Date().getTime();
</script>