天天看點

微信網頁開發-分享功能自定義

自定義分享并非所有公衆号都支援,必須是通過微信認證的。可在公衆号登陸後,開發->接口權限檢視是否擁有分享接口權限。

1. 微信公衆号配置

#  JS接口安全域名配置

此處必須填寫備案過的域名或路徑,不能填寫IP位址。

微信網頁開發-分享功能自定義

#  IP白名單配置

實作自定義分享需要擷取access_token,而白名單之内的IP才能進行擷取,此處配置的是IP位址。

微信網頁開發-分享功能自定義

2.  HTML頁面

HTML頁面調用JSSDK進行功能實作。

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	</head>
	<body>
		自定義内容!
	</body>	
	<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
	<script type="text/javascript" src="./js/jquery-1.8.3.min.js"></script>	
	<script>
		window.onload=function(){
			$.ajax({
				url: "此處填寫擷取簽名的url",
				type: "GET",
				dataType: "json",
				data: {"url":location.href.split('#')[0]},
				success: function(data) {
					wx.config({
						debug: false,
						appId: data.dataMap.appid, //appId
						timestamp: data.dataMap.timestamp, //生成簽名的時間戳
						nonceStr: data.dataMap.noncestr, //生成簽名的随機串
						signature: data.dataMap.signature, //簽名
						jsApiList: ['onMenuShareAppMessage', 'onMenuShareTimeline'] //需要使用的JS接口清單

					});
					wx.ready(function() {
						//判斷目前用戶端版本是否支援指定JS接口
						wx.checkJsApi({
							jsApiList: [
								'onMenuShareAppMessage', 'onMenuShareTimeline'
							],
							success: function(res) {							
								
							}
						});
						//分享給朋友
						wx.onMenuShareAppMessage({ 
							title: '家的方向,更是心的歸向', // 分享标題
							desc: '年的味道,近鄉愈濃', // 分享描述
							link: 'xxx.xxxx.com/xxx/test.html', // 分享連結,該連結域名或路徑必須與公衆号JS安全域名一緻
							imgUrl: 'xxx.xxxx.com/xxx/test.jpg', // 分享圖示
							success: function () {
							  // 設定成功
							  
							}
							
						}),
						//分享到朋友圈
						wx.onMenuShareTimeline({ 
							title: '家的方向,更是心的歸向', // 分享标題
							desc: '年的味道,近鄉愈濃', // 分享描述
							link: 'xxx.xxxx.com/xxx/test.html', // 分享連結,該連結域名或路徑必須與目前頁面對應的公衆号JS安全域名一緻
							imgUrl: 'xxx.xxxx.com/xxx/test.jpg', // 分享圖示
							success: function () {
							  // 設定成功
							  
							}
						})
					});
					wx.error(function(res){
						//驗證失敗
						console.log("驗證失敗!"+res);
					});
				},
				error: function() {
					console.log("簽名請求失敗!");
				}
			})
		}

	</script>
</html>
           

3.  Java服務端實作

#  Action服務類,接受HTML頁面請求,傳回簽名。

package com.tingyu.action;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.opensymphony.xwork2.ActionContext;
import com.tingyu.util.BaseUtil;
import com.tingyu.util.ConvertUtil;
import com.tingyu.util.weixin.RandomStringGenerator;
import com.tingyu.util.weixin.SHA1;
import com.tingyu.util.weixin.Token;
import com.tingyu.util.weixin.WXConfig;

@Component(value = "wxSignatureAction")
@Scope(value = "prototype")
public class WXSignatureAction extends BaseAction {

