微信公众号接口学习

概述

微信公众号的集成现在在很多业务上都需要。看着官方一大堆的 文档,大家可能不知道如何下手。本篇从 api 入手和大家一起了解下公众号在业务上如何集成。

基础环境

公众号的开发需要认证的服务号,不过官方也提供了 测试帐号 供开发测试。

开发过程中需要的环境如下:

  • 测试号
  • 公网

内网穿透

公网主要用于接收微信推送消息,比方有新用户关注、取消关注等事件。
如果大家没有公网,可以通过内网穿透来本机调试。比较常用的有 ngrokSunny-Ngroknatapp花生壳钉钉 等。

笔者这里用的 Sunny-Ngrok 免费版,不过网络不稳定,经常断开。

测试号

验证 接口配置信息, 需要启动程序响应公众平台验证。验证通过即可响应公众平台事件消息, 比方说 关注事件, 然后将微信用户与平台用户进行 绑定

回调域名配置。编辑网页服务-网页帐号,配置如下:

回调验证

这里基于 Java 实现,笔者采用的 SpringBoot

application.properties

1
2
3
4
5
server.port=80

wx.appID=wx3f2f5354f615c639
wx.appsecret=80ae2299328c6c8f6ae0c774a69b08b0
wx.token=123456

工具类

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
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

public class SHA1 {

public static String gen(String... arr) {
Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
String[] parr = arr;
int size = arr.length;

for(int i = 0; i < size; ++i) {
String a = parr[i];
sb.append(a);
}

return sha1(sb.toString());
}

public static String sha1(String str) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(str.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 "";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 签名校验
* @param timestamp
* @param nonce
* @param signature
* @return
*/
public boolean checkSignature(String timestamp, String nonce, String signature) {
try {

return SHA1.gen(new String[]{wxConfig.getToken(), timestamp, nonce}).equals(signature);

} catch (Exception e) {

logger.error("签名校验失败: {}", e.getMessage());
return false;
}
}

签名校验与回调消息接收

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
@RestController
@RequestMapping("/weixin/sign")
public class WxPortalController {

private static final Logger logger = LoggerFactory.getLogger(WxPortalController.class);

@Autowired
private WxService wxService;

/**
*
* @param signature 微信加密签名
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
* @return
*/
@GetMapping(produces = "text/plain;charset=utf-8")
public String authGet(@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "timestamp", required = false) String timestamp,
@RequestParam(name = "nonce", required = false) String nonce,
@RequestParam(name = "echostr", required = false) String echostr) {

logger.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature,
timestamp, nonce, echostr);

if (wxService.checkSignature(timestamp, nonce, signature)) {
return echostr;
}
return "非法请求";

}

/**
*
* @param requestBody 回调消息
* @param signature
* @param timestamp
* @param nonce
* @param openid
* @param encType
* @param msgSignature
* @return
*/
@PostMapping(produces = "application/xml; charset=UTF-8")
public String post(@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("openid") String openid,
@RequestParam(name = "encrypt_type", required = false) String encType,
@RequestParam(name = "msg_signature", required = false) String msgSignature) {

// 获取到openId
logger.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}],"
+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
openid, signature, encType, msgSignature, timestamp, nonce, requestBody);

try {
wxService.handleEvent(requestBody);
} catch (Exception e) {
e.printStackTrace();
}

return null;
}
}

至此,微信的基本开发就ok了。大家可以根据回调事件的消息做相应的业务处理。

接口分析

获取 access_token

基本所有的接口调用都需要 access_token

GET

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx3f2f5354f615c639&secret=80ae2299328c6c8f6ae0c774a69b08b0

Response

1
2
3
4
{
"access_token": "32_S2GVg7DuHc1VxsHY5Hf9kdqCCP1BBI9pBv-ZvK_JHtN1HTjXyN0NmIPYvRHZwkiHy7cDA_944K9V0WU3_XC3GuL8Q8rVF93PDXGDZpuN8-3V6gKFELbEnrF-sN-Ps24he8zrQs8Db_gxqrtYULKeAFAYPO",
"expires_in": 7200
}

