网站集成微信分享功能

本文最后更新于:2025年2月18日 下午

网站集成微信分享功能

说明:文章具有时效性,若您此刻的时间距离文章发布时间过长,无法保证此文章依旧有效,请自行斟酌。本文写于2024/10/26

序言

写在前面的话

要想实现在微信分享出去的链接携带有网站图标网站标题网站描述。只能按照微信官方文档中的要求,使用微信js文件的SDK

这种必须是在微信中用微信自带的浏览器打开的网页下才能生效。**而且不能是直接复制链接发送给好友然后点开网站的方式进去,这种状态下即便进去,分享网站后,出来的内容也只有一条网址而已。对于已经生成带有图标标题和描述的分享卡片,这种类型的链接点进去打开网站是可以正常分享出这种效果的。**普通网站想分享还是应该采用二维码展示的方式。

如果你的网站要适配移动端,希望在微信打开后方便分享给其他微信用户或者朋友圈,那我这篇文章将会有所帮助。

准备

一、微信服务号

首先,要实现这种类似的功能需要一个企业认证的微信服务号,而且认证必须在有效期内。若只是个人测试学习,可以申请使用公众平台测试号。测试号拥有全部功能,会生成一个虚拟的微信公众号,需要关注这个号,才能正常使用。

二、服务器

一台拥有公网IP的服务器。**因为微信公众平台需要测试你要引入此功能的项目地址是可用的。**若是个人测试,为了尽量节省开销,可以考虑开通个按量付费的服务器,选择停机不收费,用的时候打开,不用的时候关掉就好了,只不过每次启用公网IP地址都会变。按自己需求来吧,这里不再赘述。

配置

测试号与微信服务号信息位置对照表

测试号 服务号
测试号信息 设置与开发 > 开发接口管理 > 基本配置 > 公众号开发信息
接口配置信息 设置与开发 > 开发接口管理 > 基本配置 > 服务器配置
JS接口安全域名 设置与开发 > 公众号设置 > 功能设置 > JS接口安全域名

微信服务器接口配置

这里需要你的项目中开放一个供微信请求的接口,微信通过成功调用这个接口来判断你的项目是否可用。

注:由于测试号无法指定令牌加密方式,所以接下来就以明文模式来展示后端接口的示例。

其他加密模式请参阅官方文档自行实现。当然你不验证token直接返回echostr,也能直接验证成功!成功后,即便再次启动项目,接口配置信息也无需再次配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 此接口用于给微信验证服务器是否可用【微信接口配置信息 >>> 验证消息的确来自微信服务器】
* @param signature 签名,微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
* @return 原样返回微信请求携带的随机字符串,即可通过微信的服务器验证
* @see <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html#%E7%AC%AC%E4%BA%8C%E6%AD%A5%EF%BC%9A%E9%AA%8C%E8%AF%81%E6%B6%88%E6%81%AF%E7%9A%84%E7%A1%AE%E6%9D%A5%E8%87%AA%E5%BE%AE%E4%BF%A1%E6%9C%8D%E5%8A%A1%E5%99%A8">验证消息的确来自微信服务器</a>
*/
@GetMapping("/wxCheck")
public String wxCheck(
@RequestParam(value = "signature")
String signature,
@RequestParam(value = "timestamp")
String timestamp,
@RequestParam(value = "nonce")
String nonce,
@RequestParam(value = "echostr")
String echostr
){
// 验证消息是否来自微信服务器
String token = "myWXToken";
String[] arr = {token, timestamp, nonce};

// 按字典序排序
Arrays.sort(arr);

// 使用StringBuilder进行拼接
StringBuilder result = new StringBuilder();
for (String s : arr) {
result.append(s);
}
// 将字符串加密
String s = SHA1(String.valueOf(result));
// 将加密后的密文与签名匹配,失败则说明项目内的token与服务器配置的token不一致!
if (!s.equals(signature)) {
System.out.println("Token值匹配失败!");
// 返回给微信空值,说明验证不通过
return null;
}
System.out.println("Token值匹配成功!");
// 将请求携带的参数加入Map集合
Map<String,Object> map = new HashMap<>();
map.put("signature",signature);
map.put("timestamp",timestamp);
map.put("nonce",nonce);
map.put("echostr",echostr);
// 打印Map集合查看微信的消息
System.out.println("来自微信的消息:"+map);
// 返回给微信自己传来的随机字符串,则验证通过
return echostr;
}