	/**
	 * @type: long
	 * @description:
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * @description:擷取微信簽名
	 * @return
	 * @author: tingyu
	 * @date: 2019年3月6日下午5:38:27
	 * @modify:
	 */
	public String getSignature() {
		dataMap = new HashMap<String, Object>();
		try {
			Map<String, Object> map = ActionContext.getContext().getParameters();
			Map<String, Object> paraMap = BaseUtil.decodeMap(map);
			String url = ConvertUtil.convertToString(paraMap.get("url"));
			String ticket = Token.getTicket();
			String noncestr = RandomStringGenerator.getRandomStringByLength(32);
			long timestamp = new Date().getTime() / 1000;
			String sign = "jsapi_ticket=" + ticket + "&noncestr="// 請勿更換字元組裝順序
					+ noncestr + "&timestamp=" + timestamp + "&url=" + url;
			String signature = new SHA1().getDigestOfString(sign.getBytes("utf-8"));
			dataMap.put("url", url);
			dataMap.put("ticket", ticket);
			dataMap.put("appid", WXConfig.appid);
			dataMap.put("timestamp", ConvertUtil.convertToString(timestamp));
			dataMap.put("noncestr", noncestr);
			dataMap.put("signature", signature);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return SUCCESSJSON;
	}

}
           

#  Token工具類,擷取Token和Ticket,過期重新申請。

package com.tingyu.util.weixin;

import net.sf.json.JSONObject;

public class Token {

	private static String access_token = "";

	private static String jsapi_ticket = "";

	public static int time = 0;

	private static int expires_in = 7200;

	static {
		Thread t = new Thread(new Runnable() {
			public void run() {
				do {
					time++;
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} while (true);
			}
		});
		t.start();
	}

	public static String getToken() {
		if ("".equals(access_token) || access_token == null) {
			send();
		} else if (time > expires_in) {
			// 目前token已經失效,從新擷取資訊
			send();
		}
		return access_token;
	}

	public static String getTicket() {
		if ("".equals(jsapi_ticket) || jsapi_ticket == null) {
			send();
		} else if (time > expires_in) {
			// 目前token已經失效,從新擷取資訊
			send();
		}
		return jsapi_ticket;
	}

	private static void send() {
		String url = WXConfig.server_token_url + "&appid=" + WXConfig.appid + "&secret=" + WXConfig.appsecret;
		JSONObject json = HttpRequest.sendGet(url);
		access_token = json.getString("access_token");
		String ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token
				+ "&type=jsapi";
		jsapi_ticket = HttpRequest.sendGet(ticket_url).getString("ticket");
		time = 0;
	}
}
           

#  Http請求類,發送HTTP請求。

package com.tingyu.util.weixin;

import java.io.IOException;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import net.sf.json.JSONObject;

public class HttpRequest {

    /**
     * 
     * @param url
     * @param jsonParam
     * @return
     */
    public static JSONObject sendGet(String url){
        
        CloseableHttpClient httpclient = HttpClients.createDefault();
        JSONObject jsonResult = null;
        HttpGet method = new HttpGet(url);
        try {
            CloseableHttpResponse result = httpclient.execute(method);
            if (result.getStatusLine().getStatusCode() == 200) {
                String str = "";
                try {
                    str = EntityUtils.toString(result.getEntity());
                    jsonResult = JSONObject.fromObject(str);
                } catch (Exception e) {
                    System.out.println("get請求送出失敗:" + url);
                }
            }
        } catch (IOException e) {
            System.out.println("get請求送出失敗:" + url);
        }
        return jsonResult;
    }
}
           

#  随機串生成類

package com.tingyu.util.weixin;

import java.util.Random;

public class RandomStringGenerator {
	
	/**
     * 擷取一定長度的随機字元串
     * @param length 指定字元串長度
     * @return 一定長度的字元串
     */
    public static String getRandomStringByLength(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }
}
           

#  微信配置參數類

package com.tingyu.util.weixin;

public class WXConfig {

    public static String appid = "xxxxxxxxxxxxx"; //替換為自己的appid
    
    public static String appsecret = "xxxxxxxxxx";  //替換為自己的appsecret
    
    public static String access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token";
    
    public static String oauth_url = "https://open.weixin.qq.com/connect/oauth2/authorize";
    