获取用户基本信息

GET

https://api.weixin.qq.com/cgi-bin/user/info?access_token=32_ybQmVgsXDCHKSiG3bRIL7xRq1Y39sKVylwUOMQls8IKlpY5dqiycEPvQmzBvsl2REtAi11WUSn5Nc29ZnOddO0V8Rll0RtKw6WLtwgzhr3_4h5-I1pGShVa1KuD1GQXcPIKblSae5Bt8PkxiVEHjACAFOR&openid=oHOLJw-r6lBxSXU4pRDKpoDyqWI0&lang=zh_CN

Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"subscribe": 1,
"openid": "oHOLJw-r6lBxSXU4pRDKpoDyqWI0",
"nickname": "当我遇上你",
"sex": 1,
"language": "zh_CN",
"city": "朝阳",
"province": "北京",
"country": "中国",
"headimgurl": "http://thirdwx.qlogo.cn/mmopen/RFKUCMNiaHBDu2OOyCcvq5uTIteIlicusVTVUVNtIicjSyNY2su0eSYAIUzDtlAkE3Ff6uaKN8UvryLwicX1c2OeLNHJR3ibBeo9G/132",
"subscribe_time": 1587187948,
"remark": "",
"groupid": 0,
"tagid_list": [],
"subscribe_scene": "ADD_SCENE_QR_CODE",
"qr_scene": 0,
"qr_scene_str": "1234567"
}

基于 Oauth2 获得授权码,需要在微信客户端打开

GET

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx3f2f5354f615c639&redirect_uri=http://idcmind.com/weixin/sign&response_type=code&scope=snsapi_base&state=STATE

Java代码web接口

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/weixin/code")
public class WxCodeController {

private static final Logger logger = LoggerFactory.getLogger(WxCodeController.class);

@GetMapping
public Object code(@RequestParam("code") String code, @RequestParam("state") String state) {
logger.info("code={}, state={}", code, state);
return code;
}
}

微信端访问日志

1
code=061PRu4d2jOFiH0Kh83d2Hig4d2PRu4m, state=123456

获取 OpenId

GET

带入刚才获得的 code

https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx3f2f5354f615c639&secret=80ae2299328c6c8f6ae0c774a69b08b0&code=061PRu4d2jOFiH0Kh83d2Hig4d2PRu4m&grant_type=authorization_code

Response

1
2
3
4
5
6
7
{
"access_token": "32_kCTsG4G6Vp-p2NWQQbelmpTshAHldad7XNBf9FFAtUn6QwhNHuFIM9utpRaabhn4NcT5ObK4XY4_Hc3wWPf5nMVs0zqXtOL5YJvB7Lk2XBg",
"expires_in": 7200,
"refresh_token": "32_oO-cHAoZdugyzJ0uvi49_E81PsddQdO7pBSGjjlmxZqRcXBGqu1aVHQRu4TW_T7_FuGMF4te3CxtCvc7H_21tfqOPWSJMEalhBrgQ3Is8K4",
"openid": "oHOLJw-r6lBxSXU4pRDKpoDyqWI0",
"scope": "snsapi_base"
}

openid 是微信用户在 公众号 的唯一身份标识。

获取用户基本信息(UnionID机制-网页开发获取用户信息, 此处的token和公众号token不是一个token)

GET

https://api.weixin.qq.com/sns/userinfo?access_token=36_9LM_Fr2s2yfcJRlF6Zz9sDdARIXXrVNJFutEh8bURe2NoglsLzeBUiGlYQhhWePWllTOnf1_8vZFwUll97tD-NC5KFq6Gz37uFgDfo7ZIFwZEG7JgTCecJikRkCRqAvjp4_R68u9KdgomRFTTCLeACAHQZ&openid=oHOLJw-r6lBxSXU4pRDKpoDyqWI0&lang=zh_CN

Response

1
2
3
4
5
6
7
8
9
10
11
12
{   
"openid": "oHOLJw-r6lBxSXU4pRDKpoDyqWI0",
"nickname": "当我遇上你",
"sex":"1",
"province":"朝阳",
"city":"北京",
"country":"中国",
"headimgurl": "http://thirdwx.qlogo.cn/mmopen/RFKUCMNiaHBDu2OOyCcvq5uTIteIlicusVTVUVNtIicjSyNY2su0eSYAIUzDtlAkE3Ff6uaKN8UvryLwicX1c2OeLNHJR3ibBeo9G/132",
"privilege":[ "PRIVILEGE1", "PRIVILEGE2"],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

获取用户列表

GET

https://api.weixin.qq.com/cgi-bin/user/get?access_token=32_ybQmVgsXDCHKSiG3bRIL7xRq1Y39sKVylwUOMQls8IKlpY5dqiycEPvQmzBvsl2REtAi11WUSn5Nc29ZnOddO0V8Rll0RtKw6WLtwgzhr3_4h5-I1pGShVa1KuD1GQXcPIKblSae5Bt8PkxiVEHjACAFOR

Response

1
2
3
4
5
6
7
8
9
10
{
"total": 1,
"count": 1,
"data": {
"openid": [
"oHOLJw-r6lBxSXU4pRDKpoDyqWI0"
]
},
"next_openid": "oHOLJw-r6lBxSXU4pRDKpoDyqWI0"
}

生成带参数的二维码

这里演示带字符串参数的永久二维码

POST

https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=32_9o3KwEVvZho6PxWqnmhVpUNiWcaGpgl_TvvJH1NHS_3ZAPQl-2dh4XCqbxdqtahdd39hKTP9kYAcnr2Jmu1-V7n0szIdiAkoHZmd0BGOUGHrSro4i4oY0I2I4zPxcPfXxmsTJKrvlotsvwjBALCbAAAPAR

请求体

1
2
3
4
5
6
7
8
{
"action_name": "QR_LIMIT_STR_SCENE",
"action_info": {
"scene": {
"scene_str": "1234567"
}
}
}

Response

1
2
3
4
{
"ticket": "gQG18TwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyTGhyV01tc3lmOTMxMDAwMGcwN24AAgTQippeAwQAAAAA",
"url": "http://weixin.qq.com/q/02LhrWMmsyf9310000g07n"
}

通过ticket换取二维码

GET

https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQG18TwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyTGhyV01tc3lmOTMxMDAwMGcwN24AAgTQippeAwQAAAAA

Response

基于字节流的图片,在浏览器访问上述地址即可渲染出二维码。该二维码是带参数的二维码,扫码关注后回调消息中会携带参数。比如按 userId 生成二维码,扫码关注后回调消息包含 openiduserid, 可以用于后续绑定操作。

用户关注后java后台会收到xml消息:

1
2
3
4
5
6
7
8
9
<xml>
<ToUserName><![CDATA[gh_d0c6b73cc08e]]></ToUserName>
<FromUserName><![CDATA[oHOLJw-r6lBxSXU4pRDKpoDyqWI0]]></FromUserName>
<CreateTime>1587187948</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[qrscene_1234567]]></EventKey>
<Ticket><![CDATA[gQG18TwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyTGhyV01tc3lmOTMxMDAwMGcwN24AAgTQippeAwQAAAAA]]></Ticket>
</xml>

新增临时素材

POST

https://api.weixin.qq.com/cgi-bin/media/upload?access_token=32_LtMp7FPdxSvV1kmPHoQTqCjhGAjigFDvhzVPnAIQsknmHyFOtTO9g5XBr7DVVNPepYLPZjSRCX0fDKZqTzBufliVoWTMmRI1Uu-T2aMt3vfTzlluzm6eANZ6nJNEa_BXLwHxIx3yLP49fr_MZTZfACAGAG&type=image

Content-Type:multipart/form-data

body中选择file类型的key,value选择本地图片

Response

1
2
3
4
5
6
{
"type": "image",
"media_id": "51TAwv-DI7RucNhJr8tzqbYLWSZFH-m1b40Td2rLz3L2WUo881sQ8DgGxeRKXMG5",
"created_at": 1587187471,
"item": []
}

获取临时素材

GET

