上篇文章写到了公众号开发的基本配置(不明白的可以参考https://blog.youkuaiyun.com/TOP__ONE/article/details/78183209),这里继续写微信分享接口功能代码实现。
如果以下代码涉及到微信工具类方法,而我没有提到的,请到链接下载:https://download.youkuaiyun.com/download/top__one/10875681
实现微信页面的分享自定义接口功能,需要先配置js-sdk以下数据项,所以需要先获取这些数据项。
wx.config({
debug : false,
appId : appId,
timestamp : timestamp,
nonceStr : nonceStr,
signature : signature,
jsApiList : [ 'chooseWXPay', 'checkJsApi', 'closeWindow',
'onMenuShareTimeline', 'onMenuShareAppMessage' ]
});
1、后台接口程序开发
先对跳转分享页面action的基本编写,进行必要数据的封装
/**
* 分享页初始化
*
* @param map
* @return
*/
@RequestMapping(value = "/toshare")
public String index(Model model, @RequestParam Map<String, String> map) {
logger.info("进入分享主页");
// 为了获取调取微信的jsdk的方法,进行配置数据项的封装
Map<String, String> sign = getSingture(request);
model.addAttribute("sign", sign);
//此host为访问域名,即项目域名,要和js安全域名一致
String host = PropertieSingle.getInstance().getProperty("host");
model.addAttribute("host", host);
return "shareIndex";
}
/**
* 获取singture的公共方法
*
* @param request
* @return
*/
private static Map<String, String> getSingture(HttpServletRequest request) {
// response.setContentType("text/javascript;charset=utf-8");
// FiXME 通过接口获取access_token,再通过access_token获取jsapi_ticket
String wechatTicket = WechatServiceUtil.getWechatTicket();
String strBackUrl = "http://" + request.getServerName() // 服务器地址
// + ":"
// + request.getServerPort() //端口号
+ request.getContextPath() // 项目名称
+ request.getServletPath(); // 请求页面或其他地址
if (!StringUtils.isEmpty(request.getQueryString()))
strBackUrl += "?" + (request.getQueryString()); // 参数
Map<String, String> sign = Sign.sign(wechatTicket, strBackUrl);
sign.put("appId", PropertieSingle.getInstance().getProperty("APPID"));
return sign;
}
其中里面的Sign工具类代码
package com.chinatsp.wechat.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.UUID;
public class Sign {
public static void main(String[] args) {
String jsapi_ticket = "jsapi_ticket";
// 注意 URL 一定要动态获取,不能 hardcode
String url = "http://example.com";
Map<String, String> ret = sign(jsapi_ticket, url);
for (Map.Entry entry : ret.entrySet()) {
System.out.println(entry.getKey() + ", " + entry.getValue());
}
};
/**
* 这是jssdk中需要的那个singture签名
* @param jsapi_ticket
* @param url
* @return
*/
public static 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 = "";
System.out.println(nonce_str);
System.out.println(timestamp);
System.out.println(jsapi_ticket);
System.out.println(url);
//注意这里参数名必须全部小写,且必须有序
string1 = "jsapi_ticket=" + jsapi_ticket +
"&noncestr=" + nonce_str +
"×tamp=" + timestamp +
"&url=" + url;
System.out.println(string1);
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");//加密方法
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
}
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);
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;
}
public static String create_nonce_str() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
其中wechatTicket的获取代码如下,另附TicketAPI.
/**
*这里是用到了redis缓存,tiket的有效期为7200s,我设置redis过期时间为7000s
*tiket每天获取次数有限,所以尽量做一个缓存处理
*/
public static String getWechatTicket(){
//获取TICKET,从redis中
String ticket= RedisUtils.getCachRedisData(WechatBean.TICKET);
if(ticket==null){
//获取token
Ticket t= TicketAPI.ticketGetticket(getWechatToken());
RedisUtils.setCachRedisDataTimeLong(WechatBean.TICKET,Integer.parseInt(WechatBean.WECHAT_TOKEN_TIME+""),t.getTicket());
return t.getTicket();
}else{
return ticket;
}
}
另附TicketAPI.ticketGetticket方法代码
package weixin.popular.api;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import weixin.popular.bean.Ticket;
import weixin.popular.client.LocalHttpClient;
/**
* JSAPI ticket
* @author LiYi
*
*/
public class TicketAPI extends BaseAPI{
/**
* 获取 jsapi_ticket
* @param access_token
* @return
*/
public static Ticket ticketGetticket(String access_token){
HttpUriRequest httpUriRequest = RequestBuilder.post()
.setUri(BASE_URI + "/cgi-bin/ticket/getticket")
.addParameter("access_token",access_token)
.addParameter("type", "jsapi")
.build();
return LocalHttpClient.executeJsonResult(httpUriRequest,Ticket.class);
}
}
至此,后台获取token、ticket、sign等参数,然后跳转前台页面,进行页面数据配置
2、进行页面js-sdk数据配置,通过config接口注入权限验证配置
首先需要引用js: <script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
注:请注意,原有的 wx.onMenuShareTimeline、wx.onMenuShareAppMessage、wx.onMenuShareQQ、wx.onMenuShareQZone 接口,即将废弃。请尽快迁移使用客户端6.7.2及JSSDK 1.4.0以上版本支持的 wx.updateAppMessageShareData、updateTimelineShareData 接口。
我此处引用的是1.2.0版本js,此博文也是在1.2.0版本基础上进行的。后面后附上我试用1.4版本js的问题。
在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.4.0.js
如需进一步提升服务稳定性,当上述资源不可访问时,可改访问:http://res2.wx.qq.com/open/js/jweixin-1.4.0.js (支持https)。
备注:支持使用 AMD/CMD 标准模块加载方法加载
页面代码:
<script type="text/javascript">
$(document).ready(
function() {
var appId = $("#appId").val();
var timestamp = $("#timestamp").val();
var nonceStr = $("#nonceStr").val();
var signature = $("#signature").val();
wx.config({
debug : true,
appId : appId,
timestamp : timestamp,
nonceStr : nonceStr,
signature : signature,
jsApiList : [ 'chooseWXPay', 'checkJsApi', 'closeWindow',
'onMenuShareTimeline', 'onMenuShareAppMessage' ]
});
wx.error(function(res) {
//alert(JSON.stringify(res));
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
wx.checkJsApi({
jsApiList : [ 'checkJsApi', 'onMenuShareTimeline',
'onMenuShareAppMessage' ], // 需要检测的JS接口列表,所有JS接口列表见附录2,
success : function(res) {
//alert(res);
// 以键值对的形式返回,可用的api值true,不可用为false
// 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
}
});
});
wx.ready(function() {
var title = "分享朋友圈?";
var desc = "晒能力,抢大红包";
var link = "https://blog.youkuaiyun.com/TOP__ONE/article/details/78183209";
var imgUrl ="https://blog.youkuaiyun.com/TOP__ONE/share_logo.png";
wx.onMenuShareTimeline({
title : title, // 分享标题
link : link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl : imgUrl, // 分享图标
success : function() {
// 用户确认分享后执行的回调函数
$.ajax({
type: "POST",
async: false,
url: "",
success : function(msg) {
}
});
},
cancel : function() {
// 用户取消分享后执行的回调函数
//window.location.href='*.action';
}
});
wx.onMenuShareAppMessage({
title : title, // 分享标题
desc : desc, // 分享描述
link : link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl : imgUrl, // 分享图标
type : '', // 分享类型,music、video或link,不填默认为link
dataUrl : '', // 如果type是music或video,则要提供数据链接,默认为空
success : function() {
// 用户确认分享后执行的回调函数
$.ajax({
type: "POST",
async: false,
url: "",
success : function(msg) {
}
});
},
cancel : function() {
// 用户取消分享后执行的回调函数
}
});
});
</script>
此代码中appId、timestamp、nonceStr、signature均为后台封装的sign中的值,通过自己方法获取即可。
wx.config中的debug更改为true可查看配置情况。
至此,微信分享接口demo即开发完毕。更详细官方链接https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
--------------------------------------贞操 分割线---------------------------------------
在微信分享过程中也遇到了一些问题,比如
1、签名错误invalid signature,需要检查页面wx.config中的字段是否正确,需要检查js安全域名是否与项目域名一致。一般检查顺序如下:
(1)确认签名算法正确,可用 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 页面工具进行校验。
(2)确认config中nonceStr(js中驼峰标准大写S), timestamp与用以签名中的对应noncestr, timestamp一致。
(3)确认url是页面完整的url(请在当前页面alert(location.href.split('#')[0])确认),包括'http(s)://'部分,以及'?'后面的GET参数部分,但不包括'#'hash后面的部分。
(4)确认 config 中的 appid 与用来获取 jsapi_ticket 的 appid 一致。
(5)确保一定缓存access_token和jsapi_ticket。
(6)确保你获取用来签名的url是动态获取的,动态页面可参见实例代码中php的实现方式。如果是html的静态页面在前端通过ajax将url传到后台签名,前端需要用js获取当前页面除去'#'hash部分的链接(可用location.href.split('#')[0]获取,而且需要encodeURIComponent),因为页面一旦分享,微信客户端会在你的链接末尾加入其它参数,如果不是动态获取当前链接,将导致分享后的页面签名失败。
如果后台返回的config 信息 与微信签名工具校验一致的话,那很有可能是前端访问的url和后台生成签名的url不一致导致,看是否需要encodeURIComponent或者decode下。
2、安卓分享成功,ios分享不成功问题
(1) IOS分享的时候图片的链接地址 和 link 都需要在 JS安全域名下并且带http头。
(2)网上搜的资源还有说是 图片大小不能超过32kb,这个不知道有没有用,没试过
(3)分享的link等url中包含中文会导致ios分享不成功,需要对中文字段进行编码处理 例:
var link = "*.action?name=" + encodeURIComponent($('#uname').val());
因为安卓可以解析,IOS可能解析不了
(4)微信版本低,也有可能导致分享不成功
更多错误代码请详见https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115附录5