这段时间有个需求,大概是老板看到别人发的产品链接是卡片形式展示的,而我们分享出去就是个http链接,感觉不太靠谱。
微信官方文档地址: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
于是就改呗。这里做下开发记录,以备后需
1. 卡片链接的必要条件
- 必须要有微信公众号
- 使用jssdk进行开发即可
3. 开发流程
- 公众号获取appid,AppSecret, 并且配置Ip白名单。
设置与开发-基本配置
- h5中引入微信的jssdk
- 公众号配置js安全域名
设置与开发-公众号设置-功能设置-JS接口安全域名
- 开发后端签名代码, 参考下文
- h5端注册响应事件,参考下文
上述流程后在微信打开链接,点击分享给朋友,分享出去就是卡片形式啦。其实还是很简单的。
以下代码复制后即可使用。
h5端示例代码:
// js 这里需要每次请求后端生成分享的签名参数信息
fetch(`/api/wxshareinfo?q=${encodeURIComponent(location.href)}`).then(res => res.json()).then(res => {
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: 'xxxxxx', // 必填,公众号的唯一标识
timestamp: res.timestamp, // 必填,生成签名的时间戳
nonceStr: res.nonceStr, // 必填,生成签名的随机串
signature: res.signature ,// 必填,签名
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData'
]
});
// 通过error接口处理失败验证
wx.error(function (errres) {
console.info("失败:", errres);
});
wx.ready(function () { //需在用户可能点击分享按钮前就先调用
var sharedata = {
title: 'hello', // 分享标题
desc: `hello!`, // 分享描述
link: res.url, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: location.protocol + "//" + location.host+ "/logo.png", // 分享图标链接,png格式
success: function () {
// 设置成功
}
}
wx.updateAppMessageShareData(sharedata);
wx.updateTimelineShareData(sharedata);
});
});
后端代码:
@GetMapping(value = "/api/wxshareinfo")
@ResponseBody
public Map<String, String> wxshareinfo(@RequestParam("q") String url) throws UnsupportedEncodingException {
String jsApiTicket = null;
try {
jsApiTicket = jsapi_ticketGetUtil.getJSApiTicket();
Map<String, String> sign = jsapi_ticketGetUtil.sign(jsApiTicket, url);
return sign;
} catch (IOException e) {
logger.error("wxshare error", e);
}
return null;
}
工具类代码,这里需要啰嗦一句:
里面的access_token,和 jsticket这两个需要做缓存的,官方7200秒失效,我这里缓存了7000秒。
package com.springboot.demo.zhifu;
import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.springboot.demo.controller.BatchUpdateAgreeController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class jsapi_ticketGetUtil {
public static final String APPID = "xxxx";
public static final String APPSecret = "xxxxxxxxx";
private static Logger logger = LoggerFactory.getLogger(jsapi_ticketGetUtil.class);
public static final Cache<String, String> cache = CacheBuilder
.newBuilder().expireAfterWrite(6000, TimeUnit.SECONDS).build();
/***
* 获取jsapiTicket
* @return
*/
public static String getJSApiTicket() throws FileNotFoundException, IOException {
String getJSApiTicket = cache.getIfPresent("getJSApiTicket");
if(Objects.nonNull(getJSApiTicket)) {
return getJSApiTicket;
}
//获取token
String acess_token= getAccessToken();
String urlStr = //"http://gy7tgr.natappfree.cc//wxapi/weixinMall/index.jsp";
"https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+acess_token+"&type=jsapi";
String backData=jsapi_ticketGetUtil.sendGet(urlStr, "utf-8", 10000);
logger.info("jsapiticket 远程返回 {}", backData);
String ticket = (String) JSON.parseObject(backData).getString("ticket");
cache.put("getJSApiTicket", ticket);
return ticket;
}
/***
* 获取acess_token
* @return
*/
public static String getAccessToken() throws FileNotFoundException, IOException{
String getAccessToken = cache.getIfPresent("getAccessToken");
if(Objects.nonNull(getAccessToken)) {
return getAccessToken;
}
String appid = APPID;
String appSecret = APPSecret;
String url ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appid+"&secret="+appSecret+"";
String backData=jsapi_ticketGetUtil.sendGet(url, "utf-8", 10000);
logger.info("accesstoken 远程返回 {}", backData);
String accessToken = (String) JSON.parseObject(backData).getString("access_token");
cache.put("getAccessToken", accessToken);
return accessToken;
}
public static Map<String, String> sign(String jsapi_ticket, String url) throws FileNotFoundException, IOException {
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();
}
//自动获取WxTokenUtil.properties中appid属性,避免appid改动时重复更改
String appId = APPID;
ret.put("url", url);
//注意这里 要加上自己的appId
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);
}
/***
* 模拟get请求
* @param url
* @param charset
* @param timeout
* @return
*/
public static String sendGet(String url, String charset, int timeout)
{
String result = "";
try
{
URL u = new URL(url);
try
{
URLConnection conn = u.openConnection();
conn.connect();
conn.setConnectTimeout(timeout);
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset));
String line="";
while ((line = in.readLine()) != null)
{
result = result + line;
}
in.close();
} catch (IOException e) {
return result;
}
}
catch (MalformedURLException e)
{
return result;
}
return result;
}
}
4. 常见问题
- debug模式打开后, 页面打开没有任何提示。 这里检查下jssdk是否正确引入。config代码是否执行
wx.ready
方法不执行。这里重点检查下config方法是否执行- 弹出提示签名错误,这里主要检查appid,和secrekey是否正确。可以使用 官方签名工具来验证签名是否正确
- 所有配置都正确但是提示授权失败,这里需要检查公众号性质。个人注册的公众号没有也无法获取分享功能接口使用的。在设置开发-接口权限那里可以看到
- 链接发送出去后是链接形式,不是卡片形式。这是由于微信要求链接只能通过公众号菜单分享,或者将链接生成二维码,用户扫码后再分享,这两种情况下分享出去才是卡片。