最近做一个小游戏,需要使用微信分享,经查询,无法直接在网页中直接添加分享按钮进行添加,需调用微信接口定制微信的分享按钮,具体步骤详见微信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 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 test(HttpServletRequest requesturl) throws Exception {
String ticket = weiXinRequest.getWeiXinTicket();
// 注意 URL 一定要动态获取,不能 hardcode
String url = requesturl.getRequestURL().toString();
Map 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 sign(String jsapi_ticket, String url) {
Map ret = new HashMap();
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> 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;
}