JAVA调用微信接口实现页面分享功能(分享到朋友圈显示图片,分享给朋友)
钉钉提供的内网穿透之HTTP穿透:https://www.cnblogs.com/pxblog/p/13862376.html
网页分享到微信中如何显示标题图,如果自定义标题图,描述,显示效果如下
官网接口地址;https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
加入maven依赖
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>[4.1.12,)</version> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20171018</version> </dependency>
HttpClientUtil.java
package com.test.cms.share; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; public class HttpClientUtil { private static class SingletonHolder{ private final static HttpClientUtil INSTANCE=new HttpClientUtil(); } private HttpClientUtil(){} public static HttpClientUtil getInstance(){ return SingletonHolder.INSTANCE; } public String get(String url){ CharsetHandler handler = new CharsetHandler("UTF-8"); CloseableHttpClient client = null; try { HttpGet httpget = new HttpGet(new URI(url)); HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); client= httpClientBuilder.build(); client = (CloseableHttpClient) wrapClient(client); return client.execute(httpget, handler); } catch (Exception e) { //e.printStackTrace(); return ""; }finally { try { if(client!=null){ client.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static String post(String url, String params,String contentType) { //创建HttpClientBuilder HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); //HttpClient CloseableHttpClient client = httpClientBuilder.build(); client = (CloseableHttpClient) wrapClient(client); HttpPost post = new HttpPost(url); CloseableHttpResponse res = null; try { StringEntity s = new StringEntity(params,"UTF-8"); if(StringUtils.isBlank(contentType)){ s.setContentType("application/json"); } s.setContentType(contentType); s.setContentEncoding("utf-8"); post.setEntity(s); res = client.execute(post); HttpEntity entity = res.getEntity(); return EntityUtils.toString(entity, "utf-8"); } catch (Exception e) { e.printStackTrace(); } finally { try { res.close(); client.close(); } catch (IOException e) { e.printStackTrace(); } } return ""; } public static String post(String urlStr,String xmlInfo) { String line1 = ""; try { URL url = new URL(urlStr); URLConnection con = url.openConnection(); con.setDoOutput(true); //con.setRequestProperty("Pragma:", "no-cache"); con.setRequestProperty("Cache-Control", "no-cache"); con.setRequestProperty("Content-Type", "text/xml"); OutputStreamWriter out = new OutputStreamWriter(con .getOutputStream()); out.write(new String(xmlInfo.getBytes("utf-8"))); out.flush(); out.close(); BufferedReader br = new BufferedReader(new InputStreamReader(con .getInputStream())); String line = ""; for (line = br.readLine(); line != null; line = br.readLine()) { line1+=line; } return new String(line1.getBytes(),"utf-8"); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } private class CharsetHandler implements ResponseHandler<String> { private String charset; public CharsetHandler(String charset) { this.charset = charset; } public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException { StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() >= 300) { throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); } HttpEntity entity = response.getEntity(); if (entity != null) { if (!StringUtils.isBlank(charset)) { return EntityUtils.toString(entity, charset); } else { return EntityUtils.toString(entity); } } else { return null; } } } private static HttpClient wrapClient(HttpClient base) { try { SSLContext ctx = SSLContext.getInstance("TLSv1"); X509TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { } public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; ctx.init(null, new TrustManager[] { tm }, null); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(ctx, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); return httpclient; } catch (Exception ex) { return null; } } }
RandomStr.java
package com.test.cms.share; import java.util.Random; public class RandomStr { private static char ch[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1' };//最后又重复两个0和1,因为需要凑足数组长度为64 private static Random random = new Random(); //生成指定长度的随机字符串 public static String createRandomString(int length) { if (length > 0) { int index = 0; char[] temp = new char[length]; int num = random.nextInt(); for (int i = 0; i < length % 5; i++) { temp[index++] = ch[num & 63];//取后面六位,记得对应的二进制是以补码形式存在的。 num >>= 6;//63的二进制为:111111 // 为什么要右移6位?因为数组里面一共有64个有效字符。为什么要除5取余?因为一个int型要用4个字节表示,也就是32位。 } for (int i = 0; i < length / 5; i++) { num = random.nextInt(); for (int j = 0; j < 5; j++) { temp[index++] = ch[num & 63]; num >>= 6; } } return new String(temp, 0, length); } else if (length == 0) { return ""; } else { throw new IllegalArgumentException(); } } public static void main(String[] args) { System.out.println(createRandomString(16)); } }
Sha1.java
package com.test.cms.share; import java.security.MessageDigest; public class Sha1 { private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static String getFormattedText(byte[] bytes) { int len = bytes.length; StringBuilder buf = new StringBuilder(len * 2); // 把密文转换成十六进制的字符串形式 for (int j = 0; j < len; j++) { buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); buf.append(HEX_DIGITS[bytes[j] & 0x0f]); } return buf.toString(); } public static String encode(String str) { if (str == null) { return null; } try { MessageDigest messageDigest = MessageDigest.getInstance("SHA1"); messageDigest.update(str.getBytes()); return getFormattedText(messageDigest.digest()); } catch (Exception e) { throw new RuntimeException(e); } } }
控制器类
ShareController.java
package com.test.cms.share; import cn.hutool.json.JSONUtil; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.json.JSONException; import org.json.JSONObject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.net.URI; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; @Controller public class ShareController { /** * 微信公众号的appid */ private String appid="wxd"; /** * 微信公众号的appSecret */ private String secret="ebded33"; /** * 这是跳转到分享的页面 * @return */ @RequestMapping(value = "/to_detail") public String share(){ return "/index/share"; } /** * 获取微信分享配置的请求 方法只写了主要方法,需要根据自己的要求 完善代码 * @param url 前台传过来的当前页面的请求地址 * @return */ @RequestMapping(value = "/get_wx_config") @ResponseBody public String share(String url){ long timestamp = System.currentTimeMillis() / 1000; String noncestr = RandomStr.createRandomString(16); String ticket =getJsapiTicket(); String str = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url; System.out.println("签名str:" + str); String signature = Sha1.encode(str); //这只是简单写法,没有做错误判断 Map map=new HashMap(); map.put("appId",appid); map.put("timestamp",timestamp); map.put("nonceStr",noncestr); map.put("signature",signature); //这里使用了hutool工具将map转为String String json = JSONUtil.toJsonStr(map); return json; } /** * 官方文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62 * * 获取jsapi_ticket * * * 生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。 * 正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。 * 由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。 * * 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token): * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html * * 用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket): * https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi * * @return */ public String getJsapiTicket() { String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"; tokenUrl = tokenUrl + "&appid=" + appid + "&secret=" + secret; JSONObject tokenJson=new JSONObject(); tokenJson=getUrlResponse(tokenUrl); System.out.println("tokenJson:"+tokenJson.toString()); String token=""; try { token=tokenJson.getString("access_token"); } catch (JSONException e) { e.printStackTrace(); System.out.println("报错了"); return null; } String jsapiTicketUrl="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; JSONObject jsapiTickeJson=new JSONObject(); System.out.println("getJsapiTicket:获取token:"+token); if(StringUtils.isNotBlank(token)){ jsapiTicketUrl = jsapiTicketUrl.replace("ACCESS_TOKEN",token); jsapiTickeJson=getUrlResponse(jsapiTicketUrl); System.out.println("tokenJson:"+jsapiTickeJson.toString()); try { return (String) jsapiTickeJson.get("ticket"); } catch (JSONException e) { e.printStackTrace(); return null; } }else{ return null; } } private JSONObject getUrlResponse(String url){ CharsetHandler handler = new CharsetHandler("UTF-8"); try { HttpGet httpget = new HttpGet(new URI(url)); HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); //HttpClient CloseableHttpClient client = httpClientBuilder.build(); client = (CloseableHttpClient) wrapClient(client); return new JSONObject(client.execute(httpget, handler)); } catch (Exception e) { e.printStackTrace(); return null; } } private static HttpClient wrapClient(HttpClient base) { try { SSLContext ctx = SSLContext.getInstance("TLSv1"); X509TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { } public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; ctx.init(null, new TrustManager[] { tm }, null); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(ctx, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); return httpclient; } catch (Exception ex) { return null; } } private class CharsetHandler implements ResponseHandler<String> { private String charset; public CharsetHandler(String charset) { this.charset = charset; } public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException { StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() >= 300) { throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); } HttpEntity entity = response.getEntity(); if (entity != null) { if (!StringUtils.isBlank(charset)) { return EntityUtils.toString(entity, charset); } else { return EntityUtils.toString(entity); } } else { return null; } } } }
页面主要代码、这里展示的是分享给朋友、分享到朋友圈代码,其他分享可以具体看官方接口代码
share.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>分享页面给朋友,朋友圈</title> </head> <body> </body> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script> <script type="text/javascript"> $(function () { //当前页面的url地址 var currUrl = decodeURIComponent(location.href.split('#')[0]); $.ajax({ url: "/get_wx_config", dataType : "json", data: { 'url': currUrl }, error: function (res) { console.log(res); alert("发生错误"); }, success: function (res) { console.log(res); var appId = res.appId; var nonceStr = res.nonceStr; var timestamp = res.timestamp; var signature = res.signature; wx.config({ debug: false, //开启调试模式,开发阶段可以改成true,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: appId, //必填,公众号的唯一标识 timestamp: timestamp, // 必填,生成签名的时间戳 nonceStr: nonceStr, //必填,生成签名的随机串 signature: signature, // 必填,签名,见附录1 jsApiList: [ //必填,需要使用的JS接口列表,所有JS接口列表 见附录2 'updateAppMessageShareData', 'updateTimelineShareData' ] }); wx.ready(function () { //需在用户可能点击分享按钮前就先调用 //分享给朋友”及“分享到QQ” wx.updateAppMessageShareData({ title: '朋友我是标题', // 分享标题 desc: '朋友 我是描述', // 分享描述 link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 imgUrl: '', // 分享图标 success: function (res) { // 设置成功 } }) //分享到朋友圈”及“分享到QQ空间 wx.updateTimelineShareData({ title: '朋友圈我是标题', // 分享标题 link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 imgUrl: '', // 分享图标 success: function (res) { // 设置成功 } }) }); wx.error(function (res) { }); } }); }); </script> </html>
设置公众号的js安全域名
把域名放进去,不用加http的前缀,点击保存即可
保存之前要把文件下载下来放到项目根目录下,如果是开发环境的springboot项目可以参照这个教程做
https://www.cnblogs.com/pxblog/p/13445128.html
-----------------------有任何问题可以在评论区评论,也可以私信我,我看到的话会进行回复,欢迎大家指教------------------------
(蓝奏云官网有些地址失效了,需要把请求地址lanzous改成lanzoux才可以)