自定義分享并非所有公衆号都支援,必須是通過微信認證的。可在公衆号登陸後,開發->接口權限檢視是否擁有分享接口權限。
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 + "×tamp=" + 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. 分享效果