一、服务器端程序
package
com.wiimedia.controller;
import
java.io.IOException;
import
java.security.MessageDigest;
import
java.security.NoSuchAlgorithmException;
import
java.text.ParseException;
import
java.text.SimpleDateFormat;
import
java.util.Arrays;
import
java.util.Date;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Controller;
import
org.springframework.web.bind.annotation.RequestMapping;
import
com.google.gson.Gson;
import
com.wiimedia.model.Ticket;
import
com.wiimedia.service.ArticleSolrService;
import
com.wiimedia.service.TicketRepository;
import
com.wiimedia.service.TicketRepositorySolr;
import
com.wiimedia.utils.GetRandomStr;
import
com.wiimedia.utils.SignatureBean;
import
com.wiimedia.utils.weixin.WeixinUtil;
/**
*
*
*<p>Project:mryl_phone_v2</p>
*
*<p>Package:com.wiimedia.controller</p>
*
*<p>Description:微信分享Controller</p>
*
*<p>Company:Wiimedia</p>
*
*@Athor:SongJia
*
*@Date:2016-7-15 上午09:34:10
*
*/
@Controller
@RequestMapping
(
"/WeixinshareController/Api/Inteface"
)
public
class
WeixinshareController {
@Autowired
private
TicketRepositorySolr ticketRepositorySolr;
@RequestMapping
(
"/getSignature"
)
public
String getSignature( HttpServletRequest request,
HttpServletResponse response)
throws
IOException, ParseException{
//获取签名页面链接
String url = request.getParameter(
"url"
);
SimpleDateFormat format =
new
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"
);
//从数据库中获取标签,并检查标签是否过期
Ticket oldticket = ticketRepositorySolr.getTicketById(
"20160114wiimediamrylsong1152"
);
if
(oldticket==
null
){
//第一次访问,标签不存在。
executeTicket(response,
"1"
,url,format);
return
null
;
}
else
{
//标签存在,判断标签是否超时
String oldAcquiretime = oldticket.getAcquiretime();
long
difference=format.parse(format.format(
new
Date())).getTime()-format.parse(oldAcquiretime).getTime();
if
(difference>
7100000
){
//标签超时,重新到微信服务器请求标签超时时间为7200秒(7200000毫秒)
executeTicket(response,
"2"
,url,format);
return
null
;
}
else
{
//标签未超时
/**
* 注意事项
* 1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
* 2.签名用的url必须是调用JS接口页面的完整URL。
* 3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。
*
****根据第1点要求 signature 配置的时候很容易出错,需要把生成 Ticket的 noncestr和 timestamp传给客户端***
*/
String signature = signature(oldticket.getTicket(),oldticket.getTimestamp(),oldticket.getNoncestr(),url);
SignatureBean signatureBean =
new
SignatureBean();
signatureBean.setNoncestr(oldticket.getNoncestr());
signatureBean.setSignature(signature);
signatureBean.setTimestamp(oldticket.getTimestamp());
signatureBean.setUrl(url);
response.setContentType(
"text/html;charset=UTF-8"
);
response.getWriter().print(
new
Gson().toJson(signatureBean));
return
null
;
}
}
}
/**
*
*<p>Project:mryl_phone_v2</p>
*
*<p>:mryl_phone_v2</p>
*
*<p>Description:更新和获取ticket的方法,因为用的solr所以更新和新增是一样的ID无则添加,有责更新</p>
*
*<p>Company:Wiimedia</p>
*
*@Athor:SongJia
*
*@Date:2016-7-15 上午09:45:00
*
*/
public
void
executeTicket(HttpServletResponse response,String flag,String url,SimpleDateFormat format)
throws
IOException{
//获取签名随即字符串
GetRandomStr randomStr =
new
GetRandomStr();
String noncestr = randomStr.getRandomString(
15
);
//获取签名时间戳
String timestamp = Long.toString(System.currentTimeMillis());
//请求accessToken
String accessTokenUrl =
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=您的APPID&secret=您的密匙"
;
String tokenJson = WeixinUtil.httpRequest(accessTokenUrl,
"GET"
,
null
);
Gson gson =
new
Gson();
ShareAccess_Token token = gson.fromJson(tokenJson, ShareAccess_Token.
class
);
String to= token.getAccess_token();
//获取标签
String urlTicket =
"https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="
+to+
"&type=jsapi"
;
String ticketJson = WeixinUtil.httpRequest(urlTicket,
"GET"
,
null
);
Ticket ticket = gson.fromJson(ticketJson, Ticket.
class
);
String t = ticket.getTicket();
//String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
//我的Ticket ID是写死的
String acquiretime = format.format(
new
Date());
ticket.setTid(
"20160114wiimediamrylsong1152"
);
ticket.setAcquiretime(acquiretime);
ticket.setTimestamp(timestamp);
ticket.setNoncestr(noncestr);
//因为用的SOLR所以更新和添加的方法是一样的,可以根据自己具体需求进行修改,本文不再贴出代码.
if
(flag.equals(
"2"
)){
ticketRepositorySolr.addTicketToSolr(ticket);
}
else
{
ticketRepositorySolr.addTicketToSolr(ticket);
}
/**
* 注意事项
* 1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
* 2.签名用的url必须是调用JS接口页面的完整URL。
* 3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。
*
*根据第1点要求 signature 配置的时候很容易出错,需要把生成 Ticket的 noncestr和 timestamp传给客户端*
*/
String signature = signature(t,timestamp,noncestr,url);
SignatureBean signatureBean =
new
SignatureBean();
signatureBean.setNoncestr(noncestr);
signatureBean.setSignature(signature);
signatureBean.setTimestamp(timestamp);
signatureBean.setUrl(url);
response.setContentType(
"text/html;charset=UTF-8"
);
response.getWriter().print(
new
Gson().toJson(signatureBean));
}
/**
*
*<p>Project:mryl_phone_v2</p>
*
*<p>:mryl_phone_v2</p>
*
*<p>Description:根据标签,时间戳,密匙,URL进行签名</p>
*
*<p>Company:Wiimedia</p>
*
*@Athor:SongJia
*
*@Date:2016-7-15 上午09:37:13
*
*/
private
String signature(String jsapi_ticket, String timestamp, String noncestr, String url) {
jsapi_ticket =
"jsapi_ticket="
+ jsapi_ticket;
timestamp =
"timestamp="
+ timestamp;
noncestr =
"noncestr="
+ noncestr;
url =
"url="
+ url;
String[] arr =
new
String[] { jsapi_ticket, timestamp, noncestr, url };
// 将token、timestamp、nonce,url参数进行字典序排序
Arrays.sort(arr);
StringBuilder content =
new
StringBuilder();
for
(
int
i =
0
; i < arr.length; i++) {
content.append(arr[i]);
if
(i != arr.length -
1
) {
content.append(
"&"
);
}
}
MessageDigest md =
null
;
String tmpStr =
null
;
try
{
md = MessageDigest.getInstance(
"SHA-1"
);
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte
[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
}
catch
(NoSuchAlgorithmException e) {
e.printStackTrace();
}
content =
null
;
return
tmpStr;
}
/**
* 将字节转换为十六进制字符串
*
* @param mByte
* @return
*/
private
static
String byteToHexStr(
byte
mByte) {
char
[] Digit = {
'0'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'A'
,
'B'
,
'C'
,
'D'
,
'E'
,
'F'
};
char
[] tempArr =
new
char
[
2
];
tempArr[
0
] = Digit[(mByte >>>
4
) &
0X0F
];
tempArr[
1
] = Digit[mByte &
0X0F
];
String s =
new
String(tempArr);
return
s;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param byteArray
* @return
*/
private
static
String byteToStr(
byte
[] byteArray) {
String strDigest =
""
;
for
(
int
i =
0
; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return
strDigest;
}
class
ShareAccess_Token{
private
String access_token;
private
String expires_in;
public
String getAccess_token() {
return
access_token;
}
public
void
setAccess_token(String accessToken) {
access_token = accessToken;
}
public
String getExpires_in() {
return
expires_in;
}
public
void
setExpires_in(String expiresIn) {
expires_in = expiresIn;
}
}
}
二、客户端代码.
<script type=
"text/javascript"
>
var
url = window.location.href;
var
articleId =
""
;
var
shareTitle=
"明日医疗资讯"
;
var
shareImgUrl=
""
;
var
userinfo = localStorage.getItem(
"_userinfo"
);
var
timestamp;
var
noncestr;
var
signature;
//获取签名
$.ajax({
type:
"GET"
,
url:
"WeixinshareController/Api/Inteface/getSignature"
,
//data:{timestamp:timestamp,noncestr:noncestr,url:url},
data:{url:url},
success:
function
(data){
var
objData=JSON.parse(data);
timestamp=objData.timestamp;
noncestr=objData.noncestr;
signature=objData.signature;
console.log(objData);
wxShare();
}
});
function
wxShare(){
wx.config({
debug:
false
,
// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId:
'您的appid'
,
// 和获取Ticke的必须一样------必填,公众号的唯一标识
timestamp:timestamp,
// 必填,生成签名的时间戳
nonceStr: noncestr,
// 必填,生成签名的随机串
signature: signature,
// 必填,签名,见附录1
jsApiList: [
'onMenuShareAppMessage'
]
// 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
}
wx.ready(
function
(){
//config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,
//config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关
//接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
//----------“分享给朋友”
wx.onMenuShareAppMessage({
title:
"明日医疗资讯"
,
// 分享标题
desc: shareTitle,
// 分享描述
link: url,
// 分享链接
imgUrl: shareImgUrl,
// 分享图标
type:
''
,
// 分享类型,music、video或link,不填默认为link
dataUrl:
''
,
// 如果type是music或video,则要提供数据链接,默认为空
success:
function
() {
// 用户确认分享后执行的回调函数、
},
cancel:
function
() {
// 用户取消分享后执行的回调函数
}
});
//------------"分享到朋友圈"
wx.onMenuShareTimeline({
title:
'明日医疗资讯'
,
// 分享标题
link:
''
,
// 分享链接
imgUrl: shareImgUrl,
// 分享图标
success:
function
() {
// 用户确认分享后执行的回调函数
},
cancel:
function
() {
// 用户取消分享后执行的回调函数
}
});
//-------------分享到QQ
wx.onMenuShareQQ({
title:
'明日医疗资讯'
,
// 分享标题
desc: shareTitle,
// 分享描述
link:
''
,
// 分享链接
imgUrl: shareImgUrl,
// 分享图标
success:
function
() {
// 用户确认分享后执行的回调函数
},
cancel:
function
() {
// 用户取消分享后执行的回调函数
}
});
//-------------分享到QQ空间
wx.onMenuShareQZone({
title:
'明日医疗资讯'
,
// 分享标题
desc: shareTitle,
// 分享描述
link:
''
,
// 分享链接
imgUrl: shareImgUrl,
// 分享图标
success:
function
() {
// 用户确认分享后执行的回调函数
},
cancel:
function
() {
// 用户取消分享后执行的回调函数
}
});
});
三、服务器需要的工具类和Model
package
com.wiimedia.model;
public
class
Ticket{
private
String tid;
private
String ticket;
private
String errcode;
private
String errmsg;
private
String expires_in;
private
String acquiretime;
private
String noncestr;
private
String timestamp;
public
Ticket(String tid, String ticket, String errcode, String errmsg,
String expiresIn, String acquiretime, String noncestr,
String timestamp) {
super
();
this
.tid = tid;
this
.ticket = ticket;
this
.errcode = errcode;
this
.errmsg = errmsg;
expires_in = expiresIn;
this
.acquiretime = acquiretime;
this
.noncestr = noncestr;
this
.timestamp = timestamp;
}
public
String getTid() {
return
tid;
}
public
void
setTid(String tid) {
this
.tid = tid;
}
public
String getTicket() {
return
ticket;
}
public
void
setTicket(String ticket) {
this
.ticket = ticket;
}
public
String getErrcode() {
return
errcode;
}
public
void
setErrcode(String errcode) {
this
.errcode = errcode;
}
public
String getErrmsg() {
return
errmsg;
}
public
void
setErrmsg(String errmsg) {
this
.errmsg = errmsg;
}
public
String getExpires_in() {
return
expires_in;
}
public
void
setExpires_in(String expiresIn) {
expires_in = expiresIn;
}
public
String getAcquiretime() {
return
acquiretime;
}
public
void
setAcquiretime(String acquiretime) {
this
.acquiretime = acquiretime;
}
public
String getNoncestr() {
return
noncestr;
}
public
void
setNoncestr(String noncestr) {
this
.noncestr = noncestr;
}
public
String getTimestamp() {
return
timestamp;
}
public
void
setTimestamp(String timestamp) {
this
.timestamp = timestamp;
}
}
② 添加到数据库的业务根据自己需要进行实现.
③ GetRandomStr
③ GetRandomStr
package
com.wiimedia.utils;
import
java.util.Random;
public
class
GetRandomStr {
/**
*
*<p>Project:mryl_phone_v2</p>
*
*<p>:mryl_phone_v2</p>
*
*<p>Description:生成随即字符串 </p>
*
*<p>Company:Wiimedia</p>
*
*@Athor:SongJia
*
*@Date:2016-7-14 上午11:14:46
*
*/
public
String getRandomString(
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();
}
}
④ SignatureBean
package
com.wiimedia.utils;
public
class
SignatureBean {
private
String noncestr;
private
String url;
private
String timestamp;
private
String signature;
public
String getNoncestr() {
return
noncestr;
}
public
void
setNoncestr(String noncestr) {
this
.noncestr = noncestr;
}
public
String getUrl() {
return
url;
}
public
void
setUrl(String url) {
this
.url = url;
}
public
String getTimestamp() {
return
timestamp;
}
public
void
setTimestamp(String timestamp) {
this
.timestamp = timestamp;
}
public
String getSignature() {
return
signature;
}
public
void
setSignature(String signature) {
this
.signature = signature;
}
}
⑤ WeixinUtil
package
com.wiimedia.utils.weixin;
import
java.io.BufferedReader;
import
java.io.InputStream;
import
java.io.InputStreamReader;
import
java.io.OutputStream;
import
java.net.ConnectException;
import
java.net.URL;
import
javax.net.ssl.HttpsURLConnection;
import
javax.net.ssl.SSLContext;
import
javax.net.ssl.SSLSocketFactory;
import
javax.net.ssl.TrustManager;
/**
*
*<p>Project:mryl_phone_v2</p>
*
*<p>:mryl_phone_v2</p>
*
*<p>Description:公众平台接口工具类</p>
*
*<p>Company:Wiimedia</p>
*
*@Athor:SongJia
*
*@Date:2016-7-15 上午09:37:13
*
*/
public
class
WeixinUtil {
/**
* 发起https请求并获取结果
*
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
*/
public
static
String httpRequest(String requestUrl, String requestMethod, String outputStr) {
StringBuffer buffer =
new
StringBuffer();
try
{
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = {
new
MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance(
"SSL"
,
"SunJSSE"
);
sslContext.init(
null
, tm,
new
java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url =
new
URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(
true
);
httpUrlConn.setDoInput(
true
);
httpUrlConn.setUseCaches(
false
);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
if
(
"GET"
.equalsIgnoreCase(requestMethod))
httpUrlConn.connect();
// 当有数据需要提交时
if
(
null
!= outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes(
"UTF-8"
));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader =
new
InputStreamReader(inputStream,
"utf-8"
);
BufferedReader bufferedReader =
new
BufferedReader(inputStreamReader);
String str =
null
;
while
((str = bufferedReader.readLine()) !=
null
) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream =
null
;
httpUrlConn.disconnect();
return
buffer.toString();
}
catch
(ConnectException ce) {
ce.printStackTrace();
}
catch
(Exception e) {
e.printStackTrace();
}
return
""
;
}
}
四、至此,分享功能已经开发完成,但是,在生成signature的时候会遇到很多问题,这里提供一些wx.config失败的排错方法。
① 确认自己的生成的signature是否正确
在微信提供的http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign进行校验
② wx.config中使用的noncestr, timestamp与用以签名中的对应noncestr, timestamp是否一致一致…如上面(一.服务器代码)
(有可能因为JS页面加载顺序问题,服务器生成的signature,noncestr,timestamp在wx.config中没有获取到)。
③ 确认url是页面完整的url,包括GET参数部分
需要去掉#后面的部分
④ config 中的 appid 与用来获取 jsapi_ticket 的 appid 是否一致
⑤ 报错{errmsg:config:ok}是debug的正常返回把调试模式关掉就OK
wx.config debug: false,
本文已被整理到了《Android微信开发教程汇总》,《java微信开发教程汇总》欢迎大家学习阅读。