微信分享功能开发

一、服务器端程序

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
 
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微信开发教程汇总》欢迎大家学习阅读。