注意:需要先启动项目,再配置接口,因为确定配置接口时,微信会请求我们填写的请求接口。当成功返回微信本身请求携带的随机字符串给他后,验证成功!

JS接口安全域名

这里配置服务器地址或绑定服务器的已备案的域名。

这里测试号与服务号有些区别:

服务号的官方提示信息:

设置JS接口安全域名后,公众号开发者可在该域名下调用微信开放的JS接口。

注意事项:

1、可填写五个域名或路径(例:wx.qq.comwx.qq.com/mp),需使用由字母、数字、“-”或中文组成的合法域名,不支持IP地址、端口号及短链域名。

2、填写的域名须通过ICP备案的验证。

3、 将文件MP_verify_ffPvlU5jlXYhLLC2.txt上传至填写域名或路径指向的web服务器(或虚拟主机)的目录(若填写域名,将文件放置在域名根目录下,例如wx.qq.com/MP_verify_ffPvlU5jlXYhLLC2.txt;若填写路径,将文件放置在路径目录下,例如wx.qq.com/mp/MP_verify_ffPvlU5jlXYhLLC2.txt),并确保可以访问。

4、 一个自然月内最多可修改并保存五次,本月剩余保存次数:4

服务号,需要项目开放静态资源路径以供微信访问,若开启了拦截器或过滤器,则需要将此放行。


测试号的提示信息:

设置JS接口安全域后,通过关注该测试号,开发者即可在该域名下调用微信开放的JS接口

IP白名单

服务号需要将服务器的ip地址配置在白名单里,否则会报异常。

流程

  1. 引入微信SDKjs文件
  2. js携带当前页地址参数请求后端接口
  3. 后端通过appidappsecret参数,使用OAuth2.0的客户端认证模式,请求获取微信access_token
  4. 后端通过access_token获取api_ticket
  5. 生成当前时间戳timestamp和随机字符串nonceStr
  6. 通过SHA1签名算法生成签名signature
  7. 将签名signature,时间戳timestamp,随机字符串nonceStr和appID返回给前端。
  8. 前端接收到信息后,完成微信配置
  9. 前端定义分享的格式。
  10. 测试微信分享效果。

开始

前端页面完整代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<!DOCTYPE html>
<html>
<head>
<title>微信分享示例</title>
<meta charset="UTF-8">
</head>
<body>
<h1>欢迎使用微信分享功能!</h1>

<!-- 引入微信JS SDK -->
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script src="/static/dist/js/jquery-3.6.0.min.js"></script>
<script>
$.ajax({
async : false,
type: "POST",
url: "/demo",
data : {
URL : encodeURIComponent(window.location.href.split("#")[0])
},
success: function (data) {
console.log(data);
// 微信JS SDK配置
wx.config({
debug: true, // 开启后,微信打开网页后,会显示配置是否成功的信息
appId: data.appId, // 替换为您的微信公众平台App ID
timestamp: data.timestamp, // 替换为您的服务器时间戳
nonceStr: data.nonceStr, // 替换为您的随机字符串
signature: data.signature, // 替换为您的签名
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData'
] // 需要使用的微信JS接口列表
});
console.log("wx配置结束!");
// 页面加载完成后的回调函数
wx.ready(function () {
// 在这里编写分享到朋友圈和分享给好友的代码
shareData = { // 标题和描述这里我是从localStorage取的值,可自定义
title: localStorage.getItem("title"), // 分享标题
desc: localStorage.getItem("Description"), // 分享描述
link: window.location.href, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: "http://你的域名/你的图片.jpg", // 分享图标
success: function () {
console.log('分享成功');
},
cancel: function () {
// 取消分享的回调函数
console.log('取消分享');
}
};
// 分享到朋友圈
wx.updateTimelineShareData(shareData);
// 分享给好友
wx.updateAppMessageShareData(shareData);
});

// 页面卸载前的回调函数,用于清理资源等操作
wx.error(function () {
console.log('微信JS SDK加载失败');
});

},
error: function (xhr, status, error) {
console.error('提交失败: ', error);
}
})
</script>
</body>
</html>

