app内部分享到微信,QQ都正常
再从微信打开,继续分享给朋友或者朋友圈,就变成下面这个样子了
一切变得那么的。。。无助!!!
开始以为在H5页面上添加一些东西即可,后来发现,完全不是自己想象的那个样子。
这个东西,对于一个从未用过微信JS的码农来说,或许要被带坑里去卡个几天!!!
以下是本人的一点点经验拿来和各位分享,希望刚接触到的少走一些弯路!
思路:
1.需要公众号一个,得到appid,appkey等等
2.当H5页面加载完成,ajax异步请求,调用微信api,获取access_token,获取jsapi_ticket【api的每天调用次数有限,每次调用返回的结果也不同,即,会改变之前的token,之前的就会失效,分享结果也失效】
3.调用api目的就是得到“nonceStr”和“signature”
4.将参数注入H5页面加载、配置微信分享参数【wx.config】
over。。。
官方的示例代码:http://demo.open.weixin.qq.com/jssdk/sample.zip
官方文档:点击打开链接
JAVA代码:
/****************************** js请求接口 ***************/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 用途描述:微信内部点击分享Controller
*
* @author
* @version 1.0.0
*/
@RestController
@RequestMapping(value = "/wechat")
public class WeChatShareController {
private final static Logger LOG = LoggerFactory.getLogger(WeChatShareController.class);
@Autowired
private WeChatShareService weChatShareService;
/**
* 获取微信加密信息
*
* @param request
* @return
*/
@RequestMapping(value = "/getsignature", method = {RequestMethod.GET})
public Map<String, Object> toTranscript(@RequestParam(value = "url", required = true) String url) {
Map<String, Object> data = new HashMap<String, Object>();
try {
// 去数据库查询已保存的微信加密信息token等等(调用微信api获取token和保存的代码下面都有)
Map<String, Object> wxInfo = weChatShareService.queryWechatInfo(url);
data.put("wxInfo", wxInfo);
return MsgCodeUtil.createMsg(MsgCodeUtil.CODE_SUCCESS, data);
} catch (Exception e) {
LOG.error("获取微信加密信息" + e.getMessage(), e);
return MsgCodeUtil.createMsg(MsgCodeUtil.CODE_UNKNOWN_ERROR);
}
}
}
/****************************** WeChatShareService ***************/
/**
* 用途描述:微信内部点击分享Service
*
* @version 1.0.0
*/
@Service
public class WeChatShareService {
@Autowired
private WechatShareTokenDao wechatShareTokenDao;
/**
* 获取微信加密信息
*
* @param url
* @return
* @throws Exception
*/
public Map<String, Object> queryWechatInfo(String url) throws Exception {
Map<String, Object> wxInfo = new HashMap<>();
String accessToken = "";
String jsapiTicket = "";
//1、获取AccessToken和jsapiTicket,先从数据库获取最近一次保存的。
// Map<String, Object> result = wechatShareTokenDao.queryWechatInfo();
// if (null != result && !result.isEmpty()) {
// accessToken = this.objToString(result.get("wechat_access_token"), "");
// jsapiTicket = this.objToString(result.get("wechat_jsapi_ticket"), "");
// }
// 先不存储,直接调用api查询测试(注意:每日查询token次数有限,所以我们要自己定时存储到数据库,
// 每个token是2小时过期,我们间隔30分钟去更新一个就差不多了,时间设置多久自己看着办)
accessToken = WeChatUitl.getAccessToken();
jsapiTicket = WeChatUitl.getTicket(accessToken);
//3、时间戳和随机字符串
long currentTimes = System.currentTimeMillis(); // 时间戳
String noncestr = UUID.randomUUID().toString().replace("-", "").substring(0, 16);//随机字符串
String timestamp = String.valueOf(currentTimes / 1000);// 时间戳
//5、将参数排序并拼接字符串
String params = "jsapi_ticket=" + jsapiTicket + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url;
//6、将字符串进行sha1加密
String signature = WeChatUitl.getSHA1(params);
//7、微信appId
String appId = WeChatUitl.getAppIdWx();
wxInfo.put("appId", appId);
wxInfo.put("accessToken", accessToken);
wxInfo.put("jsapiTicket", jsapiTicket);
wxInfo.put("timestamp", timestamp);
wxInfo.put("nonceStr", noncestr);
wxInfo.put("params", params);
wxInfo.put("signature", signature);
return wxInfo;
}
public static String objToString(Object obj, String def) {
return obj == null ? def : obj.toString();
}
}
****************************** WeChatUitl ***************
import net.sf.json.JSONObject;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
/**
* 用途描述: 微信开发获取信息---微信开放平台
* (这个方法内参数不要去改,说不定会出什么问题,除了tokenWx,appIdWx,appSecretWx,keyWx修改为自己对应值)
*
* @version 1.0.0
*/
public class WeChatUitl {
// 获取token的描述,自己定义就可以了
public static final String tokenWx = "*****_token";
private static String appIdWx = "wx700000000000000"; // 微信appid---微信公众平台
private static String appSecretWx = "71**************************4"; // 微信AppSecret---微信公众平台
// 测试号,可以在微信公众平台直接创建一个,下面我会贴图考诉你怎么创建,这些都是要获取微信token用的
// private static String appIdWx = "wx500000000000000"; // 微信appid---微信公众平台--测试号
// private static String appSecretWx = "8e**************************ad"; // 微信AppSecret---微信公众平台--测试号
// 这个是APP应用的标识,所有微信开发相关的项目(即应用)都有一个appkey,我们的公众号绑定了APP应用
private static String keyWx = "7b**************************d66"; // 微信key 应用签名
public static String getAppIdWx() {
return appIdWx;
}
public static void setAppIdWx(String appIdWx) {
WeChatUitl.appIdWx = appIdWx;
}
public static String getAppSecretWx() {
return appSecretWx;
}
public static void setAppSecretWx(String appSecretWx) {
WeChatUitl.appSecretWx = appSecretWx;
}
public static String getKeyWx() {
return keyWx;
}
public static void setKeyWx(String keyWx) {
WeChatUitl.keyWx = keyWx;
}
/**
* 获取access_token
*
* @return
*/
public static String getAccessToken() {
String access_token = "";
String grant_type = "client_credential";//获取access_token填写client_credential
String AppId = appIdWx;//第三方用户唯一凭证
String secret = appSecretWx;//第三方用户唯一凭证密钥,即appsecret
//这个url链接地址和参数皆不能变
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=" + grant_type + "&appid=" + AppId + "&secret=" + secret;
try {
URL urlGet = new URL(url);
HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
http.setRequestMethod("GET"); // 必须是get方式请求
http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
http.setDoOutput(true);
http.setDoInput(true);
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
http.connect();
InputStream is = http.getInputStream();
int size = is.available();
byte[] jsonBytes = new byte[size];
is.read(jsonBytes);
String message = new String(jsonBytes, "UTF-8");
JSONObject demoJson = JSONObject.fromObject(message);
System.out.println("JSON字符串:" + demoJson);
access_token = demoJson.getString("access_token");
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return access_token;
}
/**
* 获取jsapi_ticket
*
* @param access_token
* @return
*/
public static String getTicket(String access_token) {
String ticket = null;
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi";//这个url链接和参数不能变
try {
URL urlGet = new URL(url);
HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
http.setRequestMethod("GET"); // 必须是get方式请求
http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
http.setDoOutput(true);
http.setDoInput(true);
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
http.connect();
InputStream is = http.getInputStream();
int size = is.available();
byte[] jsonBytes = new byte[size];
is.read(jsonBytes);
String message = new String(jsonBytes, "UTF-8");
JSONObject demoJson = JSONObject.fromObject(message);
System.out.println("JSON字符串:" + demoJson);
ticket = demoJson.getString("ticket");
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return ticket;
}
/**
* SHA、SHA1加密
*
* @parameter: str:待加密字符串
* @return: 加密串
**/
public static String getSHA1(String str) {
try {
MessageDigest digest = java.security.MessageDigest
.getInstance("SHA-1"); //如果是SHA加密只需要将"SHA-1"改成"SHA"即可
digest.update(str.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexStr = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexStr.append(0);
}
hexStr.append(shaHex);
}
return hexStr.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
//1、获取AccessToken
String accessToken = getAccessToken();
// String accessToken = "6_Exc9VRFdPMLeF-4gPaJJGmoo-BJUGzgSJcs3vkT_y4eXPiQzRf1vdMvlVXNE85sfYH9AtQcdd-zptyD5t5S98VXSwIapyMoYjBNfvH7A11GZOoWs2u6agFlLS9NMqzTgN1N5V16BZrL1BnV_WTIaAIAHET";
//2、获取Ticket
String jsapi_ticket = getTicket(accessToken);
//3、时间戳和随机字符串
String noncestr = UUID.randomUUID().toString().replace("-", "").substring(0, 16);//随机字符串
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);//时间戳
System.out.println("accessToken:" + accessToken + "\njsapi_ticket:" + jsapi_ticket + "\n时间戳:" + timestamp + "\n随机字符串:" + noncestr);
//4、获取url
String url = "http://localhost:8080/project/share";
// 根据JSSDK上面的规则进行计算,这里比较简单,我就手动写啦
// String[] ArrTmp = {"jsapi_ticket","timestamp","nonce","url"};
// Arrays.sort(ArrTmp);
// StringBuffer sf = new StringBuffer();
// for(int i=0;i<ArrTmp.length;i++){
// sf.append(ArrTmp[i]);
// }
//5、将参数排序并拼接字符串
String str = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + noncestr + "×tamp=" + timestamp + "&url=" + url;
//6、将字符串进行sha1加密
String signature = Constant.getSHA1(str);
System.out.println("参数:" + str + "\n签名:" + signature);
}
}
****************************** MsgCodeUtil工具类 ***************
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
/**
* 返回code及提示消息
* @version 1.0.0
*/
public class MsgCodeUtil {
/**
* 成功
*/
public final static int CODE_SUCCESS = 1;
/**
* 失败
*/
public final static int CODE_FAILED = 2;
/**
* 无权限
*/
public final static int CODE_UNAUTHORIZED = 401;
/**
* 未知错误
*/
public final static int CODE_UNKNOWN_ERROR = 1000;
private final static String MSG_SUCCESS = "成功!";
private final static String MSG_FAILED = "失败!";
private final static String MSG_UNAUTHORIZED = "无权限访问!";
private final static String MSG_UNKNOWN_ERROR = "系统出错了!";
public static Map<String, Object> createMsg(int code, String msg, Object data) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("code", code);
message.put("msg", setMsg(code, msg));
if (data != null) {
message.put("data", data);
}
return message;
}
public static Map<String, Object> createMsg(int code, String msg) {
return createMsg(code, msg, null);
}
public static Map<String, Object> createMsg(int code, Object obj) {
return createMsg(code, null, obj);
}
public static Map<String, Object> createMsg(int code) {
return createMsg(code, null, null);
}
private static String setMsg(int code, String msg) {
if (code == CODE_SUCCESS) {
return StringUtils.isBlank(msg) ? MSG_SUCCESS : msg;
}
if (code == CODE_FAILED) {
return StringUtils.isBlank(msg) ? MSG_FAILED : msg;
}
if (code == CODE_UNAUTHORIZED) {
return StringUtils.isBlank(msg) ? MSG_UNAUTHORIZED : msg;
}
if (code == CODE_UNKNOWN_ERROR) {
return StringUtils.isBlank(msg) ? MSG_UNKNOWN_ERROR : msg;
}
return msg;
}
}
H5,js代码
<%-- IOS JSON 赋值 getShareBean(); --%>
<input type="hidden" id="shareTitle" name="shareTitle" value="这个是分享标题"/>
<input type="hidden" id="shareContent" name="shareContent" value="这个是分享内容"/>
<input type="hidden" id="shareImage" name="shareImage" value="这里填一个图片全路径"/>
<c:import url="../common_footer.jsp"/>
<%--------------------------------------- 微信分享开始 ---------------------------------%>
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script>
// 只有先这样取值,再赋值给下面的title,desc,imgUrl,link,不然IOS苹果端无法调用成功
var shareTitle = $("#shareTitle").val();
var shareContent = $("#shareContent").val();
var shareImage = $("#shareImage").val();
// 分享出去点击跳转链接必须用这个
var shareUrl = location.href.split('#')[0].toString();
var title = shareTitle;
var desc = shareContent;
// 分享的图片,最好是正方形,不是也没关系,但是一定是http模式,即绝对路径,而不是服务器路劲
var imgUrl = shareImage;
// 该链接域名或路径必须与当前页面对应的公众号JS安全域名一致-----这个特别重要,请看清楚
// 这里的地址可以写死,也可以动态获取,但是一定不能带有微信分享后的参数,不然分享也是失败的
var link = shareUrl;
</script>
<%----这个页面写公共的JS方法,在下面-----%>
<c:import url="../wechat_share.jsp"/>
<%----或者封装成js文件再引入-----%>
<%----<script src="js/wechat.js"></script>-----%>
<%--------------------------------------- 微信分享结束 ---------------------------------%>
/****************************** wechat_share.jsp ***************/
<script>
// 当前页面访问路径
var url = location.href.split('#')[0].toString();
var getUrl = "wechat/getsignature";
$.get(getUrl,
{"url": url}).done(function (data) {
// 注意这里的url,一定要这样写,也就是动态获取,不然也不会成功的。
// console.log(data);
// console.log(data.code);
if (data.code == 1) {
var wxInfo = data.wxInfo;
if (wxInfo.signature != null) {
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: wxInfo.appId, // 必填,公众号的唯一标识
timestamp: wxInfo.timestamp, // 必填,生成签名的时间戳
nonceStr: wxInfo.nonceStr, // 必填,生成签名的随机串
signature: wxInfo.signature,// 必填,签名,见附录1
jsApiList: [
'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone'
] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
}
}
}).fail(function (msg) {
// console.log("error:" + msg);
});
// 分享给朋友、QQ、微博
var shareData = {
"imgUrl": imgUrl,
"title": title,
"desc": desc,
'link': link,
success: function() {
// layer.msg("分享成功~", {});
},
cancel: function() {
// layer.msg("取消分享");
}
};
// 分享到朋友圈
var shareToTimeline = {
"imgUrl": imgUrl,
"title": title,
'link': link,
success: function() {
// layer.msg("分享成功~", {});
},
cancel: function() {
// layer.msg("取消分享");
}
}
wx.ready(function () {
wx.onMenuShareTimeline(shareToTimeline); // 分享到微信朋友圈
wx.onMenuShareAppMessage(shareData); // 分享给微信朋友
wx.onMenuShareQQ(shareData); // 分享到QQ
wx.onMenuShareQZone(shareData); // 分享到QQ空间
wx.onMenuShareWeibo(shareData); // 分享到微博
wx.error(function (res) {
alert(res.errMsg);
});
});
</script>
第二步:
先登录微信公众号平台
【调用次数有限,请注意】
【确认签名算法是否正确】
http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
可用此页面工具进行校验
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
第三步:
接下来最重要的步骤:
1,最好先去申请一个域名(花生壳),用于测试,不然只有发布到线上测试了【微信只认域名,不认ip+端口】
2,在公众号后台添加IP白名单
本机IP获取方式
调用“获取access_token”接口,返回结果。
错误信息显示ip无权限,就是这个。
在这里也一起把线上服务器的IP也一起设置进去,因为每次设置的时候都要找老板的手机来扫微信二维码,脑壳疼
注意,如果线上服务器ip地址有变动,必须修改微信公众号ip白名单配置
第四步:
配置公众号业务域名和js安全域名
按照步骤下载文件,将文件复制到项目根目录和tomcat项目根目录
在浏览器能访问到文件就OK
比如项目名是:tb_project
本地测试调用:localhost:8080/tb_project/querylist
花生壳调用接口方式:xxx.xicp.io/querylist
到这里,就完全到位了,over
关于定时保存token到自己的服务器,推荐用 spring scheduled,简单,方便,实用
spring_scheduled.xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<!-- 设置定时任务 -->
<task:annotation-driven/>
<!--扫描包路径-->
<context:component-scan base-package="com.nas.timed"/>
<!--注意这边需要配置供扫描的类-->
<bean id="pickDealOrderTask" class="com.***.***.pickTask"/>
<!--开启定时任务-->
<task:scheduled-tasks scheduler="scheduler">
<task:scheduled ref="pickTask" method="tickers" cron="0 */30 * * * ?"/> <!--每30分钟执行一次,cron表达式很多种写法,问一下度娘-->
</task:scheduled-tasks>
<!--最大线程数-->
<task:scheduler id="scheduler" pool-size="9" />
</beans>
配置文件加好之后,PickTask这个类下面的【tickers】方法,30分钟执行一次。
具体使用方法,问度娘。
有关数据存储做好之后,最好再存储一下缓存,比如redis,每次分享就不用去查询数据库了