微信分享功能开发
用了一天时间,把微信发送给朋友和分享到朋友圈功能开发出来,在这里给大家分享一下,避免大家走弯路。
一、服务器端程序
- package com.wiimedia.controller;
- import java.io.IOException;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.text.ParseException;
- import java.text.SimpleDateFormat;
- import java.util.Arrays;
- import java.util.Date;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import com.google.gson.Gson;
- import com.wiimedia.model.Ticket;
- import com.wiimedia.service.ArticleSolrService;
- import com.wiimedia.service.TicketRepository;
- import com.wiimedia.service.TicketRepositorySolr;
- import com.wiimedia.utils.GetRandomStr;
- import com.wiimedia.utils.SignatureBean;
- import com.wiimedia.utils.weixin.WeixinUtil;
- /**
- *
- *
- *<p>Project:mryl_phone_v2</p>
- *
- *<p>Package:com.wiimedia.controller</p>
- *
- *<p>Description:微信分享Controller</p>
- *
- *<p>Company:Wiimedia</p>
- *
- *@Athor:SongJia
- *
- *@Date:2016-7-15 上午09:34:10
- *
- */
- @Controller
- @RequestMapping("/WeixinshareController/Api/Inteface")
- public class WeixinshareController {
- @Autowired
- private TicketRepositorySolr ticketRepositorySolr;
- @RequestMapping("/getSignature")
- public String getSignature( HttpServletRequest request,
- HttpServletResponse response) throws IOException, ParseException{
- //获取签名页面链接
- String url = request.getParameter("url");
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- //从数据库中获取标签,并检查标签是否过期
- Ticket oldticket = ticketRepositorySolr.getTicketById("20160114wiimediamrylsong1152");
- if(oldticket==null){//第一次访问,标签不存在。
- executeTicket(response,"1",url,format);
- return null;
- }else{//标签存在,判断标签是否超时
- String oldAcquiretime = oldticket.getAcquiretime();
- long difference=format.parse(format.format(new Date())).getTime()-format.parse(oldAcquiretime).getTime();
- if(difference>7100000){//标签超时,重新到微信服务器请求标签超时时间为7200秒(7200000毫秒)
- executeTicket(response,"2",url,format);
- return null;
- }else{//标签未超时
- /**
- * 注意事项
- * 1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
- * 2.签名用的url必须是调用JS接口页面的完整URL。
- * 3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。
- *
- ****根据第1点要求 signature 配置的时候很容易出错,需要把生成 Ticket的 noncestr和 timestamp传给客户端***
- */
- String signature = signature(oldticket.getTicket(),oldticket.getTimestamp(),oldticket.getNoncestr(),url);
- SignatureBean signatureBean = new SignatureBean();
- signatureBean.setNoncestr(oldticket.getNoncestr());
- signatureBean.setSignature(signature);
- signatureBean.setTimestamp(oldticket.getTimestamp());
- signatureBean.setUrl(url);
- response.setContentType("text/html;charset=UTF-8");
- response.getWriter().print(new Gson().toJson(signatureBean));
- return null;
- }
- }
- }
- /**
- *
- *<p>Project:mryl_phone_v2</p>
- *
- *<p>:mryl_phone_v2</p>
- *
- *<p>Description:更新和获取ticket的方法,因为用的solr所以更新和新增是一样的ID无则添加,有责更新</p>
- *
- *<p>Company:Wiimedia</p>
- *
- *@Athor:SongJia
- *
- *@Date:2016-7-15 上午09:45:00
- *
- */
- public void executeTicket(HttpServletResponse response,String flag,String url,SimpleDateFormat format) throws IOException{
- //获取签名随即字符串
- GetRandomStr randomStr = new GetRandomStr();
- String noncestr = randomStr.getRandomString(15);
- //获取签名时间戳
- String timestamp = Long.toString(System.currentTimeMillis());
- //请求accessToken
- String accessTokenUrl ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=您的APPID&secret=您的密匙";
- String tokenJson = WeixinUtil.httpRequest(accessTokenUrl, "GET", null);
- Gson gson = new Gson();
- ShareAccess_Token token = gson.fromJson(tokenJson, ShareAccess_Token.class);
- String to= token.getAccess_token();
- //获取标签
- String urlTicket ="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+to+"&type=jsapi";
- String ticketJson = WeixinUtil.httpRequest(urlTicket, "GET", null);
- Ticket ticket = gson.fromJson(ticketJson, Ticket.class);
- String t = ticket.getTicket();
- //String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
- //我的Ticket ID是写死的
- String acquiretime = format.format(new Date());
- ticket.setTid("20160114wiimediamrylsong1152");
- ticket.setAcquiretime(acquiretime);
- ticket.setTimestamp(timestamp);
- ticket.setNoncestr(noncestr);
- //因为用的SOLR所以更新和添加的方法是一样的,可以根据自己具体需求进行修改,本文不再贴出代码.
- if(flag.equals("2")){
- ticketRepositorySolr.addTicketToSolr(ticket);
- }else{
- ticketRepositorySolr.addTicketToSolr(ticket);
- }
- /**
- * 注意事项
- * 1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
- * 2.签名用的url必须是调用JS接口页面的完整URL。
- * 3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。
- *
- *根据第1点要求 signature 配置的时候很容易出错,需要把生成 Ticket的 noncestr和 timestamp传给客户端*
- */
- String signature = signature(t,timestamp,noncestr,url);
- SignatureBean signatureBean = new SignatureBean();
- signatureBean.setNoncestr(noncestr);
- signatureBean.setSignature(signature);
- signatureBean.setTimestamp(timestamp);
- signatureBean.setUrl(url);
- response.setContentType("text/html;charset=UTF-8");
- response.getWriter().print(new Gson().toJson(signatureBean));
- }
- /**
- *
- *<p>Project:mryl_phone_v2</p>
- *
- *<p>:mryl_phone_v2</p>
- *
- *<p>Description:根据标签,时间戳,密匙,URL进行签名</p>
- *
- *<p>Company:Wiimedia</p>
- *
- *@Athor:SongJia
- *
- *@Date:2016-7-15 上午09:37:13
- *
- */
- private String signature(String jsapi_ticket, String timestamp, String noncestr, String url) {
- jsapi_ticket = "jsapi_ticket=" + jsapi_ticket;
- timestamp = "timestamp=" + timestamp;
- noncestr = "noncestr=" + noncestr;
- url = "url=" + url;
- String[] arr = new String[] { jsapi_ticket, timestamp, noncestr, url };
- // 将token、timestamp、nonce,url参数进行字典序排序
- Arrays.sort(arr);
- StringBuilder content = new StringBuilder();
- for (int i = 0; i < arr.length; i++) {
- content.append(arr[i]);
- if (i != arr.length - 1) {
- content.append("&");
- }
- }
- MessageDigest md = null;
- String tmpStr = null;
- try {
- md = MessageDigest.getInstance("SHA-1");
- // 将三个参数字符串拼接成一个字符串进行sha1加密
- byte[] digest = md.digest(content.toString().getBytes());
- tmpStr = byteToStr(digest);
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- }
- content = null;
- return tmpStr;
- }
- /**
- * 将字节转换为十六进制字符串
- *
- * @param mByte
- * @return
- */
- private static String byteToHexStr(byte mByte) {
- char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
- char[] tempArr = new char[2];
- tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
- tempArr[1] = Digit[mByte & 0X0F];
- String s = new String(tempArr);
- return s;
- }
- /**
- * 将字节数组转换为十六进制字符串
- *
- * @param byteArray
- * @return
- */
- private static String byteToStr(byte[] byteArray) {
- String strDigest = "";
- for (int i = 0; i < byteArray.length; i++) {
- strDigest += byteToHexStr(byteArray[i]);
- }
- return strDigest;
- }
- class ShareAccess_Token{
- private String access_token;
- private String expires_in;
- public String getAccess_token() {
- return access_token;
- }
- public void setAccess_token(String accessToken) {
- access_token = accessToken;
- }
- public String getExpires_in() {
- return expires_in;
- }
- public void setExpires_in(String expiresIn) {
- expires_in = expiresIn;
- }
- }
- }
- <script type="text/javascript">
- var url = window.location.href;
- var articleId = "";
- var shareTitle="明日医疗资讯";
- var shareImgUrl="";
- var userinfo = localStorage.getItem("_userinfo");
- var timestamp;
- var noncestr;
- var signature;
- //获取签名
- $.ajax({
- type: "GET",
- url: "WeixinshareController/Api/Inteface/getSignature",
- //data:{timestamp:timestamp,noncestr:noncestr,url:url},
- data:{url:url},
- success: function(data){
- var objData=JSON.parse(data);
- timestamp=objData.timestamp;
- noncestr=objData.noncestr;
- signature=objData.signature;
- console.log(objData);
- wxShare();
- }
- });
- function wxShare(){
- wx.config({
- debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
- appId: '您的appid', // 和获取Ticke的必须一样------必填,公众号的唯一标识
- timestamp:timestamp, // 必填,生成签名的时间戳
- nonceStr: noncestr, // 必填,生成签名的随机串
- signature: signature,// 必填,签名,见附录1
- jsApiList: [
- 'onMenuShareAppMessage'
- ] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
- });
- }
- wx.ready(function(){
- //config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,
- //config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关
- //接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
- //----------“分享给朋友”
- wx.onMenuShareAppMessage({
- title: "明日医疗资讯", // 分享标题
- desc: shareTitle, // 分享描述
- link: url, // 分享链接
- imgUrl: shareImgUrl, // 分享图标
- type: '', // 分享类型,music、video或link,不填默认为link
- dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
- success: function () {
- // 用户确认分享后执行的回调函数、
- },
- cancel: function () {
- // 用户取消分享后执行的回调函数
- }
- });
- //------------"分享到朋友圈"
- wx.onMenuShareTimeline({
- title: '明日医疗资讯', // 分享标题
- link: '', // 分享链接
- imgUrl: shareImgUrl, // 分享图标
- success: function () {
- // 用户确认分享后执行的回调函数
- },
- cancel: function () {
- // 用户取消分享后执行的回调函数
- }
- });
- //-------------分享到QQ
- wx.onMenuShareQQ({
- title: '明日医疗资讯', // 分享标题
- desc: shareTitle, // 分享描述
- link: '', // 分享链接
- imgUrl: shareImgUrl, // 分享图标
- success: function () {
- // 用户确认分享后执行的回调函数
- },
- cancel: function () {
- // 用户取消分享后执行的回调函数
- }
- });
- //-------------分享到QQ空间
- wx.onMenuShareQZone({
- title: '明日医疗资讯', // 分享标题
- desc: shareTitle, // 分享描述
- link: '', // 分享链接
- imgUrl: shareImgUrl, // 分享图标
- success: function () {
- // 用户确认分享后执行的回调函数
- },
- cancel: function () {
- // 用户取消分享后执行的回调函数
- }
- });
- });
三、服务器需要的工具类和Model
① Ticket
- package com.wiimedia.model;
- public class Ticket{
- private String tid;
- private String ticket;
- private String errcode;
- private String errmsg;
- private String expires_in;
- private String acquiretime;
- private String noncestr;
- private String timestamp;
- public Ticket(String tid, String ticket, String errcode, String errmsg,
- String expiresIn, String acquiretime, String noncestr,
- String timestamp) {
- super();
- this.tid = tid;
- this.ticket = ticket;
- this.errcode = errcode;
- this.errmsg = errmsg;
- expires_in = expiresIn;
- this.acquiretime = acquiretime;
- this.noncestr = noncestr;
- this.timestamp = timestamp;
- }
- public String getTid() {
- return tid;
- }
- public void setTid(String tid) {
- this.tid = tid;
- }
- public String getTicket() {
- return ticket;
- }
- public void setTicket(String ticket) {
- this.ticket = ticket;
- }
- public String getErrcode() {
- return errcode;
- }
- public void setErrcode(String errcode) {
- this.errcode = errcode;
- }
- public String getErrmsg() {
- return errmsg;
- }
- public void setErrmsg(String errmsg) {
- this.errmsg = errmsg;
- }
- public String getExpires_in() {
- return expires_in;
- }
- public void setExpires_in(String expiresIn) {
- expires_in = expiresIn;
- }
- public String getAcquiretime() {
- return acquiretime;
- }
- public void setAcquiretime(String acquiretime) {
- this.acquiretime = acquiretime;
- }
- public String getNoncestr() {
- return noncestr;
- }
- public void setNoncestr(String noncestr) {
- this.noncestr = noncestr;
- }
- public String getTimestamp() {
- return timestamp;
- }
- public void setTimestamp(String timestamp) {
- this.timestamp = timestamp;
- }
- }
③ GetRandomStr
- package com.wiimedia.utils;
- import java.util.Random;
- public class GetRandomStr {
- /**
- *
- *<p>Project:mryl_phone_v2</p>
- *
- *<p>:mryl_phone_v2</p>
- *
- *<p>Description:生成随即字符串 </p>
- *
- *<p>Company:Wiimedia</p>
- *
- *@Athor:SongJia
- *
- *@Date:2016-7-14 上午11:14:46
- *
- */
- public String getRandomString(int length) {
- String base = "abcdefghijklmnopqrstuvwxyz0123456789";
- Random random = new Random();
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < length; i++) {
- int number = random.nextInt(base.length());
- sb.append(base.charAt(number));
- }
- return sb.toString();
- }
- }
- package com.wiimedia.utils;
- public class SignatureBean {
- private String noncestr;
- private String url;
- private String timestamp;
- private String signature;
- public String getNoncestr() {
- return noncestr;
- }
- public void setNoncestr(String noncestr) {
- this.noncestr = noncestr;
- }
- public String getUrl() {
- return url;
- }
- public void setUrl(String url) {
- this.url = url;
- }
- public String getTimestamp() {
- return timestamp;
- }
- public void setTimestamp(String timestamp) {
- this.timestamp = timestamp;
- }
- public String getSignature() {
- return signature;
- }
- public void setSignature(String signature) {
- this.signature = signature;
- }
- }
- package com.wiimedia.utils.weixin;
- import java.io.BufferedReader;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.net.ConnectException;
- import java.net.URL;
- import javax.net.ssl.HttpsURLConnection;
- import javax.net.ssl.SSLContext;
- import javax.net.ssl.SSLSocketFactory;
- import javax.net.ssl.TrustManager;
- /**
- *
- *<p>Project:mryl_phone_v2</p>
- *
- *<p>:mryl_phone_v2</p>
- *
- *<p>Description:公众平台接口工具类</p>
- *
- *<p>Company:Wiimedia</p>
- *
- *@Athor:SongJia
- *
- *@Date:2016-7-15 上午09:37:13
- *
- */
- public class WeixinUtil {
- /**
- * 发起https请求并获取结果
- *
- * @param requestUrl 请求地址
- * @param requestMethod 请求方式(GET、POST)
- * @param outputStr 提交的数据
- * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
- */
- public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
- StringBuffer buffer = new StringBuffer();
- try {
- // 创建SSLContext对象,并使用我们指定的信任管理器初始化
- 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();
- return buffer.toString();
- } catch (ConnectException ce) {
- ce.printStackTrace();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return "";
- }
- }
四、至此,分享功能已经开发完成,但是,在生成signature的时候会遇到很多问题,这里提供一些wx.config失败的排错方法。
① 确认自己的生成的signature是否正确
在微信提供的http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign进行校验
② wx.config中使用的noncestr, timestamp与用以签名中的对应noncestr, timestamp是否一致一致…如上面(一.服务器代码)
(有可能因为JS页面加载顺序问题,服务器生成的signature,noncestr,timestamp在wx.config中没有获取到)。
③ 确认url是页面完整的url,包括GET参数部分
需要去掉#后面的部分
④ config 中的 appid 与用来获取 jsapi_ticket 的 appid 是否一致
⑤ 报错{errmsg:config:ok}是debug的正常返回把调试模式关掉就OK
wx.config debug: false,
以上就是本文的全部内容,希望对大家的学习有所帮助