后端接口:

demo页面控制,引入thymeleaf视图解析器依赖测试即可。

1
2
3
4
5
6
7
8
@Controller
public class IndexController {

@GetMapping("/demo")
public String demo() {
return "demo";
}
}

请求接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/**
* 集成微信登录控制器
* @className WechatController
* @see <a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html">微信官方文档</a>
*/
@Controller
@RequiredArgsConstructor
public class WxController {

/**
* 此接口用于给微信验证本地服务器是否可用【微信接口配置信息 >>> 验证消息的确来自微信服务器】
* @param signature 签名,微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
* @return 原样返回微信请求携带的随机字符串,即可通过微信的服务器验证
* @see <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html#%E7%AC%AC%E4%BA%8C%E6%AD%A5%EF%BC%9A%E9%AA%8C%E8%AF%81%E6%B6%88%E6%81%AF%E7%9A%84%E7%A1%AE%E6%9D%A5%E8%87%AA%E5%BE%AE%E4%BF%A1%E6%9C%8D%E5%8A%A1%E5%99%A8">验证消息的确来自微信服务器</a>
*/
@ResponseBody
@GetMapping("/wxCheck")
public String wxCheck(
@ApiParam(name = "signature",value = "微信加密签名")
@RequestParam(value = "signature")
String signature,
@ApiParam(name = "timestamp",value = "时间戳")
@RequestParam(value = "timestamp")
String timestamp,
@ApiParam(name = "nonce",value = "随机数")
@RequestParam(value = "nonce")
String nonce,
@ApiParam(name = "echostr",value = "随机字符串")
@RequestParam(value = "echostr")
String echostr
){

// 验证消息是否来自微信服务器

String token = "weChatTest";
String[] arr = {token, timestamp, nonce};

// 按字典序排序
Arrays.sort(arr);

// 使用StringBuilder进行拼接
StringBuilder result = new StringBuilder();
for (String s : arr) {
result.append(s);
}
String s = SHA1(String.valueOf(result));

if (!s.equals(signature)) {
System.out.println("Token值匹配失败!");
return null;
}
System.out.println("Token值匹配成功!");

// 将请求携带的参数加入Map集合
Map<String,Object> map = new HashMap<>();
map.put("signature",signature);
map.put("timestamp",timestamp);
map.put("nonce",nonce);
map.put("echostr",echostr);
// 打印Map集合查看微信的消息
System.out.println("来自微信的消息:"+map);
return echostr;
}


/**
* 微信分享专用接口
* @className WechatController
*/
@ResponseBody
@PostMapping("demo")
public Map<String,Object> demo(
HttpSession session, String URL) {

String url = "";
String appId = "你的appid";
String appSecret = "你的appSecret";
try {
url = java.net.URLDecoder.decode(URL,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
System.out.println("UTF-8格式地址解析异常!");
retuen null;// 异常就停止
}
// 获取access_token,这里要从存到session,生成这个参数的wx调用接口有调用次数限制。这部分存session,下面的下载文件里面没有更新,自己添加进去吧,不然接口次数很快就用完了。
String access_token=(String) session.getAttribute("access_token");
if(access_token==null){
// 若session中不存在才请求token,并将请求到的token存到session中
access_token = getAccessToken(appId, appSecret);
if (access_token ==null){
System.out.println("获取access_token失败!");
return null;
}
session.setAttribute("access_token", access_token);
}
// 获取jsapi_ticket
String jsapi_ticket = (String) session.getAttribute("access_ticket");
if(jsapi_ticket==null){
// 若session中不存在才请求api_ticket,并将请求到的api_ticket存到session中
jsapi_ticket = getTicket(access_token);
if (jsapi_ticket ==null){
System.out.println("获取jsapi_ticket失败!");
return null;
}
session.setAttribute("jsapi_ticket", jsapi_ticket);
}

// 3、时间戳和随机字符串
String noncestr = UUID.randomUUID().toString().replace("-", "").substring(0, 16);//随机字符串
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);//时间戳

System.out.println("accessToken:"+access_token+"\njsapi_ticket:"+jsapi_ticket+"\n时间戳:"+timestamp+"\n随机字符串:"+noncestr);


// 4、将参数排序并拼接字符串
String str = "jsapi_ticket="+jsapi_ticket+"&noncestr="+noncestr+"&timestamp="+timestamp+"&url="+url;

// 5、将字符串进行sha1加密
String signature =SHA1(str);
if (signature == null){
System.out.println("SHA1加密失败!");
return null;
}
System.out.println("参数:"+str+"\n签名:"+signature);
// 将参数统一放进Map集合,然后返回给前端
Map<String,Object> map = new HashMap<>();
map.put("signature",signature);
map.put("appId",appId);
map.put("timestamp",timestamp);
map.put("nonceStr",noncestr);
return map;
}

/**
* 第一步:获取access_token
*/
public static String getAccessToken(String appid,String secret) {

try(CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 请求地址
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appid+"&secret="+secret;
HttpGet httpGet = new HttpGet(url);
// 接收请求响应的信息
CloseableHttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
Map<String, Object> map = JSON.parseObject(result);
return (String) map.get("access_token");
}else {
System.out.println("===========");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}

/**
* 第二步:获取ticket
*/
public static String getTicket(String access_token) {

try(CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 请求地址
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+access_token+"&type=jsapi";
HttpGet httpGet = new HttpGet(url);
// 接收请求响应的信息
CloseableHttpResponse response = httpClient.execute(httpGet);
System.out.println(response.getStatusLine());
if (response.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println(result);
Map<String, Object> map = JSON.parseObject(result);
return (String) map.get("ticket");
}else {
System.out.println("===========");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}

/**
* 第三步:SHA1加密
*/
public static String SHA1(String decript) {
try {
MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();

} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}

使用

这里需要将地址构造成二维码的形式,再用微信扫描打开。

草料二维码

因为微信对直接访问直链地址有限制,直接在聊天记录里点链接打开,分享出的信息也只有直链地址,没有标题、描述和图标的。

打开后,进入我们的那个demo页面,前端如果在微信配置中,开启了debug,那么这时就会弹出提示框:errMsg:config:ok这样就成功了,点击右上角然后分享给自己,就可以看到带有标题、描述和图标的分享卡片效果了。

最后

弄好后,可以先在电脑上打开页面,这样可以在控制台看到后端请求的结果,前端数据载入情况等。

控制台都正常,没有问题信息的话,再用手机扫码访问测试。

附录5-常见错误及解决方法

参考资料

  1. 微信内分享网页自定义标题,图片,描述
  2. 微信公众号官方文档 - 开始开发 - 接入指南
  3. 微信公众号官方文档 - 开始开发 - 接口测试号申请
  4. 微信公众号官方文档 - 微信网页开发 - JS-SDK说明文档

网站集成微信分享功能
https://superlovelace.top/2024/10/26/网站集成微信分享功能/
作者
棱境
发布于
2024年10月26日
更新于
2025年2月18日
许可协议