    public static String server_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
}
           

#  簽名生成算法類

package com.tingyu.util.weixin;

public class SHA1 {
	private final int[] abcde = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 };
	// 摘要資料存儲數組
	private int[] digestInt = new int[5];
	// 計算過程中的臨時資料存儲數組
	private int[] tmpData = new int[80];

	// 計算sha-1摘要
	private int process_input_bytes(byte[] bytedata) {
		// 初試化常量
		System.arraycopy(abcde, 0, digestInt, 0, abcde.length);
		// 格式化輸入位元組數組,補10及長度資料
		byte[] newbyte = byteArrayFormatData(bytedata);
		// 擷取資料摘要計算的資料單元個數
		int MCount = newbyte.length / 64;
		// 循環對每個資料單元進行摘要計算
		for (int pos = 0; pos < MCount; pos++) {
			// 将每個單元的資料轉換成16個整型資料,并儲存到tmpData的前16個數組元素中
			for (int j = 0; j < 16; j++) {
				tmpData[j] = byteArrayToInt(newbyte, (pos * 64) + (j * 4));
			}
			// 摘要計算函數
			encrypt();
		}
		return 20;
	}

	// 格式化輸入位元組數組格式
	private byte[] byteArrayFormatData(byte[] bytedata) {
		// 補0數量
		int zeros = 0;
		// 補位後總位數
		int size = 0;
		// 原始資料長度
		int n = bytedata.length;
		// 模64後的剩餘位數
		int m = n % 64;
		// 計算添加0的個數以及添加10後的總長度
		if (m < 56) {
			zeros = 55 - m;
			size = n - m + 64;
		} else if (m == 56) {
			zeros = 63;
			size = n + 8 + 64;
		} else {
			zeros = 63 - m + 56;
			size = (n + 64) - m + 64;
		}
		// 補位後生成的新數組内容
		byte[] newbyte = new byte[size];
		// 複制數組的前面部分
		System.arraycopy(bytedata, 0, newbyte, 0, n);
		// 獲得數組Append資料元素的位置
		int l = n;
		// 補1操作
		newbyte[l++] = (byte) 0x80;
		// 補0操作
		for (int i = 0; i < zeros; i++) {
			newbyte[l++] = (byte) 0x00;
		}
		// 計算資料長度,補資料長度位共8位元組,長整型
		long N = (long) n * 8;
		byte h8 = (byte) (N & 0xFF);
		byte h7 = (byte) ((N >> 8) & 0xFF);
		byte h6 = (byte) ((N >> 16) & 0xFF);
		byte h5 = (byte) ((N >> 24) & 0xFF);
		byte h4 = (byte) ((N >> 32) & 0xFF);
		byte h3 = (byte) ((N >> 40) & 0xFF);
		byte h2 = (byte) ((N >> 48) & 0xFF);
		byte h1 = (byte) (N >> 56);
		newbyte[l++] = h1;
		newbyte[l++] = h2;
		newbyte[l++] = h3;
		newbyte[l++] = h4;
		newbyte[l++] = h5;
		newbyte[l++] = h6;
		newbyte[l++] = h7;
		newbyte[l++] = h8;
		return newbyte;
	}

	private int f1(int x, int y, int z) {
		return (x & y) | (~x & z);
	}

	private int f2(int x, int y, int z) {
		return x ^ y ^ z;
	}

	private int f3(int x, int y, int z) {
		return (x & y) | (x & z) | (y & z);
	}

	private int f4(int x, int y) {
		return (x << y) | x >>> (32 - y);
	}

