久违的新文章发布。。我的锅
这篇文章是在写的太长,可以慢慢看一下,伪代码,毕竟简书上面,望大家见谅!!!
先看官方文档:微信jssdk官方文档
首先得有我们自己的公众号对吧,而且token要验证过了哈,如果不会,我可以写文章教大家,有留言我就写
继续老的节奏,按步骤来做
STEP 1
我们进入公众号测试平台,如图所示:
并且打开开发者文档
查看一下权限
最好认证一下公众号无论是订阅号还是服务号,都是有必要的!如果是真心想做好微信端的,有利无害!
这里以我申请的测试号为例,权限相对比较充足
没打马赛克哈
STEP 2
Controller层可以这么写
@RequestMapping(value="/share", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
protected Mapshare(String url) throws ServletException, IOException {
System.out.println("inter share");
String ticket=null;
EhcacheUtil cache = EhcacheUtil.initCache("sys-js_tickt-cache");
//我使用了ehcache的缓存
System.out.println(cache);
Object aa=cache.get("jst");
//从我们自己定义的cache块中去出我们放入的对象(如果是第一次运行则其为null)
if(aa!=null){
ticket=aa.toString(); //赋值给我们的ticket字段
}else{
//appid,appSecret自行写成static final string ,我就不贴出我的了
AccessToken token = WeixinUtil.getAccessToken(appId, appSecret);
// AccessToken类为我们自己写的接受token的一个entity类,很简单,你也直接用string替代
//WeixinUtil.getAccessToken(string,string)方法见下面
String toke=token.getToken();
JSToken jt=WeixinUtil.getTicket(toke);
//我们获取到了token后,需要再将token已get方式传给getTicket()方法,见下面
ticket=jt.getJsapi_ticket();
cache.put("jst", ticket);
//放进缓存,以便下次使用
}
System.out.println("ticket:"+ticket+"url:"+url);
Mapret = WeixinUtil.sign(ticket, url);
//此处对ticket,url,noncestr,timestamp进行签名排序
ret.put("appid", appId);
for (Map.Entry entry : ret.entrySet()) {
System.out.println(entry.getKey() + ", " + entry.getValue());
},
return ret;
}
缓存存储的原因请看下面:access_TOKEN 获取每天是有限制次数的!!!而且官方说明:
access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
公众平台的API调用所需的access_token的使用及生成方式说明:
1、为了保密appsecrect,第三方需要一个access_token获取和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则会造成access_token覆盖而影响业务;
2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器对外输出的依然是老access_token,此时公众平台后台会保证在刷新短时间内,新老access_token都可用,这保证了第三方业务的平滑过渡;
3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。
具体参数配置可以看一下文章末尾的附录1
STEP 3
WeixinUtil工具类写法,一下方法均在此类中
所涉及的jar包,可以看文章末尾附录2
package com.fredia.weixin;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.servlet.http.HttpServletRequest;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
public class WeixinUtil{
public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
public final static String JS_url ="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
public static String js_tick="";
public static AccessToken getAccessToken(String appid, String appsecret)
throws JSONException {
AccessToken accessToken = null;
String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
// 如果请求成功
if (null != jsonObject) {
try {
accessToken = new AccessToken(); accessToken.setToken(jsonObject.getString("access_token")); accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
} catch (JSONException e) {
accessToken = null;
// 获取token失败//
log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
System.out.println("获取token失败 errcode:{} errmsg:{}"+jsonObject.getInt("errcode")+jsonObject.getString("errmsg"));
} }
return accessToken;
}
public static JSToken getTicket(String token)
throws JSONException {
JSToken jstoken = null;
String requestUrl = JS_url.replace("ACCESS_TOKEN", token);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
// 如果请求成功
if (null != jsonObject) {
try {
jstoken = new JSToken();
jstoken.setJsapi_ticket(jsonObject.getString("ticket"));
jstoken.setExpiresIn(jsonObject.getInt("expires_in"));
} catch (JSONException e) {
jstoken = null;
System.out.println("获取ticket失败 errcode:{} errmsg:{}"+jsonObject.getInt("errcode")+jsonObject.getString("errmsg"));
} }
return jstoken;
}
public static Map sign(String jsapi_ticket, String url) {
Map ret = new HashMap();
String nonce_str = create_nonce_str();
String timestamp = create_timestamp();
String string1;
String signature = "";
//注意这里参数名必须全部小写,且必须有序
string1 = "jsapi_ticket=" + jsapi_ticket +"&noncestr=" + nonce_str +"×tamp=" + timestamp +"&url=" + url;
System.out.println(string1);
try {
//SHA-1加密方式,jar包有
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
//字符串utf-8处理
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;
}
//byteTohex 方法
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);
}
自己重写httprequest请求方法
/**
- 描述: 发起https请求并获取结果
- @param requestUrl 请求地址
- @param requestMethod 请求方式(GET、POST)
- @param outputStr 提交的数据
- @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
*/
//网上一大堆的下面方法的模板
public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = null;
StringBuffer buffer = new StringBuffer();
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
//MyX509TrustManager方法网上有教程
作用?哼哼:
通过X509TrustManager接口实现自己创建的证书
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
if ("GET".equalsIgnoreCase(requestMethod))
httpUrlConn.connect();
// 当有数据需要提交时
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
jsonObject = JSONObject.fromObject(buffer.toString());
System.out.println("hehe");
} catch (ConnectException ce) {
// log.error("Weixin server connection timed out.");
System.out.println("Weixin server connection timed out.");
} catch (Exception e) {
// log.error("https request error:{}", e);
System.out.println("https request error:{}");
}
return jsonObject;
}
}
STEP 4
自己封装一个ehcache 所需功能的方法
package com.ehcache
import java.util.List;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
/**
- 将Ehcache 包装一层
*/
public class EhcacheUtil {
private final Ehcache cache;
private EhcacheUtil(Ehcache ehcache) {
cache = ehcache;
}
//初始化缓存块
public static EhcacheUtil initCache(String cacheName) {
CacheManager cacheManager = (CacheManager) BeanFactoryUtil.getContext()
.getBean("ehCacheManager");
Ehcache cache = cacheManager.getCache(cacheName);
if (cache == null) {
cacheManager.addCache(cacheName);
} else {
cache = cacheManager.getCache(cacheName);
}
return new EhcacheUtil(cache);
}
public boolean containsKey(Object key) {
return cache.isElementInMemory(key) || cache.isElementOnDisk(key);
}
public void put(Object key, Object value) {
Element element = new Element(key, value);
cache.put(element);
}
public Object get(Object key) {
Element element = cache.get(key);
if (element == null) {
return null;
}
return element.getObjectValue();
}
public void remove(Object key) {
cache.remove(key);
}
public void removeAllKey() {
cache.removeAll();
}
public List getKeys() {
return cache.getKeys();
}
}
STEP 5
下面我们该懂jQuery了,不管使用是的jsp 还是freemarker之类的模板引擎
页面js的写法如下
$(function(){
var url=location.href.split('#')[0];// 获取当前访问浏览器的地址的"#“前的地址
核心是下面
$.ajax({
//ajax将我当前页面的url传给我的share controller
type: "POST",
url: '/weixin/share',
dataType: 'json',
data: {url:url},
success: function(result){
//成功返回给页面时,我们回调wx.config方法。可以放在ajax里面,亲测
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: result.appid, // 必填,公众号的唯一标识
timestamp: result.timestamp, // 必填,生成签名的时间戳
nonceStr: result.noncestr, // 必填,生成签名的随机串
signature: result.signature,// 必填,签名
// 检测自己所将要使用的jssdk api是否有权限
jsApiList: [
'checkJsApi',
'onMenuShareTimeline',
'onMenuShareAppMessage',
]
});
}
});
});
wx.ready(function () {
// 1 判断当前版本是否支持指定 JS 接口,支持批量判断
document.querySelector('#checkJsApi').onclick = function () {
wx.checkJsApi({
jsApiList: [
'checkJsApi',
'onMenuShareTimeline',
'onMenuShareAppMessage',
],
success: function (res) {
alert(JSON.stringify(res));
}
});
};
//最好以触发时间来进行操作,如果是自动加载的话,容易出现点击无响应情况,毕竟自动加载是有顺序的
// 2. 监听“分享给朋友”,按钮点击、自定义分享内容及分享结果接口
document.querySelector('#onMenuShareAppMessage').onclick = function () {
wx.onMenuShareTimeline({
//下面字段都可以动态,以静态字符串示例
title: '邀请好友',
desc: 'fredia王瘦子',
link: url,
imgUrl: '?88',
trigger: function (res) {
},
success: function (res) {
alert('已分享');
},
cancel: function (res) {
alert('已取消');
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
wx.onMenuShareAppMessage({
title:' 邀请好友',
desc: '快来吧!',
link: url,
imgUrl: 'http://www.ftoow.com/attachment/Mon_1603/145_2_84ec990ab4f8b11.jpg?88',
trigger: function (res) {
},
success: function (res) {
alert('已分享');
},
cancel: function (res) {
alert('已取消');
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
});
//jssdk- 错误的处理方案方法
wx.error(function (res) {
alert(res.errMsg);
});
在页面上创建id=“checkJsApi”,id=“onMenuShareAppMessage”的button,可以进行测试一下
STEP 6
调试工具选择
推荐使用官方web开发者工具,虽然慢了点,但是毕竟是官方的,对于调试还是很不错的!
附录1
ehcache配置
配置自定义缓存
maxElementsInMemory:缓存中允许创建的最大对象数
eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,
两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,
如果该值是 0 就意味着元素可以停顿无穷长的时间。
timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,
这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
overflowToDisk:内存不足时,是否启用磁盘缓存。
memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。