注意:实现此功能需要做的准备工作。
(1:):准备一个认证过的微信公众号。
(2):登录微信公众后台配置ip白名单,添加域名。
(3):获取公众号的app_id和app_secret
1. 在要分享的链接页面中(一般是首页),<head>标签中引入微信官方js
<script type="text/javascript" src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
2.把下面这段js代码,放到页面中的任意位置,或者封装到js文件中,引入到页面。
注意:需要修改自己的后台请求路径,编辑文案,修改图标路径.
<script type="text/javascript">
var appId = ""; // 必填,公众号的唯一标识
var timestamp = ""; // 必填,生成签名的时间戳
var nonceStr = ""; // 必填,生成签名的随机串
var signature = "";// 必填,签名,见附录1
var lineLink = window.location.href; //分享的网页,地址完整地址如:https://xxx.com/xxx/xxx.html
$(function(){
//发送ajax请求后台微信接口,返回需要的一些参数,见下面success中
$.ajax({
url : 'https://tests.wanxue.cn/shenlongpc/promote/getWXParam',//后台请求路径
type : "POST",
data : {
url : lineLink
},
//后台响应回的数据
success : function(res) {
var r = eval(res);
appId = r.appId;
timestamp = r.timestamp;
nonceStr = r.nonceStr;
signature = r.signature;
wx_fx();
}
});
})
function wx_fx(){
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appId, // 必填,公众号的唯一标识
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: nonceStr, // 必填,生成签名的随机串
signature: signature,// 必填,签名,见附录1
jsApiList: [
'onMenuShareTimeline', //分享给好友
'onMenuShareAppMessage' //分享到朋友圈
] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
wx.ready(function () {
//自定义分享,小图标,标题,内容
var imgUrl = "http://xxx.com/images/img.png"; (填写完整的图标地址)
var shareTitle = "此处填写分享的标题";
var descContent = "此处填写分享的内容";
//分享到朋友圈
wx.onMenuShareTimeline({
title: shareTitle,
link: lineLink,
imgUrl: imgUrl,
success: function (res) {
alert("分享成功!")
},
cancel: function (res) {
alert("取消分享!")
},
fail: function (res) {
alert("分享失败!");
}
});
//分享给朋友
wx.onMenuShareAppMessage({
title: shareTitle,
desc: descContent ,
link: lineLink,
imgUrl: imgUrl,
success: function (res) {
/*shared(shareLink, "friend", shareGid);*/
alert("分享给朋友成功!");
},
fail: function (res) {
alert("分享给朋友失败!" + JSON.stringify(res));
}
});
});
}
</script>
3.编写后台代码,根据公众号的app_id/app_secret获取tiket。
@RequestMapping("/promote")
@Controller
public class PromoteController {
@RequestMapping("/getWXParam")
@ResponseBody
public Map<String,String> getWXParam(String url){
//ticket是公共的,所有用户进来用的都是一个ticket.微信官方规定ticket失效时间两小时,每天的获取次数也有限制,所以每次进入都先从redis中获取ticket,存取redis的方法需要根据自己项目修改.或者存到数据库中也可以.
String ticket = RedisPoolTools.getRedis(RedisPoolTools.getDatabase(),"redis_ticket");
//判断ticket是否失效
if("".equals(ticket) || null == ticket){
//微信公众号的appId和secret
String appId = "wx91fc8777e7516707";
String secret= "254001781918a47a79643656226e8543";
//请求微信官方接口,获取access_token
String s = UrlTools.INSTANCE.httpURLConnection(null,null,"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId+"&secret="+secret,"GET");
JSONObject res1Json = JSONObject.parseObject(s);
Map<String,Object> wxBack = (Map<String,Object>)res1Json;
System.out.println("wxBack==="+wxBack);
String access_token = wxBack.get("access_token").toString();
//请求微信接口,获取ticket
String url2 = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ access_token +"&type=jsapi";
System.out.println("获取jsapi_ticket的地址==="+url2);
String res2 = UrlTools.INSTANCE.httpURLConnection(null, null, url2, "GET");
System.out.println("获取jsapi_ticket的结果==="+res2);
Map<String,Object> resMap2 = JsonUtil.parseJSON2Map(res2);
try{
ticket = resMap2.get("ticket").toString();
//把取到的ticket存到redis中(ticket默认两小时失效),设置失效时间7140s,也就是119分钟
RedisPoolTools.setRedis(RedisPoolTools.getDatabase(),"redis_ticket",ticket,7140);
}catch (Exception e){
throw new RuntimeException("获取jsapi_ticket失败!");
}
}
//调用工具类
return jsApiUtil.sign(ticket,url);
}
}
再次注意:
- 只有使用企业认证的公众号才能使用自定义分享的功能.代码中的appid和secret填写使用的公众号的.
- 把线上服务器的ip地址和域名配置到公众号后台,把微信认证的一个.txt文件放到服务器的域名根目录下,我的项目springBoot项目,放在了服务器的static目录下了
- 1:使用的工具类1: jsApiUtil
public class jsApiUtil {
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 string1;
String signature = "";
//注意这里参数名必须全部小写,且必须有序
string1 = "jsapi_ticket=" + jsapi_ticket +
"&noncestr=" + nonce_str +
"×tamp=" + timestamp +
"&url=" + url;
System.out.println(string1);
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
//Map<String,String> wxInfo = WXInfoApplication.getWXInfo(CookieUtil.getWxRegion());
//微信公众号的appid,使用时需要修改
String appId = "wx91fc8777e7516707";
ret.put("url", url);
ret.put("appId",appId);
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);
}
}
4.2:使用的工具类2,把json转成map
public class JsonUtil {
/**
* json转map
* @param jsonStr
* @return
*/
public static Map<String, Object> parseJSON2Map(String jsonStr){
Map<String, Object> map = new HashMap<String, Object>();
if(jsonStr!=null && !jsonStr.equals("")){
if(jsonStr.indexOf("[")==0){
jsonStr=jsonStr.substring(1,jsonStr.length()-1);
}
//最外层解析
JSONObject json = JSONObject.fromObject(jsonStr);
for(Object k : json.keySet()){
Object v = json.get(k);
//如果内层还是数组的话,继续解析
if(v instanceof JSONArray){
List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
Iterator<JSONObject> it = ((JSONArray)v).iterator();
while(it.hasNext()){
JSONObject json2 = it.next();
list.add(parseJSON2Map(json2.toString()));
}
map.put(k.toString(), list);
} else {
if(v.equals(null)){
v=new String();
}
map.put(k.toString(), v);
}
}
}
return map;
}
}
5.使用的工具类3:请求外部接口工具类,可换成自己的方式发送http请求
public enum UrlTools {
INSTANCE;
/**
* lyc
* 接口调用 get/POST
*/
public String httpURLConnection (Map<String,Object> headers, String param, String url, String method) {
String res = "";
InputStream in = null;
InputStreamReader isr = null;
BufferedReader br = null;
HttpURLConnection connection = null;
PrintWriter out = null;
try {
URL u = new URL(url);
// 将url 以 open方法返回的urlConnection 连接强转为HttpURLConnection连接 (标识一个url所引用的远程对象连接)
// 此时cnnection只是为一个连接对象,待连接中
connection = (HttpURLConnection) u.openConnection();
// 设置连接输出流为true,默认false (post 请求是以流的方式隐式的传递参数)
connection.setDoOutput(true);
// 设置连接输入流为true
connection.setDoInput(true);
// 设置请求方式为post
connection.setRequestMethod(method);
// post请求缓存设为false
connection.setUseCaches(false);
// 设置该HttpURLConnection实例是否自动执行重定向
connection.setInstanceFollowRedirects(true);
//addRequestProperty添加相同的key不会覆盖,如果相同,内容会以{name1,name2}
connection.setRequestProperty("Accept", "*/*");
connection.setRequestProperty("Content-Type", "application/json;charset=utf-8");
if(null != headers){
setProperty(headers,connection);
}
// 建立连接 (请求未开始,直到connection.getInputStream()方法调用时才发起,以上各个参数设置需在此方法之前进行)
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
if("POST".equals(method) || "PUT".equals(method) && null != param){
out = new PrintWriter(new OutputStreamWriter(connection.getOutputStream(),"utf-8"));
out.println(param);
out.close();
}
if(connection.getResponseCode() == HttpURLConnection.HTTP_OK ||
connection.getResponseCode() == HttpURLConnection.HTTP_CREATED ||
connection.getResponseCode() == HttpURLConnection.HTTP_ACCEPTED){
in = connection.getInputStream();
}else{
in = connection.getErrorStream();
}
isr = new InputStreamReader(in);
br = new BufferedReader(isr);
String line = "";
StringBuilder content = new StringBuilder();
while ((line = br.readLine()) != null) {
content.append(line);
}
res = content.toString().trim();
return res;
} catch (Exception e) {
//logger.error(" ** 接口访问失败 - "+url +"\r\n 错误信息:" + e.getClass()+" - "+e.getMessage());
e.printStackTrace();
throw new RuntimeException("接口访问失败");
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
}
}
if (null != isr) {
try {
isr.close();
} catch (IOException e) {
}
}
if (null != in) {
try {
in.close();
} catch (IOException e) {
}
}
if (null != out) {
try {
out.close();
} catch (Exception e) {
}
}
if(null != connection){
try {
connection.disconnect(); // 销毁连接
} catch (Exception e) {
}
}
}
}
//lyc
private void setProperty(Map<String,Object> headers,HttpURLConnection connection){
if(headers.containsKey("v")){
connection.setRequestProperty("v",headers.get("v").toString());
}
if(headers.containsKey("p")){
connection.setRequestProperty("p",headers.get("p").toString());
}
if(headers.containsKey("d")){
connection.setRequestProperty("d",headers.get("d").toString());
}
if(headers.containsKey("t")){
connection.setRequestProperty("t",headers.get("t").toString());
}
if(headers.containsKey("a")){
connection.setRequestProperty("a",headers.get("a").toString());
}
if(headers.containsKey("Access-Control-Allow-Origin")){
connection.setRequestProperty("Access-Control-Allow-Origin",headers.get("Access-Control-Allow-Origin").toString());
}
/*if(headers.containsKey("Content-Type")){
connection.setRequestProperty("Content-Type",headers.get("Content-Type").toString());
}*/
}
}
注意:后台代码使用的时候需要修改的内容:
- 存取redis的方式,改成自己的
- 微信公众号的appid和secret改成你使用的公众号
- 不要忘了把自己服务器的ip和项目域名配置公众号后台,进行认证.
前端代码使用注意:
- 不要忘记引入使用微信的js文件jweixin-1.6.0.js
- 修改后台请求路径
- 修改js代码中的图标地址,编辑标题和内容文案.
6.效果