	// 單元摘要計算函數
	private void encrypt() {
		for (int i = 16; i <= 79; i++) {
			tmpData[i] = f4(tmpData[i - 3] ^ tmpData[i - 8] ^ tmpData[i - 14] ^ tmpData[i - 16], 1);
		}
		int[] tmpabcde = new int[5];
		for (int i1 = 0; i1 < tmpabcde.length; i1++) {
			tmpabcde[i1] = digestInt[i1];
		}
		for (int j = 0; j <= 19; j++) {
			int tmp = f4(tmpabcde[0], 5) + f1(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[j]
					+ 0x5a827999;
			tmpabcde[4] = tmpabcde[3];
			tmpabcde[3] = tmpabcde[2];
			tmpabcde[2] = f4(tmpabcde[1], 30);
			tmpabcde[1] = tmpabcde[0];
			tmpabcde[0] = tmp;
		}
		for (int k = 20; k <= 39; k++) {
			int tmp = f4(tmpabcde[0], 5) + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[k]
					+ 0x6ed9eba1;
			tmpabcde[4] = tmpabcde[3];
			tmpabcde[3] = tmpabcde[2];
			tmpabcde[2] = f4(tmpabcde[1], 30);
			tmpabcde[1] = tmpabcde[0];
			tmpabcde[0] = tmp;
		}
		for (int l = 40; l <= 59; l++) {
			int tmp = f4(tmpabcde[0], 5) + f3(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[l]
					+ 0x8f1bbcdc;
			tmpabcde[4] = tmpabcde[3];
			tmpabcde[3] = tmpabcde[2];
			tmpabcde[2] = f4(tmpabcde[1], 30);
			tmpabcde[1] = tmpabcde[0];
			tmpabcde[0] = tmp;
		}
		for (int m = 60; m <= 79; m++) {
			int tmp = f4(tmpabcde[0], 5) + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[m]
					+ 0xca62c1d6;
			tmpabcde[4] = tmpabcde[3];
			tmpabcde[3] = tmpabcde[2];
			tmpabcde[2] = f4(tmpabcde[1], 30);
			tmpabcde[1] = tmpabcde[0];
			tmpabcde[0] = tmp;
		}
		for (int i2 = 0; i2 < tmpabcde.length; i2++) {
			digestInt[i2] = digestInt[i2] + tmpabcde[i2];
		}
		for (int n = 0; n < tmpData.length; n++) {
			tmpData[n] = 0;
		}
	}

	// 4位元組數組轉換為整數
	private int byteArrayToInt(byte[] bytedata, int i) {
		return ((bytedata[i] & 0xff) << 24) | ((bytedata[i + 1] & 0xff) << 16) | ((bytedata[i + 2] & 0xff) << 8)
				| (bytedata[i + 3] & 0xff);
	}

	// 整數轉換為4位元組數組
	private void intToByteArray(int intValue, byte[] byteData, int i) {
		byteData[i] = (byte) (intValue >>> 24);
		byteData[i + 1] = (byte) (intValue >>> 16);
		byteData[i + 2] = (byte) (intValue >>> 8);
		byteData[i + 3] = (byte) intValue;
	}

	// 将位元組轉換為十六進制字元串
	private static String byteToHexString(byte ib) {
		char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
		char[] ob = new char[2];
		ob[0] = Digit[(ib >>> 4) & 0X0F];
		ob[1] = Digit[ib & 0X0F];
		String s = new String(ob);
		return s;
	}

	// 将位元組數組轉換為十六進制字元串
	private static String byteArrayToHexString(byte[] bytearray) {
		String strDigest = "";
		for (int i = 0; i < bytearray.length; i++) {
			strDigest += byteToHexString(bytearray[i]);
		}
		return strDigest;
	}

	// 計算sha-1摘要,傳回相應的位元組數組
	public byte[] getDigestOfBytes(byte[] byteData) {
		process_input_bytes(byteData);
		byte[] digest = new byte[20];
		for (int i = 0; i < digestInt.length; i++) {
			intToByteArray(digestInt[i], digest, i * 4);
		}
		return digest;
	}

	// 計算sha-1摘要,傳回相應的十六進制字元串
	public String getDigestOfString(byte[] byteData) {
		return byteArrayToHexString(getDigestOfBytes(byteData));
	}

}
           

服務端的簽名生成算法在JSSDK文檔有說明,此處直接使用現成的。參考https://www.cnblogs.com/di8hao/p/5412708.html

4.  分享效果

微信網頁開發-分享功能自定義

繼續閱讀