实现方案:
1、主要通过使用微信JS-SDK实现;
2、微信相关文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
相关配置:
1、调用微信的分享需要提供微信公众号的AppID和AppSecret(注意AppSecret是保密的,如果重置可能会影响以前的使用场景)
2、本次所使用的 AppID: ****** AppSecret: ******
3、通过AppID和AppSecret获取微信的access_token,注意需要配置微信的ip白名单
4、使用微信的sdk分享还需要配置JS接口安全域名(具体配置规则微信的文档里面有详细介绍)
5、后端需要根据微信文档做权限算法解析,并返回前端所需要的字段
后端代码:
package com.fairy.fshop.util;
import java.io.IOException;
import java.net.URISyntaxException;
import org.apache.http.client.ClientProtocolException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.fairy.scsowing.common.HttpUtil;
import com.sowing.common.exception.ServiceException;
import net.sf.json.JSONObject;
/**
* 微信分享工具类
* @author dwj
*
*/
@Component
public class WeiXinShareUtil {
private static final Logger LOG = LoggerFactory.getLogger(WeiXinShareUtil.class);
public static String wx_appid = "wx51a9d9d6e19f9bef";
public static String wx_secret = "c10df335f8723f4198fe19d4c0137a04";
public static String wx_ticketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=";
public static String wx_accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
//以上值为测试用
public WeiXinShareUtil(){}
@Value("${share.weixin.appid}")
public void setAppid(String appid) {
wx_appid = appid;
}
@Value("${share.weixin.secret}")
public void setSecret(String secret) {
wx_secret = secret;
}
@Value("${share.weixin.ticketUrl}")
public void setWeixinTicketUrl(String weixinTicketUrl) {
wx_ticketUrl = weixinTicketUrl;
}
@Value("${share.weixin.accessTokenUrl}")
public void setWeixinAccessTokenUrl(String weixinAccessTokenUrl) {
wx_accessTokenUrl = weixinAccessTokenUrl;
}
/**
* 获取微信认证token(有效期7200s)
* @return
* @throws ClientProtocolException
* @throws URISyntaxException
* @throws IOException
*/
public static String getWXAccessToken() {
try {
HttpUtil httpUtil = new HttpUtil();
String access_token = httpUtil.doGetForString(wx_accessTokenUrl + wx_appid + "&secret=" + wx_secret);
LOG.info("获取微信accessToken接口返回参数:{}", access_token);
Object json =JSONObject.fromObject(access_token).get("access_token");
return json.toString();
} catch (Exception e) {
LOG.error("获取微信accessToken异常,原因:{}", e);
throw new ServiceException("获取微信accessToken异常");
}
}
/**
* 通过获取的微信token获取ticket
* @param accessToken
* @return
* @throws IOException
* @throws URISyntaxException
* @throws ClientProtocolException
*/
public static String getWXTicket(String accessToken) {
try {
HttpUtil httpUtil = new HttpUtil();
String ticket = httpUtil.doGetForString(wx_ticketUrl + accessToken);
LOG.info("获取微信ticket接口返回参数:{}", ticket);
Object json =JSONObject.fromObject(ticket).get("ticket");
return json.toString();
} catch (Exception e) {
LOG.error("获取微信ticket异常,原因:", e);
throw new ServiceException("获取微信ticket异常");
}
}
}
package com.sowing.hsh.api.controller;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.fairy.fshop.system.redis.RedisService;
import com.fairy.fshop.util.WeiXinShareUtil;
import com.fairy.scsowing.common.HttpUtil;
/**
* 微信分享
*/
@RestController
public class WeixinShareController {
private static final Logger LOG = LoggerFactory.getLogger(WeixinShareController.class);
private static String weixinTicketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=";
@Resource
private RedisService redisService;
/**
* 获取微信分享的ticket
*
* @param accessToken
* @return
* @throws IOException
* @throws URISyntaxException
*/
@RequestMapping("/weixin/shareTicket")
public static void getWeixinTicket(@RequestParam("accessToken") String accessToken, HttpServletResponse response)
throws IOException, URISyntaxException {
HttpUtil httpUtil = new HttpUtil();
String ticket = httpUtil.doGetForString(weixinTicketUrl + accessToken);
response.getOutputStream().write(ticket.getBytes());
}
/**
* 获取微信分享js-sdk权限验证签名
* @param url 分享地址
* @return
*/
@RequestMapping("/weixin/share/sign")
@ResponseBody
public Map<String, String> getWXSign(@RequestParam("shareUrl") String shareUrl) {
try {
shareUrl = URLDecoder.decode(shareUrl, "UTF-8");
} catch (UnsupportedEncodingException e) {
LOG.error("分享url解码异常:{}", e.getMessage());
}
//通过redis做缓存,防止获取微信token和ticket超过请求限制
String accessToken = redisService.getString("weixin_access_token_redis_key");
String jsapi_ticket = redisService.getString("weixin_ticket_redis_key");
if(StringUtils.isBlank(accessToken)) {
accessToken = WeiXinShareUtil.getWXAccessToken();
redisService.setString("weixin_access_token_redis_key", accessToken, 7000);
}
if(StringUtils.isBlank(jsapi_ticket)) {
jsapi_ticket = WeiXinShareUtil.getWXTicket(accessToken);
redisService.setString("weixin_ticket_redis_key", jsapi_ticket, 7000);
}
String nonce_str = createNonceStr();
String timestamp = createTimestamp();
String signature;
// 注意这里参数名必须全部小写,且必须有序
String signatureStr = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + shareUrl;
LOG.info("微信分享加密字符串:{}", signatureStr);
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(signatureStr.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
} catch (Exception e) {
LOG.error("获取微信签名signature异常,原因:{}", e);
return null;
}
Map<String, String> ret = new HashMap<String, String>();
ret.put("url", shareUrl);
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;
}
/**
* 随机字符串
* @return
*/
private static String createNonceStr() {
return UUID.randomUUID().toString();
}
/**
* 获取系统时间戳
* @return
*/
private static String createTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
前端代码:
export const wxApi = {
wxRegister(option){ //获取签名算法解析之后的相关字段
Axios.get(`${process.env.VUE_APP_URL}weixin/share/sign?`,{
params: {
shareUrl: decodeURIComponent(location.href)
}
}).then(function (res) {
let data = res.data;
if(res){
wx.config({ //根据微信的文档进行config接口注入权限验证配置
debug: true, // 开启调试模式
appId: data.appId || 'wx53c4a6dcfd495e12', // 必填,公众号的唯一标识
timestamp: data.timestamp, // 必填,生成签名的时间戳
nonceStr: data.nonceStr, // 必填,生成签名的随机串
signature: data.signature, // 必填,签名,见附录1
jsApiList: ['updateAppMessageShareData','updateTimelineShareData','onMenuShareTimeline'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
})
wx.ready(() => { //通过ready接口处理成功验证
if (option) {
wxApi.updateAppMessageShareData(option);
wxApi.updateTimelineShareData(option);
wxApi.onMenuShareTimeline(option)
}
})
}
})
.catch(function (err) {
console.log(err);
})
},
updateAppMessageShareData(option){
wx.updateAppMessageShareData({
title: option.title, // 分享标题
desc: '刚在惠生活发现了一个好东西,我猜你也感兴趣,快来看看吧!',
link: option.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: option.imgUrl, // 分享图标
success: function () {
// 设置成功
}
})
},
updateTimelineShareData(option){
wx.updateTimelineShareData({
title: option.title, // 分享标题
link: option.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: option.imgUrl, // 分享图标
success: function () {
// 设置成功
}
})
},
onMenuShareTimeline(option){
wx.onMenuShareTimeline({
title: option.title, // 分享标题
link: option.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: option.imgUrl, // 分享图标
success: function () {
// 用户点击了分享后执行的回调函数
}
})
}
}
使用方法:
解构并调用wxRegister方法,此方法需传递一个option对象前端使用:
option:{
title: '', // 分享标题
link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: '', // 分享图标
}