阅读 14

微信公众号接口学习

概述

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

基础环境

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

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

  • 测试号
  • 公网

内网穿透

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

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

测试号

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

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

回调验证

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

application.properties

server.port=80

wx.appID=wx3f2f5354f615c639
wx.appsecret=80ae2299328c6c8f6ae0c774a69b08b0
wx.token=123456
复制代码

工具类

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 "";
    }
}
复制代码
    /**
     * 签名校验
     * @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;
        }
    }
复制代码

签名校验与回调消息接收

@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

{
    "access_token": "32_S2GVg7DuHc1VxsHY5Hf9kdqCCP1BBI9pBv-ZvK_JHtN1HTjXyN0NmIPYvRHZwkiHy7cDA_944K9V0WU3_XC3GuL8Q8rVF93PDXGDZpuN8-3V6gKFELbEnrF-sN-Ps24he8zrQs8Db_gxqrtYULKeAFAYPO",
    "expires_in": 7200
}
复制代码

基于 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 接口

@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;
    }
}
复制代码

微信端访问日志

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

{
    "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 机制)

GET

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

Response

{
    "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"
}
复制代码

获取用户列表

GET

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

Response

{
    "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
复制代码

请求体

{
    "action_name": "QR_LIMIT_STR_SCENE",
    "action_info": {
        "scene": {
            "scene_str": "1234567"
        }
    }
}
复制代码

Response

{
    "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 消息:

<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

{
    "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

返回图片素材

获取模板列表

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

我创建的模板如下:

欢迎你: {{name.DATA}}
复制代码

GET

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

Response

{
    "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
复制代码

请求体

 {
           "touser":"oHOLJw-r6lBxSXU4pRDKpoDyqWI0",
           "template_id":"7BsKKP1MsDxbNmfYfTaNVm9guRvgwH8l2PmFbDCMip0",
           "url":"http://idea360.cn",
           "data":{
                   "name": {
                       "value":"登高射太阳!",
                       "color":"#173177"
                   }
           }
}
复制代码

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

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

<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
复制代码

消息体

{
    "touser":"oHOLJw-r6lBxSXU4pRDKpoDyqWI0",
    "msgtype":"text",
    "text":
    {
         "content":"Hello World"
    }
}
复制代码

Response

{
    "errcode": 0,
    "errmsg": "ok"
}
复制代码

查看公众号会看到消息。

最后

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