微信分享开发流程:
步骤一:绑定域名
先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
步骤二:引入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 标准模块加载方法加载
步骤三:通过config接口注入权限验证配置
见微信官方文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
//微信分享
//调用后台接口获取url签名
shareWXCTerminal: async function(){
let shareParams = {
appId: 'xxxxxxxxxxxxxxxxxx',
url: encodeURIComponent(location.href.split('#')[0])
}
const res= await http.post("/weshare/getwxfxSign/ashx", shareParams)
if(res.data.code==200&&res.data.data!=''){
var getMsg=res.data.data
wx.config({
//注意驼峰命名
debug: false, //生产环境需要关闭debug模式
appId: getMsg.appId, //appId通过微信服务号后台查看
timestamp: getMsg.timestamp, //生成签名的时间戳
nonceStr: getMsg.nonceStr, //生成签名的随机字符串
signature: getMsg.signature, //签名
jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ','onMenuShareQZone']
});
wx.ready(function() {
wx.onMenuShareTimeline({
title:'wenjuan', // 分享标题
link: location.href.split('#')[0]+'#'+location.href.split('#')[1],// 分享链接
imgUrl: 'http://scrm.taikang.com/d6e18c2231ec926ebae2cc95a55ec3c3(0c95d4edab6d4f5397931f936c731b11).jpg', // 分享图标
success:function(res){
}
}),
wx.onMenuShareAppMessage({
title:'标题', // 分享标题
desc: 'mianshuu', //分享描述
link: location.href.split('#')[0]+'#'+location.href.split('#')[1],// 分享链接
imgUrl: 'http://scrm.taikang.com/d6e18c2231ec926ebae2cc95a55ec3c3(0c95d4edab6d4f5397931f936c731b11).jpg', // 分享图标
success:function(res){
}
}),
wx.onMenuShareQZone({
title:'标题', // 分享标题
desc: 'mianshuu', //分享描述
link: location.href.split('#')[0]+'#'+location.href.split('#')[1],// 分享链接
imgUrl: 'http://scrm.taikang.com/d6e18c2231ec926ebae2cc95a55ec3c3(0c95d4edab6d4f5397931f936c731b11).jpg', // 分享图标
success:function(res){
}
}),
wx.onMenuShareQQ({
title:'标题', // 分享标题
desc: 'mianshuu', //分享描述
link: location.href.split('#')[0]+'#'+location.href.split('#')[1],// 分享链接
imgUrl: 'http://scrm.taikang.com/d6e18c2231ec926ebae2cc95a55ec3c3(0c95d4edab6d4f5397931f936c731b11).jpg', // 分享图标
success:function(res){
}
})
});
}
},
后台步骤
jsapi_ticket
生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。
1.参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):../15/54ce45d8d30b6bf6758f68d2e95bc627.html
2.用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
成功返回如下JSON:
{
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}
获得jsapi_ticket之后,就可以生成JS-SDK权限验证的签名了。
签名算法
签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。
即signature=sha1(string1)。 示例:
noncestr=Wm3WZYTPz0wzccnW
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg
timestamp=1414587457
url=http://mp.weixin.qq.com?params=value
步骤1. 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value
步骤2. 对string1进行sha1签名,得到signature:
0f9de62fce790f9a083d5c99e95740ceb90c27ed
注意事项
1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
2.签名用的url必须是调用JS接口页面的完整URL。
3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。
代码示例:
@PostMapping("/getwxfxSign/ashx")
public BaseBean getSignature(@RequestBody Map param) throws UnsupportedEncodingException {
String appId = param.get("appId");
String url = param.get("url");
//前端请求中的 url 是 encodeURIComponent(location.href.split('#')[0])处理过的
//后端相应的要 解码
String decodeUrl = URLDecoder.decode(url, "UTF-8");
SigVO sign = this.sign(appId, decodeUrl);
return BaseBean.success(sign);
}
private SigVO sign(String appid, String url) {
//32位随机字符串
String nonceStr = WeShareUtils.CreateNoncestr();
//时间戳
String timeStamp = WeShareUtils.GetTimeStamp();
//签名
String signature = StringUtils.EMPTY;
//获取jsapi_ticket
Map resultMap = new HashMap<>();
getJsapiTicketCatch(appid,resultMap);
String jsapiTicket = resultMap.get("tick");
if (StringUtils.isBlank(jsapiTicket)) {
return null;
}
//注意 参数字段全为小写
String s1 = "jsapi_ticket=" + jsapiTicket + "&noncestr="
+ nonceStr + "×tamp=" + timeStamp + "&url=" + url;
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(s1.getBytes("UTF-8"));
signature = WeShareUtils.byteToHex(crypt.digest());
} catch (Exception e) {
e.printStackTrace();
}
return SigVO.builder()
.appId(appid)
.timestamp(timeStamp)
.nonceStr(nonceStr)
.signature(signature)
.build();
}
private void getJsapiTicketCatch(String appid, Map resultMap) {
long expr = 90 * 60 * 1000L;
long ex = 5 * 1000L;
String value = String.valueOf(System.currentTimeMillis() + ex);
boolean lock = redisLock.lock(appid, value);
//获取锁
if (lock) {
String tick = redisTemplate.opsForValue().get("zqs"+appid);
if (StringUtils.isNotBlank(tick)) {
resultMap.put("tick",tick);
} else {
//token 通过appid
BigAuthorizationInfo authorInfoByAppidService = authorizedService.getAuthorInfoByAppidService(appid);
if (authorInfoByAppidService == null
|| StringUtils.isBlank(authorInfoByAppidService.getAuthorizer_access_token())) {
resultMap.put("tick",null);
}else {
String token = authorInfoByAppidService.getAuthorizer_access_token();
String jsapiTicket = WeShareUtils.GetJsapiTicket(token);
if (StringUtils.isBlank(jsapiTicket)) {
resultMap.put("tick",null);
}else {
resultMap.put("tick",jsapiTicket);
redisTemplate.opsForValue().set("zqs"+appid, jsapiTicket, expr,TimeUnit.MILLISECONDS);
}
}
}
//释放锁
redisLock.unlock(appid, value);
}
}
上述代码未实现定时刷新凭证jsapiTicket 而是通过redis设置失效时间 如果redis中没有需要的凭证
则主动调用获取jsapiTicket 的接口 获取后存入redis中 未避免并发导致 接口调用超出限制次数 所以加锁处理
微信官方的验证签名是否正确的网页工具地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
工具类:
package com.taikang.controller.share.WeShareUtil;
import com.alibaba.fastjson.JSONObject;
import com.taikang.constant.WeChatContants;
import com.taikang.controller.share.TicketJson;
import com.taikang.utils.weChat.WeChatUtils;
import java.util.Formatter;
import java.util.Random;
public class WeShareUtils {
public static String GetJsapiTicket(String token) {
String getTickUrl = WeChatContants.GET_TICK_URL;
String url = String.format(getTickUrl, token);
String result = WeChatUtils.getUrl(url);
TicketJson ticketJson = JSONObject.parseObject(result, TicketJson.class);
int errcode = ticketJson.getErrcode();
if (errcode==0){
return ticketJson.getTicket();
}
return null;
}
public static String GetTimeStamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
public 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 CreateNoncestr() {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder res = new StringBuilder();
Random rd = new Random();
for (int i = 0; i < 32; i++) {
res.append(chars.charAt(rd.nextInt(chars.length() - 1)));
}
return res.toString();
}
}