1 准备工作
在微信浏览器内部实现分享链接卡片效果,如下所示:
1.1 注册公众号
首先需要注册一个公众号,进入微信公众平台,点击立即注册。
选择公众号。
设置邮箱和密码,选择中国大陆(只有一个选项),个人只能选择公众号,企业可以选择服务号。
主体类型要选择企业,否则会导致权限不足,无法实现分享卡片。
设置一些公众号的基本信息完成注册。
1.2 设置
在公众号界面的开发接口管理选项设置AppID和AppSecret,这两个值一定要记住后面会用到。
确认此处是否为已获得状态,当前演示为个人账户,此处无法通过。
在帐号设置 - 功能设置添加js安全域名,添加要分享的域名地址。
下载验证文件,然后将该文件放入到域名所对应的前端项目部署所在目录下才可以设置成功。
在设置与开发 - 安全中心中添加域名所对应的公网ip,例如:10.1.20.25
2 前端
在项目中安装微信开发工具包。
npm i weixin-js-sdk
在要分享的页面设置分享卡片信息,其中appId、timestamp、nonceStr、signature这四个值需要后端服务器提供。
// 初始化微信配置
const wechatConfig = () => {
if (/micromessenger/i.test(navigator.userAgent)) {
let currentUrl = window.location.href
currentUrl = currentUrl.split('#')[0] // 移除hash部分
const encodedUrl = encodeURIComponent(currentUrl) // 对URL进行编码
// 获取配置信息,需要将当前页面地址传入到后端
getWechatConfigApi(encodedUrl).then((res) => {
wx.config({
debug: false,
appId: res.appId,
timestamp: res.timestamp,
nonceStr: res.nonceStr,
signature: res.signature,
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'],
})
// 配置成功后,调用分享接口
wx.ready(function () {
// 分享到朋友
wx.updateAppMessageShareData({
title: '分享标题',
desc: '美化结果',
link: currentUrl, // 分享链接
imgUrl: 'https://xxx.png', // 卡片图片,只能是https请求
success: function () {
console.log('分享内容设置成功')
},
fail: function (err) {
console.error('分享内容设置失败:', err)
},
})
// 分享到朋友圈
wx.updateTimelineShareData({
title: '分享标题',
link: currentUrl, // 分享链接
imgUrl: 'https://xxx.png', // 卡片图片,只能是https请求
success: function () {
console.log('分享内容设置成功')
},
fail: function (err) {
console.error('分享内容设置失败:', err)
},
})
})
wx.error(function (res) {
console.error('微信JS-SDK配置失败:', res)
})
})
}
}
onMounted(() => {
wechatConfig()
})
3 后端
此处使用java代码进行演示,根据URL获取微信JS-SDK服务
package org.dromara.bkk.common.config;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
import java.util.Map;
/**
* 类描述 -> 微信JS-SDK服务
*
* @Author: ywz
* @Date: 2025/07/03
*/
@Service
public class WechatJsSdkService {
// 微信相关配置,从公众号获取(上面1.2章节中)
@Value("${wx.appId}")
private String appId;
@Value("${wx.appSecret}")
private String appSecret;
@Resource
private StringRedisTemplate stringRedisTemplate;
private final RestTemplate restTemplate = new RestTemplate();
// 获取access_token的URL
private static final String ACCESS_TOKEN_URL =
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}";
// 获取jsapi_ticket的URL
private static final String JSAPI_TICKET_URL =
"https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token={access_token}";
/**
* 获取JS-SDK配置所需的所有参数
*
* @param url 当前页面URL
* @return 包含所有配置参数的Map
*/
public Map<String, String> getJsSdkConfig(String url) {
// 1. 获取jsapi_ticket
String jsapiTicket = stringRedisTemplate.opsForValue().get("jsapi_ticket:" + url);
if (StringUtils.isBlank(jsapiTicket)) {
// 从微信服务器获取jsapi_ticket
jsapiTicket = getJsapiTicket();
// 获取jsapi_ticket每日有次数限制,需要缓存jsapi_ticket,有效期为7200秒
stringRedisTemplate.opsForValue().set("jsapi_ticket:" + url, jsapiTicket, Duration.ofSeconds(7200));
}
if (StringUtils.isBlank(jsapiTicket)) {
throw new RuntimeException("获取jsapi_ticket失败");
}
// 2. 生成签名
Map<String, String> config = WechatJsSdkSignGenerator.generateSignature(jsapiTicket, url);
// 添加appId
config.put("appId", appId);
return config;
}
/**
* 获取jsapi_ticket
*/
private String getJsapiTicket() {
// 1. 获取access_token
String accessTokenUrl = ACCESS_TOKEN_URL.replace("{appid}", appId).replace("{secret}", appSecret);
String result = restTemplate.getForObject(accessTokenUrl, String.class);
JSONObject json = JSONObject.parseObject(result);
String accessToken = json.getString("access_token");
if (StringUtils.isBlank(accessToken)) {
throw new RuntimeException("获取access_token失败: " + result);
}
// 2. 获取jsapi_ticket
String jsapiTicketUrl = JSAPI_TICKET_URL.replace("{access_token}", accessToken);
result = restTemplate.getForObject(jsapiTicketUrl, String.class);
json = JSONObject.parseObject(result);
if (json.getIntValue("errcode") == 0) {
return json.getString("ticket");
} else {
throw new RuntimeException("获取jsapi_ticket失败: " + result);
}
}
}
微信JS-SDK签名生成器
package org.dromara.bkk.common.config;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 类描述 -> 微信JS-SDK签名生成器
*
* @Author: ywz
* @Date: 2025/07/03
*/
@Slf4j
public class WechatJsSdkSignGenerator {
/**
* 生成JS-SDK配置所需的签名
*
* @param jsapiTicket jsapi_ticket
* @param url 当前网页的URL,不包含#及其后面部分
* @return 包含签名所需各项参数的Map
*/
public static Map<String, String> generateSignature(String jsapiTicket, String url) {
Map<String, String> ret = new HashMap<>();
// 生成随机字符串
String nonceStr = createNonceStr();
// 生成时间戳
String timestamp = createTimestamp();
String signature = "";
// 注意这里参数的顺序要按照 key 的 ASCII 码从小到大排序
String string1 = "jsapi_ticket=" + jsapiTicket +
"&noncestr=" + nonceStr +
"×tamp=" + timestamp +
"&url=" + url;
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes(StandardCharsets.UTF_8));
signature = byteToHex(crypt.digest());
} catch (Exception e) {
log.error("生成签名失败", e);
}
ret.put("url", url);
ret.put("jsapi_ticket", jsapiTicket);
ret.put("nonceStr", nonceStr);
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 createNonceStr() {
return UUID.randomUUID().toString();
}
private static String createTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
}