微信不得不说对程序员非常不友好,开发复杂,各种验证,各种扫一扫。
比如开发一个简单的html,想分享到朋友圈一下下,结果还要后台生成jsTicket和签名,jsTicket又依赖access_token,access_token又得缓存起来,简直就是醉了。为了防止微信成为另一个qq空间,弄的复杂的要死,不过也阻止不了成为另一个qq空间。
首先描述一下需求:做了一个静态的html,需要分享时自定义图片,和描述文字。
解决方法:各种查文档,最后思路为需要后台支撑,后台要获取jsTicKet,jsTicket需要access_token,两者都需要做缓存。后台语言为java,采用的wexin开发包为: weixin-popular https://github.com/liyiorg/weixin-popular .微信文档: https://mp.weixin.qq.com/wiki/11/74ad127cc054f6b80759c40f77ec03db.html
实现:java主要实现类,access_token采用TokenManager,自带了超时刷新,可以查看TokenManager源码,用一个定时任务,每隔118分更新一次token。为了快速开发,jsTicket采用的项目带的redis做缓存。
package cn.arvix.ober.service.impl;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import cn.arvix.ober.been.JSONResult;
import cn.arvix.ober.consants.ArvixOberConstants;
import cn.arvix.ober.controller.WeixinController;
import cn.arvix.ober.service.WeixinService;
import cn.arvix.ober.util.WeixinJsSign;
import weixin.popular.api.TicketAPI;
import weixin.popular.support.TokenManager;
@Service
public class WeixinServiceImpl implements WeixinService{
private static String REDIS_WEIXIN_JS_TOKEN = "WEIXIN_JSAPI_TOKEN";
private static final Logger log = LoggerFactory
.getLogger(WeixinController.class);
@Autowired
public RedisTemplate<Object, Object> redisTemplate;
@Override
public JSONResult getAccessToken() {
JSONResult result = new JSONResult();
String token = TokenManager.getToken(ArvixOberConstants.WH_CHAT_CLIENT_APP_ID);
log.info("weixin access token--->{}",token);
result.setSuccess(true);
result.setData(token);
return result;
}
@Override
public JSONResult getJsApiToken() {
JSONResult result = new JSONResult();
result.setSuccess(true);
result.setData(getJsApiTokenStr());
return result;
}
private String getJsApiTokenStr(){
String token = TokenManager.getToken(ArvixOberConstants.WH_CHAT_CLIENT_APP_ID);
log.info("weixin access token--->{}",token);
Object cacheJsToken = redisTemplate.opsForValue().get(REDIS_WEIXIN_JS_TOKEN);
String jsToken = "";
if(null!=cacheJsToken){
jsToken = cacheJsToken.toString();
log.info("get weixin jsToken from cache--->{}",jsToken);
}else{
jsToken = TicketAPI.ticketGetticket(token).getTicket();
log.info("get weixin jsToken from weixin --->{}",jsToken);
redisTemplate.opsForValue().set(REDIS_WEIXIN_JS_TOKEN, jsToken, 118, TimeUnit.MINUTES);
}
return jsToken;
}
@Override
public JSONResult getJsSign(String url) {
JSONResult result = new JSONResult();
result.setSuccess(true);
result.setData(WeixinJsSign.sign(getJsApiTokenStr(), url));
return result;
}
}
签名实现类
package cn.arvix.ober.util;
import java.util.UUID;
import java.util.Map;
import java.util.HashMap;
import java.util.Formatter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WeixinJsSign {
private static final Logger log = LoggerFactory.getLogger(Email.class);
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 signInput;
String signature = "";
// 注意这里参数名必须全部小写,且必须有序
signInput = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str
+ "×tamp=" + timestamp + "&url=" + url;
log.info("signInput:{}",signInput);
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(signInput.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
} catch (NoSuchAlgorithmException e) {
log.error("",e);
} catch (UnsupportedEncodingException e) {
log.error("",e);
}
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;
}
private static String create_nonce_str() {
return UUID.randomUUID().toString();
}
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}
其它就是暴露一个接口api,提交url做为参数去换取jsApiConfig需要的参数。
重点来了,本地调试:下载 https://mp.weixin.qq.com/wiki/10/e5f772f4521da17fa0d7304f68b97d7e.html 里面的开发者工具,pc端上模拟微信手机端,然后就是域名的问题,js绑定了域名,只有在该域名下的请求,微信才会接收,这里只能是取巧:更改本地hosts文件,让配置的域名指向本地服务,win10参考配置地址:
http://jingyan.baidu.com/article/cbcede073380fb02f40b4d84.html ,其它系统类似。
js示例代码:
var _timestamp='',_nonscestring='',_signature='';
var url = location.href.split('#')[0]
$.ajax({
url:'http://192.168.0.104:9007/api/v1/weixin/getJsSign',
data:{url:url},
type:'get',
async:false,
success:function(data){
if(data.success){
_timestamp=data.data.timestamp;
_nonscestring=data.data.nonceStr;
_signature=data.data.signature;
}
alert(location.href.split('#')[0])
alert(_timestamp+","+_nonscestring+","+_signature);
console.log(_timestamp+","+_nonscestring+","+_signature)
}
})
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: 'wxb42c4e84da6027b6', // 必填,公众号的唯一标识
timestamp:_timestamp , // 必填,生成签名的时间戳
nonceStr: _nonscestring, // 必填,生成签名的随机串
signature: _signature,// 必填,签名,见附录1
jsApiList: [
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareWeibo'
] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
测试结果图片: