您好,欢迎来到壹号娱乐官网!
NEWS商会动态
发布日期:2025/12/22浏览次数:
微信公众平台模板消息实现要点:ACCESS_TOKEN及获取模板列表方法

很多开发者,在初次使用微信公众平台模板消息功能之际,皆会遇到意想不到的“坑”,于那看似简单的流程当中 。

模板消息的价值与挑战

公众号借助模板消息可向用户发送诸如订单状态、预约提醒等之类的服务通知,这提升了服务效率以及用户体验,可是,从获取凭据一直到最终发送,每一个环节都存在需要留意的技术细节,稍有差错就会致使消息发送失败或者接口调用出现异常,从而给项目开发增添额外的时间成本。

深入理解Access_Token

拥有调用全部微信高级接口功能的钥匙是Access_Token,其有效期时长规定为7200秒。它是通过AppID以及AppSecret进行换取的。存在一个关键限制情况,即重复获取就会即刻致使旧Token失效,不过微信会针对旧Token留存大约5分钟的缓冲期。这就代表着在分布式系统当中,必须要设计出一套集中且高效的Token管理机制,以此来防止多个服务实例同时刷新进而造成Token频繁失效 。

获取与配置消息模板

开发者于公众号后台之上的模板消息库里头,能够选用那行业模板或者去申请那新模板。获取模板列表的接口调用虽说简单,然而务必严格依照参数格式。模板变量格式是{{xxx.DATA}},括号以内不能存在空格。早期时期微信官方文档那儿的示例代码曾含有空格,此情况误导了好多开发者 ,在进行实际编码时要格外仔细核对。

发送消息的具体步骤

采用POST请求来发送那所谓的模板消息,在请求体里要带着负责接收方面的OpenID、还有模板ID以及具体呈现的数据内容。数据内容呢,它是个属于JSON的对象,并且它要和模板当中事先定义好的变量逐一分别对应起来哟。除此以外呀,能进行跳转,至于跳转的是指定好的页面或者小程序呢。发送达成成功的状态之后呀,那些用户就会在微信对话列表当中收到这一条对应的服务通知啦。

充分利用测试号进行开发

/**
 *  获取微信的AccessToken
 *
 *  微信提供了一个rest接口,根据appid和secret 更新并返回 AccessToken;
 *  微信的这个AccessToken有几点需要注意:
 *  1.每次调用该接口,会返回新的AccessToken,老的AccessToken会有5分钟的存活期
 *  2.微信端该接口返回的AccessToken有效期目前为7200s
 * Created by xh on 2019/4/25.
 */
