最近做一個小遊戲,需要使用微信分享,經查詢,無法直接在網頁中直接添加分享按鈕進行添加,需調用微信接口定製微信的分享按鈕,具體步驟詳見微信JSSDK開發文檔,通過查找資料,實踐如下:
1.在微信公衆平臺(需通過認證)中,按照開發文檔步驟,添加js域名,因爲本文的地址端口不是默認80端口,因爲域名還需帶上端口號,不然會提示域名錯誤
2.頁面引入微信js文件:
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"></script>
3.根據需要,添加自己的js功能,完整頁面JS代碼如下:
<script type="text/javascript">
var id = '${id}';//服務端設置的id,用於下面拼接生成需要分享的link
var timestamp = parseInt('${ret.timestamp}');//因爲服務端是String類型,此處轉化成數值類型
var nonceStr = '${ret.nonceStr}';
var signature = '${ret.signature}';
wx.config({
debug: true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
appId: 'wxa034b7003154ee6c', // 必填,公衆號的唯一標識
timestamp: timestamp, // 必填,生成簽名的時間戳
nonceStr: nonceStr, // 必填,生成簽名的隨機串
signature: signature,// 必填,簽名,見附錄1
jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'] // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2
});
wx.ready(function(){
// config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
wx.onMenuShareTimeline({
title: 'xxxxxxxxxxxxx', // 分享標題
link: 'http://www.xxxxx.com:8082/xxx/xxx.do?id='+id, // 分享鏈接
imgUrl: 'http://www.xxxxx.com:8080/xxx/xxx.jpg', // 分享圖標
success: function () {
// 用戶確認分享後執行的回調函數
},
cancel: function () {
// 用戶取消分享後執行的回調函數
}
});
wx.onMenuShareAppMessage({
title: 'xxxxxxx', // 分享標題
desc: 'xxxxxxx', // 分享描述
link: 'http://www.xxxxx.com:8082/xxx/xxx.do?id='+id, // 分享鏈接
imgUrl: 'http://www.xxxxx.com:8080/xxx/xxx.jpg', // 分享圖標
type: '', // 分享類型,music、video或link,不填默認爲link
dataUrl: '', // 如果type是music或video,則要提供數據鏈接,默認爲空
success: function () {
// 用戶確認分享後執行的回調函數
},
cancel: function () {
// 用戶取消分享後執行的回調函數
}
});
});
</script>
頁面通過config進行初始化,config中需要提供timestamp、nonceStr、signature以及jsApiList,其中timestamp、nonceStr以及jsApiList都很容易添加,關鍵是signature簽名,經實踐,生成簽名步驟如下:
1. 獲取access_token, 因爲一般有效期是7200s,所以添加到了ehcache中,默認先從緩存中獲取,失效後再發送GET請求獲取:
Object act = EhcacheUtil.getInstance().get("weixin_jsapi", "access_token");
Object apiticket = EhcacheUtil.getInstance().get("weixin_jsapi", "ticket");
logger.debug("[act] = " + act + " [apiticket] = " + apiticket);
if (null == act) {
String url = "https://api.weixin.qq.com/cgi-bin/token";
String jsonStrToken = Tools.sendGet(url, "grant_type=client_credential&appid="+ appId + "&secret=" + appSecret);
logger.debug("[jsonStrToken] = " + jsonStrToken);
JSONObject json = JSONObject.fromObject(jsonStrToken);
access_token = (String) json.getString("access_token");
if (access_token == null) {
return null;
}
EhcacheUtil.getInstance().put("weixin_jsapi", "access_token", access_token);
} else {
access_token = (String) act;
}
2.根據上一步中的access_token獲取jsapi_ticket:
if (null == apiticket) {
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
String jsonStrTicket = Tools.sendGet(url, "access_token=" + access_token + "&type=jsapi");
logger.debug("[jsonStrTicket] = " + jsonStrTicket);
JSONObject json = JSONObject.fromObject(jsonStrTicket);
ticket = (String) json.get("ticket");
} else {
ticket = (String) apiticket;
}
3.獲取當前需要分享的網頁的URL:
//獲取URL
String url = request.getRequestURL().toString();
4.最終生成簽名:
(1)獲取時間戳:
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
(2)獲取隨機字符串:
private static String create_nonce_str() {
return UUID.randomUUID().toString();
}
(3)將jsapi_ticket、noncestr、timestamp、url拼接,使用SHA1加密算法,最終生成簽名
// 注意這裏參數名必須全部小寫,且必須有序
string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str
+ "×tamp=" + timestamp + "&url=" + url;
logger.debug("[string1] = " + string1);
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
logger.debug("[signature] = " + signature);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
5.將服務端生成的noncestr、timestamp、signature設置到model(使用的是spring mvc)中,返回到頁面:
Map<String, String> ret = sign.sign(jsapi_ticket, url);
mav.addObject("ret", ret);
var timestamp = parseInt('${ret.timestamp}');
var nonceStr = '${ret.nonceStr}';
var signature = '${ret.signature}';
wx.config({
debug: true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
appId: 'wxa034b7003154ee6c', // 必填,公衆號的唯一標識
timestamp: timestamp, // 必填,生成簽名的時間戳
nonceStr: nonceStr, // 必填,生成簽名的隨機串
signature: signature,// 必填,簽名,見附錄1
jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'] // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2
});
完整服務端代碼如下:
1.請求action:
//獲取ticket
String jsapi_ticket = weiXinRequest.getWeiXinTicket();
logger.debug("[jsapi_ticket] = " + jsapi_ticket);
//獲取URL
String url = request.getRequestURL().toString();
Map<String, String> ret = sign.sign(jsapi_ticket, url);
mav.addObject("ret", ret);
logger.debug("[ret] = " + ret);
return mav;
2.weixinrequest類:
import net.sf.json.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
@Component("WeiXinRequest")
public class WeiXinRequest {
private String appId = "wxa034b7003154ee6c";
private String appSecret = "7fce4f1ee0f63b62f20fe8321a31dea8";
private Log logger = LogFactory.getLog(WeiXinRequest.class);
public String getWeiXinTicket() throws Exception {
String access_token = "";
String ticket = "";
Object act = EhcacheUtil.getInstance().get("weixin_jsapi", "access_token");
Object apiticket = EhcacheUtil.getInstance().get("weixin_jsapi", "ticket");
logger.debug("[act] = " + act + " [apiticket] = " + apiticket);
if (null == act) {
String url = "https://api.weixin.qq.com/cgi-bin/token";
String jsonStrToken = Tools.sendGet(url, "grant_type=client_credential&appid="+ appId + "&secret=" + appSecret);
logger.debug("[jsonStrToken] = " + jsonStrToken);
JSONObject json = JSONObject.fromObject(jsonStrToken);
access_token = (String) json.getString("access_token");
if (access_token == null) {
return null;
}
EhcacheUtil.getInstance().put("weixin_jsapi", "access_token", access_token);
} else {
access_token = (String) act;
}
if (null == apiticket) {
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
String jsonStrTicket = Tools.sendGet(url, "access_token=" + access_token + "&type=jsapi");
logger.debug("[jsonStrTicket] = " + jsonStrTicket);
JSONObject json = JSONObject.fromObject(jsonStrTicket);
ticket = (String) json.get("ticket");
} else {
ticket = (String) apiticket;
}
return ticket;
// 斷開連接
}
}
3.Sign簽名類:
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("Sign")
public class Sign {
@Autowired
private WeiXinRequest weiXinRequest;
private Log logger = LogFactory.getLog(Sign.class);
public Map<String, String> test(HttpServletRequest requesturl) throws Exception {
String ticket = weiXinRequest.getWeiXinTicket();
// 注意 URL 一定要動態獲取,不能 hardcode
String url = requesturl.getRequestURL().toString();
Map<String, String> ret = sign(ticket, url);
for (Map.Entry entry : ret.entrySet()) {
System.out.println(entry.getKey() + ", " + entry.getValue());
}
// ret.put("appId", weiXinRequest.appId);
return ret;
};
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 string1;
String signature = "";
// 注意這裏參數名必須全部小寫,且必須有序
string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str
+ "×tamp=" + timestamp + "&url=" + url;
logger.debug("[string1] = " + string1);
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
logger.debug("[signature] = " + signature);
} 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);
logger.debug("[ret] = " + ret);
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;
}
private static String create_nonce_str() {
return UUID.randomUUID().toString();
}
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
/**
* 用SHA1算法生成安全簽名
*
* @param token
* 票據
* @param timestamp
* 時間戳
* @param nonce
* 隨機字符串
* @param encrypt
* 密文
* @return 安全簽名
* @throws NoSuchAlgorithmException
* @throws AesException
*/
public String getSHA1(String token, String timestamp, String nonce)
throws NoSuchAlgorithmException {
String[] array = new String[] { token, timestamp, nonce };
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < 3; i++) {
sb.append(array[i]);
}
String str = sb.toString();
// SHA1簽名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
}
}
4.java發送http請求的方法:
/**
* 向指定URL發送GET方法的請求
*
* @param url
* 發送請求的URL
* @param param
* 請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
* @return URL 所代表遠程資源的響應結果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打開和URL之間的連接
URLConnection connection = realUrl.openConnection();
// 設置通用的請求屬性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
// 建立實際的連接
connection.connect();
// 獲取所有響應頭字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍歷所有的響應頭字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定義 BufferedReader輸入流來讀取URL的響應
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("發送GET請求出現異常!" + e);
e.printStackTrace();
}
// 使用finally塊來關閉輸入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
* 向指定 URL 發送POST方法的請求
*
* @param url
* 發送請求的 URL
* @param param
* 請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
* @return 所代表遠程資源的響應結果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打開和URL之間的連接
URLConnection conn = realUrl.openConnection();
// 設置通用的請求屬性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 發送POST請求必須設置如下兩行
conn.setDoOutput(true);
conn.setDoInput(true);
// 獲取URLConnection對象對應的輸出流
out = new PrintWriter(conn.getOutputStream());
// 發送請求參數
out.print(param);
// flush輸出流的緩衝
out.flush();
// 定義BufferedReader輸入流來讀取URL的響應
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("發送 POST 請求出現異常!"+e);
e.printStackTrace();
}
//使用finally塊來關閉輸出流、輸入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}