首先需求是:将微信扫一扫的分享功能 回形针图片 描述 标题 换成自定义的
大概是从上往下的这个效果。
好了需求已经很明确了,接下来说一下实现过程。先贴两个很有用的帖子可以借鉴参考:
http://www.jjyc.org/a/d/182281
https://www.jianshu.com/p/922b0986d1b0
除了这两篇帖子外的需要做的还有一个全局缓存,因为jsapi_ticket有个7200的失效时间,而微信对这个调用微信JS接口的临时票据有调用限制。因此redis最好缓存一下,能增大对外的用户使用次数(后面会把做法详细说)。
我这边静态页面是写在后端,用调接口渲染出的静态页面,谁知道后面加了需求要对接jssdk,只能一点点append。。。真是太惨了。。大致是下面这样:
仔细看过流程的人就会发现:
上面的接口是从ajax请求返回的结果里取出来的。这三个参数作为请求wx官方的参数,是很重要的,时间戳、随机字符串都很好获取,最主要的是signature这个签名。下面来说怎么获取。
说之前提一下为什么多了这步:
其实没有这步,直接在后端获取再把值赋上去,而不用用ajax返回的对象data一个个去拿值,也能达到效果,但是只能转发一个用户,当这个用户用自己的链接再转发给其他人时就失效了,因为微信转发时会自动拼接一串字符,这些是未知的,所以请求的页面url不是固定的(一个素材id转发不是只有一个用户转发),这时只能用js的var url = location.href.split(’#’)[0];方法即时获取当前地址,不同用户不同的。【当前url是post请求求参数,返回jssdk的请求参数】后端代码如下:
@ApiOperation("获取推广微信JS-SDK请求参数")
@GetMapping("/pub/html/wxJSSdk")
@ResponseBody
public WxShareSignParam getWXjsSdkResult(@RequestParam("url") String url) {
try {
return wxShareSupport.getSignParam(url);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
顺便把拼接的前端代码放一下,虽然丑陋。。。
/**
* @param accountId 用户id标识
* @param title 推广文章标题
* @param updateTime 推广页面展示:更新时间
* @param content 推广页面展示:内容
* @param QRUrl 推广页面展示:注册入口二维码
* @param imgUrl 缩略图地址
* @param link 跳转链接
* @return
*/
public String buildHtml(Long accountId, String title, String updateTime, String content, String QRUrl, String imgUrl, String link, String descLimit) {
String url = spreadRegHtmlUrl + "/#/register?id=" + accountId;
StringBuffer builder = new StringBuffer();
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("" + title + " ");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("" + title + "
");
builder.append("");
builder.append("");
builder.append("" + spreadHtmlAuthor + "
");
builder.append("" + updateTime + "
");
builder.append("");
builder.append(content);
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
builder.append("");
return builder.toString();
}
这些写在前端就会清楚很多,只不过跟我们公司的需求有关,原本只是简单拼接一个html,标题内容时间这些内容。谁知道后面加需求了,只能在原来的代码上加。。加上负责这块的同事刚离职,,真是雪上加霜。。。
前端就差不多了,后面主要讲签名怎么获取。前端这里还有一个注意事项:
config里的配置
参数名是固定的(严格区分大小写,千万别写错了),后面jsapilist是指定分享的程序,比如发给朋友,朋友圈,微博,qq等。
下面是获取签名的代码:
public WxShareSignParam getSignParam(String url) {
try {
//随机字符串
String nonceStr = getNonceStr();
//时间戳
String timeStamp = getTimeStampSecond();
//获取加密签名
String signature = SHA1("jsapi_ticket=" + getTicketWithCache() + "&noncestr=" + nonceStr + "×tamp=" + timeStamp + "&url=" + url);
return new WxShareSignParam(timeStamp, nonceStr, signature);
} catch (Exception e) {
logger.info("获取微信分享 WxShareSignParam 异常 url:{}", url, e);
}
return null;
}
/**
* 获取随机字符串,16位
*
* @return
*/
private static String getNonceStr() {
return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
/**
* 获取时间戳,秒
*
* @return
*/
private static String getTimeStampSecond() {
return String.valueOf(System.currentTimeMillis() / 1000);//时间戳
}
将参数拼成字符串,其中jsapi_ticket处理成缓存,url是ajaxs请求来的当前随机地址
/**
* 同步关键字防止并发问题
* 分布式环境仍然可能出现问题,但是概率小且无影响,暂不处理
* @return
*/
private synchronized String getTicketWithCache() {
String ticket = getTicket();
if (StringUtils.isNotBlank(ticket)) {
return ticket;
}
String accessToken = getAccessToken();
ticket = getTicket(accessToken);
return ticket;
}
private String getTicket(){
try {
//private StringRedisTemplate stringRedisTemplate;
return stringRedisTemplate.boundValueOps(WX_SHARE_TICKET_CACHE_KEY).get();
}catch (Exception e){
logger.error("redis error");
}
return "";
}
private void setTicket(String ticket, long expireTime){
if(expireTime < 0){
return ;
}
try {
stringRedisTemplate.boundValueOps(WX_SHARE_TICKET_CACHE_KEY).set(ticket,expireTime, TimeUnit.SECONDS);
}catch (Exception e){
logger.error("redis error");
}
}
//缓存里没有的话 则重新请求
/**
* 每次获取accessToken都会导致上一个失效
* 目前只有这里用到,所以不做缓存,每次都重新获取
* @return
*/
public static String getAccessToken() {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" + "&appid=" + APP_ID + "&secret=" + SECRET;
try {
JSONObject demoJson = HttpClientUtils.httpGet(url);
logger.info("获取微信分享 key 结果:{}", demoJson);
String accessToken = demoJson.getString("access_token");
if (StringUtils.isBlank(accessToken)) {
throw new SpreadRuntimeException(SpreadErrorCode.SPREAD_WX_JsSDK_ACCESS_TOKEN_ERROR2);
}
return accessToken;
} catch (Exception e) {
logger.error("获取微信分享 key 异常", e);
throw new SpreadRuntimeException(SpreadErrorCode.SPREAD_WX_JsSDK_ACCESS_TOKEN_ERROR1);
}
}
public static String SHA1(String decript) {
try {
MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new SpreadRuntimeException(SpreadErrorCode.SPREAD_WX_JsSDK_SHA1_ERROR);
}
}