https://api.weixin.qq.com/cgi-bin/media/get?access_token=32_LtMp7FPdxSvV1kmPHoQTqCjhGAjigFDvhzVPnAIQsknmHyFOtTO9g5XBr7DVVNPepYLPZjSRCX0fDKZqTzBufliVoWTMmRI1Uu-T2aMt3vfTzlluzm6eANZ6nJNEa_BXLwHxIx3yLP49fr_MZTZfACAGAG&media_id=51TAwv-DI7RucNhJr8tzqbYLWSZFH-m1b40Td2rLz3L2WUo881sQ8DgGxeRKXMG5

Response

返回图片素材

获取模板列表

创建模板不需要调用接口,在公众号后台即可设置。

我创建的模板如下:

1
欢迎你: {{name.DATA}}

GET

https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=32_ybQmVgsXDCHKSiG3bRIL7xRq1Y39sKVylwUOMQls8IKlpY5dqiycEPvQmzBvsl2REtAi11WUSn5Nc29ZnOddO0V8Rll0RtKw6WLtwgzhr3_4h5-I1pGShVa1KuD1GQXcPIKblSae5Bt8PkxiVEHjACAFOR

Response

1
2
3
4
5
6
7
8
9
10
11
12
{
"template_list": [
{
"template_id": "7BsKKP1MsDxbNmfYfTaNVm9guRvgwH8l2PmFbDCMip0",
"title": "欢迎模板",
"primary_industry": "",
"deputy_industry": "",
"content": "欢迎你: {{name.DATA}}",
"example": ""
}
]
}

发送模板消息

POST

https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=32_ybQmVgsXDCHKSiG3bRIL7xRq1Y39sKVylwUOMQls8IKlpY5dqiycEPvQmzBvsl2REtAi11WUSn5Nc29ZnOddO0V8Rll0RtKw6WLtwgzhr3_4h5-I1pGShVa1KuD1GQXcPIKblSae5Bt8PkxiVEHjACAFOR

请求体

1
2
3
4
5
6
7
8
9
10
11
 {
"touser":"oHOLJw-r6lBxSXU4pRDKpoDyqWI0",
"template_id":"7BsKKP1MsDxbNmfYfTaNVm9guRvgwH8l2PmFbDCMip0",
"url":"http://idea360.cn",
"data":{
"name": {
"value":"登高射太阳!",
"color":"#173177"
}
}
}

查看微信,会看到测试号给推送了一条模板消息。

同时,我们的java日志中会看到收到xml消息

1
2
3
4
5
6
7
8
9
<xml>
<ToUserName><![CDATA[gh_d0c6b73cc08e]]></ToUserName>
<FromUserName><![CDATA[oHOLJw-r6lBxSXU4pRDKpoDyqWI0]]></FromUserName>
<CreateTime>1587188459</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[TEMPLATESENDJOBFINISH]]></Event>
<MsgID>1301848003480829954</MsgID>
<Status><![CDATA[success]]></Status>
</xml>

发送客服消息

POST

https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=32_LBmSoFhAku0mBL8dQ3mj4pSPSe6_EFBGUt_uCEu3ZGZX-auOe9etVmtKBfwnK9FgMd9BsrTWKo-mmgy86EVCjJkpvuu2h6_VZXJ_S-fCHtW7VJIKlQ9P7tNEDlWEwDsTCUCN6Cj8dVJqK4e5PNViAJAKIQ

消息体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

{
"touser":"oHOLJw-r6lBxSXU4pRDKpoDyqWI0",
"msgtype":"news",
"news":{
"articles": [
{
"title":"当我遇上你",
"description":"当我遇上你",
"url":"https://www.baidu.com",
"picurl":"https://raw.githubusercontent.com/qidian360/oss/master/images/one.png"
}
]
}
}

Response

1
2
3
4
{
"errcode": 0,
"errmsg": "ok"
}

查看公众号会看到消息。

监测token是否失效

GET

https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=%s

刷新模板消息10W/日限额

GET

https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=%s

最后

本文到此结束,感谢阅读。欢迎关注公众号【当我遇上你】, 您的支持是我最大的动力。