@Slf4j
public class WeChatAccessTokenUtil {
    private static RestTemplate restTemplate;
    private static WeChatProperties weChatProperties;
    private static RedissonClient redissonClient;
    private volatile static String accessToken;
    private volatile static boolean callFlag = true;
    private static CountDownLatch latch = new CountDownLatch(1);
    private static boolean initFlag = false;
    private static final String LOCK_KEY = "lock-AccessToken";
    private static final String ACCESSTOKEN = "ACCESSTOKEN";
    private static final String ACCESSTOKEN_LASTUPDATE = "ACCESSTOKEN_LASTUPDATE";
    static {
        restTemplate = SpringContext.getBean(RestTemplate.class);
        weChatProperties = SpringContext.getBean(WeChatProperties.class);
        redissonClient = SpringContext.getBean(RedissonClient.class);
    }
    /**
     *  获取AccessToken
     * @return String
     */
    public static String getAccessToken() throws Exception {
        log.info("WeChatAccessTokenUtil.getAccessToken start");
        //优先从redis中获取
        RBucket accessTokenCache = redissonClient.getBucket(ACCESSTOKEN);
        //redis中存在,返回redis中的ACCESS_TOKEN;
        // 同时如果accessToken未初始化,则将redis中的ACCESS_TOKEN值写入共享变量accessToken  这个不需要考虑并发问题,重复设置也没事
        if (accessTokenCache != null && !StringUtils.isEmpty(accessTokenCache.get())) {
            if (!initFlag) {
                accessToken = accessTokenCache.get();
                initFlag = true;
            }
            return accessTokenCache.get();
        }
        //redis中不存在,那么就需要让一个线程A去调用微信接口查询accessToken并刷入redis;
        //其他线程使用老的accessToken(即共享变量accessToken),如果存在的话;  如果老的accessToken不存在则等待线程A的通知;
        //老的accessToken有5分钟的存活期,所以这里使用一个缓存key并设置失效时间来控制老的accessToken是否可用,具体方式是:
        //在将accessToken刷入redis时,同时刷入另一个key:ACCESSTOKEN_LASTUPDATE,并控制失效时间比accessToken多五分钟,当缓存失效时,我们判断缓存ACCESSTOKEN_LASTUPDATE是否存在,如果不存在则表示老的accessToken失效不可用了,这时候清空共享变量accessToken.
        else {
            Lock lock = redissonClient.getLock(LOCK_KEY);
            //所有线程循环尝试获取分布式锁,只有一个线程X 会获得锁,获得锁的线程X 首先设置计数器latch为1,然后判断是否存在缓存ACCESSTOKEN_LASTUPDATE,不存在表示老的accessToken已经过了5分钟的存活期,那么就清空共享变量accessToken;
            //然后线程X 设置共享变量callFlag = false,那么其他线程会退出while循环;
            //对于线程X,因为要考虑分布式的场景,所以首选再次去redis中查询accessToken,查询到则更新共享变量accessToken;查询不到则调rest接口获取accessToken;
            //对于其他退出循环的线程,如果共享变量accessToken有值,表示还在存活期内,则使用老的accessToken返回给业务使用;如果accessToken为空,则需要等待线程X 的通知;
            boolean innerFlag = true;  //线程私有的变量, 获得锁的线程通过修改这个标志退出循环
            //callFlag 线程共享的变量,用于当一个线程获取锁时,通知其他线程跳出循环

            while (innerFlag && callFlag) {
                if (lock.tryLock()) {  //默认30000ms
                    try {
                        latch = new CountDownLatch(1);
                        //判断老的accessToken是否可用
                        if (redissonClient.getBucket(ACCESSTOKEN_LASTUPDATE).get() == null) {
                            accessToken = null;
                        }
                        callFlag = false;
                        //获取锁之后,首先查询redis ,如果redis中存在则不再需要调用微信接口了  这里是考虑分布式的场景
                        accessTokenCache = redissonClient.getBucket(ACCESSTOKEN);
                        if (accessTokenCache != null && !StringUtils.isEmpty(accessTokenCache.get())) {
                            accessToken = accessTokenCache.get();
                        }
                        else {
                            //调用微信的接口查询ACCESS_TOKEN
                            WeChatAccessTokenResp accessTokenResp =  getAccessTokenFromWechat();
                            accessToken = accessTokenResp.getAccessToken();
                            Long expire = accessTokenResp.getExpiresIn();
                            if (expire > 200) {
                                expire -= 200;
                            }
                            //批量更新缓存
                            RBatch batch = redissonClient.createBatch();
                            batch.getBucket(ACCESSTOKEN).setAsync(accessToken, expire, TimeUnit.SECONDS);
                            batch.getBucket(ACCESSTOKEN_LASTUPDATE).setAsync(System.currentTimeMillis(), expire + 300, TimeUnit.SECONDS);
                            batch.execute();
                        }
                    }
                    finally {
                        //防止因为网络等问题导致失败,无法通知其他线程 所以这里放在finally块里
                        //共享变量accessToken已经设置新值为可用的accessToken,通知其他线程
                        latch.countDown();
                        innerFlag = false;
                        //还原
                        callFlag = true;
                        lock.unlock();
                    }
                }
            }
            if (StringUtils.isEmpty(accessToken)) {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        log.info("WeChatAccessTokenUtil.getAccessToken end");
        return accessToken;
    }
    public static WeChatAccessTokenResp getAccessTokenFromWechat() throws Exception {
    //调rest接口查询AccessToken,这里就不展示了
    }
}

微信给出了公众平台测试账号系统,开发者不用去申请正式公众号就能体验全部接口。于测试账号管理页面那儿,能够配置自身的模板消息,还能获到专用的AppID以及AppSecret。全部开发调试工作都应当在测试环境里完成,等流程彻底跑通之后再迁移至正式环境,这是规避风险的最佳实践。

设计稳健的Token管理方案

user@CentOS7.3[/xxx/xxx]$curl http://10.45.18.85:8080/luoluocaihong/wechat/template -X GET -H 'Content-Type:application/json'
[{"templateId":"NTGqIwifErpioNS1m5bX6M1DtdQAusj0q4bZMFBmRw8","title":"物流模板","primaryIndustry":"","deputyIndustry":"","content":"物流状态:{{state.DATA}}\\n\\n发货时间: {{deliverTime.DATA}}","example":""},{"templateId":"0RywEuCbkh9tMlaZyaCxyYE2uIrjxMlZYAaF4cODLEs","title":"Test","primaryIndustry":"","deputyIndustry":"","content":"{{result.DATA}}\\n\\n领奖金额:{{withdrawMoney.DATA}}\\n领奖 时间: {{withdrawTime.DATA}}\\n银行信息:{{cardInfo.DATA}}\\n到账时间: {{arrivedTime.DATA}}\\n{{remark.DATA}}","example":""},{"templateId":"NfcHMyxMr3hPTRmDFa8cCRtkKYkPoAOFGd5SmO3d-RA","title":"Hello","primaryIndustry":"","deputyIndustry":"","content":"您好,{{name.DATA}}","example":""}]user@CentOS7.3[/xxx/xxx]$

从Token的有效性以及分布式部署方面加以考量,一种较为常见的方案是运用Redis这类缓存中间件。程序读取Token之时会优先从缓存这里获取;要是失效了的话,借助互斥锁机制让单个实例前往微信服务器去更新,并且存入缓存那里设定过期时间。其他并发的请求能够短暂使用旧的Token或者等待新的Token生成。这对服务的稳定以及高可用起到了保障作用。

面对微信模板消息功能的实现,你碰到的最难搞的问题是什么呢,其是怎样被你成功攻克的呀?欢迎于评论地方分享出你的经验哟,要是认为这篇内容有帮助的话,请进行点赞或者分享给更多的开发者好啦。

user@CentOS7.3[/xxx/xxx]$curl http://10.45.18.85:8080/luoluocaihong/wechat/send -X POST -H 'Content-Type:application/json'
781640493582254081user@CentOS7.3[/xxx/xxx]$