前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >浅谈企业微信中AccessToken管理和API操作

浅谈企业微信中AccessToken管理和API操作

作者头像
geekfly
发布于 2022-05-06 11:44:48
发布于 2022-05-06 11:44:48
2.8K00
代码可运行
举报
文章被收录于专栏:geekflygeekfly
运行总次数:0
代码可运行

前言

众所周知,在微信公众平台开发中,其实就是一系列的API请求和自身业务系统的集成,而在API请求中,AccessToken是优势一个必不可少的参数。

注:

  • 本文基于企业微信,故部分API请求可能和订阅号,服务号,小程序不太相同,但整体思路一致。
  • 本项目代码基于Java语言,SpringBoot框架。

在企业微信开发文档中有这样一段:

  • access_token的有效期通过返回的expires_in来传达,正常情况下为7200秒(2小时),有效期内重复获取返回相同结果,过期后获取会返回新的access_token。
  • 由于企业微信每个应用的access_token是彼此独立的,所以进行缓存时需要区分应用来进行存储。
  • access_token至少保留512字节的存储空间。
  • 企业微信可能会出于运营需要,提前使access_token失效,开发者应实现access_token失效时重新获取的逻辑。

需要注意的点:

  • 在有效期内,重复获取不会返回新的AccessToken,并且不会延长有消息。(即使不在同一个程序内获取)
  • AccessToken可能会提前过期。(两个小时获取一次可能会出现提前过期的问题)

问题描述

原始方案: V0.1 定时器(schedule) 描述:在SpringBoot项目中,使用@Scheduled注解,每一小时获取一次AccessToken。 问题:在运行一段时间后,因网络波动导致某次请求失败,程序出错,定时器没有继续执行。 影响:程序无法进行任何微信相关的API请求。 改进:V0.2 定时器+异常捕获 V0.2 定时器+异常捕获 描述:在上述版本的情况下,增加异常捕获。 问题:运行一段时间再次出现异常,程序在获取AccessToken过程中出现阻塞,后续代码均未执行,定时器也无法执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	影响:两次带来的影响都是致命的,犹如定时炸弹,完全不清楚下次会何时继续出现。

改进方案

总结:

此方案的优点在于,程序无需通过定时器或线程去处理AccessToken,通过Redis的缓存,并设置过期时间,实现动态的管理,并能和其他程序共享AccessToken(对于多个程序需要使用同一个应用时,会存在此需求)。在过期也能自动获取,并不影响程序正常运行。 即使某次请求出现问题,不会影响之后的请求。


代码展示

图中设计的几个类实现如下:

  • WorkWXAPI类: 定义了企业微信相关的API请求的URL地址,以及其他企业微信相关的常量等。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @Author: geekfly
 * @Description:  企业微信相关API及常量定义
 * @Date: 2017/10/12
 * @Modified By:
 */
public class WorkWXAPI {

    public static String CORPID = ""; //企业ID
    public static Integer AGENTID = 1000017; //应用ID
    public static String AUTH_APP_SECRET = ""; //应用1Secret
    public static String CONTACTS_SECRET = ""; //应用2Secret
    public static String TOKEN = ""; //API接收Token
    public static String EncodingAESKey = "";//API接收EncodingAESKey

    public static String GET_ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s";
    public static String GET_USER_OPENID_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s";
    public static String SEND_MESSAGE_URL = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s";
    public static String GET_USER_INFO = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s";


    //标签相关
    public static String TAG_ADD_USERS_URL = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers?access_token=%s";
    public static String TAG_DELETE_USERS_URL = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers?access_token=%s";

    /*
       成员管理
     */
    //获取部门成员
    public static String CONTACTS_SIMPLE_LIST = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=%s&department_id=%s&fetch_child=%s";
    //获取部门成员详情
    public static String CONTACTS_LIST = "https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=%s&department_id=%s&fetch_child=%s";
    //更新成员
    public static String CONTACTS_UPDATE= "https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token=%s";
    //创建成员
    public static String CONTACTS_CREATE = "https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token=%s";
    //读取成员
    public static String CONTACTS_GET = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s";


    /*
    部门管理
     */
    //创建部门
    public static String DEPARTMENT_CREATE = "https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token=%s";
    //更新部门
    public static  String DEPARTMENT_UPDATE = "https://qyapi.weixin.qq.com/cgi-bin/department/update?access_token=%s";
    //获取部门列表
    public static String DEPARTMENT_LIST = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=%s&id=%s";

    //js api
    public static String JS_API = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=%s";

    //认证通过
    public static String AUTH_SUCCESS = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?access_token=%s&userid=%s";

    //不同身份人员所在部门ID
    public static Integer DEPARTMENT_JZG_ID_OTHER = 4658;    //教师(其他)
    public static Integer DEPARTMENT_JZG_ID = 2638;    //教师(新)

    public static Integer DEPARTMENT_NO_AUTH = 1889; //未认证用户部门
    public static Integer DEPARTMENT_ROOT = 1; //根部门ID

    //菜单ID
    public static String MENU_ID_USER_INFO = "user_info"; //个人信息
    public static String MENU_ID_USER_BIND = "user_bind"; //身份认证

}

注:代码使用String.format进行占位符替换,使用时需注意参数的顺序,以免因替换出错导致的传参错误。

  • WXAPIUtil类 WXAPIUtil为封装的企业微信相关API操作的类,如:获取AccessToken获取,更新,删除用户信息,获取,创建,更新部门信息等。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @Author geekfly
 * @Date 2018/1/17 9:28
 * @Desc 微信API帮助类
 */
public class WXAPIUtil {

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

    private static String accessTokenAuth = "access_token:auth";

    private static String accessTokenContacts = "access_token:contacts";

    /**
     * 获取access token
     * @param key 根据此key获取
     * @return
     */
    public static Map<String, Object> getAccessToken(String key){
        String secret = "";
        if(key == accessTokenAuth){
            secret = WorkWXAPI.AUTH_APP_SECRET;
        }else if(key == accessTokenContacts){
            secret = WorkWXAPI.CONTACTS_SECRET;
        }
        String json = HttpClientUtil.get(String.format(WorkWXAPI.GET_ACCESS_TOKEN_URL, WorkWXAPI.CORPID, secret));
        Map<String, Object> data = JsonUtil.WXJsonToMap(json);
        if (Integer.parseInt(data.get("errcode").toString()) == 0) {
            return  data;
        } else {
            logger.info("{} get access error,msg:{}", key, data.toString());
        }
        return null;
    }

    /**
     * 根据code获取用户信息
     * @param code
     * @return
     */
    public static Map<String, Object> getOpenId(String code){

       return HttpClientUtil.wxRequest(accessTokenAuth, String.format(WorkWXAPI.GET_USER_OPENID_URL, accessTokenAuth, code)
                , null, HttpClientUtil.METHOD_GET);
    }

    /**
     * 根据UserId获取用户信息
     * @param userId
     * @return
     */
    public static Map<String, Object> getContact(String userId){

        return HttpClientUtil.wxRequest(accessTokenContacts, String.format(WorkWXAPI.CONTACTS_GET, accessTokenContacts, userId)
                , null, HttpClientUtil.METHOD_GET);
    }
 }

注:因代码过长,此处只引入部门函数。

  • HttpClientUti类 包括Get,Post,wxRequest方法,可执行普通的http请求。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.edu.zut.wechat.utils;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author: geekfly
 * @Description: HttpClient工具类,用于发送请求
 * @Date: 2017/10/12
 * @Modified By:
 */
@Component
public class HttpClientUtil {

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

    public static String METHOD_GET = "Get";

    public static String METHOD_POST = "Post";

    private static StringRedisTemplate stringRedisTemplate;

    public static void setStringRedisTemplate(StringRedisTemplate template) {
        stringRedisTemplate = template;
    }

    /**
     * 执行企业微信接口方法
     * @param key AccessToken的key值
     * @param url 接口地址
     * @param json json数据
     * @param method 请求方式 Get or Post
     * @return
     */
    public static Map<String, Object> wxRequest(String key, String url, String json, String method){
        Map<String, Object> mapData = new HashMap<>();
        try{
            int num = 0;
            boolean flag = false;
            while(num < 3){
                num++;
                if(stringRedisTemplate.hasKey(key)){
                    url = url.replace(key, stringRedisTemplate.opsForValue().get(key));
                    String jsonData = null;
                    logger.info(stringRedisTemplate.opsForValue().get(key));
                    if(method.equals(METHOD_GET)){
                        jsonData = get(url);
                    }else if(method.equals(METHOD_POST)){
                        jsonData = post(url, json);
                    }
                    mapData = JsonUtil.toMap(jsonData);
                    int errCode = Integer.parseInt(mapData.get("errcode").toString());
                    if(errCode == 0){ //正常
                        return mapData;
                    }else if(errCode == 42001){ // access_token过期
                        stringRedisTemplate.delete(key); //删除key
                        logger.info("key:{},access token过期", key);
                    }else{
                        flag = true;
                        logger.info("key:{},url:{},json:{},method:{},msg:{}", key, url, json, method, jsonData);
                    }
                }
                if(flag == false){
                    mapData = WXAPIUtil.getAccessToken(key);
                    if(mapData != null){
                        stringRedisTemplate.opsForValue().set(key, mapData.get("access_token").toString(),
                                Integer.parseInt(mapData.get("expires_in").toString()), TimeUnit.SECONDS);
                    }
                }
            }
        }catch (Exception ex){
            ex.printStackTrace();
            logger.error("wxRequest error:{}", ex.getMessage());
        }
        mapData.put("errcode", -1000);
        mapData.put("errmsg", "未获取到信息");
        return mapData;
    }

    public static String get(String url){
        HttpClient httpClient = new DefaultHttpClient();
        HttpGet httpGet = new HttpGet(url);
        HttpResponse response = null;
        try{
            response = httpClient.execute(httpGet);
        }catch (Exception e) {}
        String temp="";
        try{
            HttpEntity entity = response.getEntity();
            temp= EntityUtils.toString(entity,"UTF-8");
        }catch (Exception e) {}

        return temp;
    }

    public static String post(String url, String json){
        HttpClient httpClient = null;
        HttpPost httpPost = null;
        String result = null;
        try{
            httpClient = new DefaultHttpClient();
            httpPost = new HttpPost(url);

            StringEntity entity = new StringEntity(json,"utf-8");//解决中文乱码问题
            entity.setContentEncoding("UTF-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            HttpResponse response = httpClient.execute(httpPost);
            if(response != null){
                HttpEntity resEntity = response.getEntity();
                if(resEntity != null){
                    result = EntityUtils.toString(resEntity);
                }
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }
        return result;
    }
}

核心方法为:wxRequest

其中StringRedisTemplate在主函数中注入

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static void main(String[] args) {
		ConfigurableApplicationContext applicationContext = SpringApplication.run(WechatApplication.class, args);

		HttpClientUtil.setStringRedisTemplate(applicationContext.getBean(StringRedisTemplate.class));
	}

	@Bean
	StringRedisTemplate template(RedisConnectionFactory connectionFactory){
		return new StringRedisTemplate(connectionFactory);
	}

Controller中调用如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Map<String, Object>  jsonMap = WXAPIUtil.updateContact(JsonUtil.toJson(wxUser));
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.edu.xxx.wechat.utils;

import com.alibaba.fastjson.JSON;

import java.util.List;
import java.util.Map;

/**
 * @Author: hanyunfei
 * @Description: Json 工具类
 * @Date: 2017/10/12
 * @Modified By:
 */
public class JsonUtil {
	/**
	 * 对象转Json
	 * @param object
	 * @return 转化后的Json字符串
	 */
	public static String toJson(Object object){
		String string = null;
		try {
			string = JSON.toJSONString(object);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return string;
	}
	
	/**
	 * 普通Json字符串转Map
	 * @param json
	 * @return 转化后的Map
	 */
	public static Map<String, Object> toMap(String json){
		return JSON.parseObject(json, Map.class);
	}

	/**
	 * 微信接口返回Json字符串转Map(考虑版本区别,需判断是否有errcode)
	 * @param json
	 * @return 转化后的Map
	 */
	public static Map<String, Object> WXJsonToMap(String json){
		Map<String, Object> map =  JSON.parseObject(json, Map.class);
		if(!map.containsKey("errcode")){ //如果不存在errcode键,则添加该键
			map.put("errcode", 0);
		}
		return map;
	}

	public static <T> T toBean(String text, Class<T> clazz) {
		return JSON.parseObject(text, clazz);
	}

	public static <T> List<T> toList(String text, Class<T> clazz) {
		return JSON.parseArray(text, clazz);
	}

	public static void main(String[] args) {
		String json = "{\"errcode\": 0,\"errmsg\": \"ok\",\"userid\": \"zhangsan\",\"name\": \"李四\",\"department\": [1, 2],\"order\": [1, 2],\"position\": \"后台工程师\",\"mobile\": \"15913215421\",\"gender\": \"1\",\"email\": \"zhangsan@gzdev.com\",\"isleader\": 1,\"avatar\": \"http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0\",\"telephone\": \"020-123456\",\"english_name\": \"jackzhang\",\"extattr\": {\"attrs\":[{\"name\":\"爱好\",\"value\":\"旅游\"},{\"name\":\"卡号\",\"value\":\"1234567234\"}]},\"status\": 1,\"qr_code\":\"https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx\"\"external_profile\": {\"external_attr\": [{\"type\": 0,\"name\": \"文本名称\",\"text\": { \"value\": \"文本\"}},{\"type\": 1,\"name\": \"网页名称\",\"web\": { \"url\": \"http://www.test.com\", \"title\": \"标题\"}},{\"type\": 2,\"name\": \"测试app\",\"miniprogram\": { \"appid\": \"wx8bd80126147df384\", \"pagepath\": \"/index\", \"title\": \"my miniprogram\"}}]}";
		System.out.println(JsonUtil.toMap(json));

	}
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-05-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Java企业微信开发_02_通讯录同步
       登录企业微信—>管理工具—>通讯录同步助手—>开启“API接口同步”  ; 开启后,即可看到通讯录密钥,也可设置通讯录API的权限:读取或者编辑通讯录。
shirayner
2018/08/10
7K0
Java企业微信开发_02_通讯录同步
Java企业微信开发_09_身份验证之移动端网页授权(有完整项目源码)
在开始使用网页授权之前,需要先设置一下授权回调域。这里瞬间想到之前做JSSDK的时候,也设置过一个域名。二者本质上都是设置可信域名。
shirayner
2018/08/10
2.8K0
Java企业微信开发_09_身份验证之移动端网页授权(有完整项目源码)
微信分享开发
微信分享的文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 微信 JS 接口签名校验工具 https://mp.
苏生不惑
2019/08/14
2.6K0
Java企业微信开发_08_素材管理之下载微信临时素材到本地服务器
一、本节要点 1.获取临时素材接口 请求方式:GET(HTTPS) 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=AC
shirayner
2018/08/10
2.9K0
Java企业微信开发_08_素材管理之下载微信临时素材到本地服务器
Java企业微信开发_03_自定义菜单
这里需要格外注意的是,企业微信中请求包的数据是Json字符串格式的,而不是xml格式。关于json序列化的问题请参考上一节   Java企业微信开发_03_通讯录同步
shirayner
2018/08/10
8800
干货 | 通用 api 封装实战,带你深入理解 PO
在普通的接口自动化测试中,如果接口的参数,比如 url,headers等传参改变,或者测试用例的逻辑、断言改变,那么整个测试代码都需要改变。apiobject设计模式借鉴了pageobject的设计模式,可以实现一个优雅、强大的接口测试框架。
用户9652437
2022/04/20
5710
企微获取成员userID
如果企业委托授权第三方服务商将通讯录从其他系统同步到企业微信,则需要企业授权“通讯录编辑授权”给服务商。
ha_lydms
2023/08/10
6410
企微获取成员userID
企业微信API-https请求模板-获取access_token-Java
原文CSDN链接:https://zwz99.blog.csdn.net/article/details/113845625
Designer 小郑
2023/08/01
2980
python发送微信及企业微信消息
直接使用第三方库 itchat,其文档中有详细使用方式; https://itchat.readthedocs.io/zh/latest/
用户1558882
2019/06/21
10.1K0
python发送微信及企业微信消息
接口测试框架实战(三) | APIObject 模式、原则与应用
在普通的接口自动化测试中,如果接口的参数,比如 url,headers 等传参改变,或者测试用例的逻辑、断言改变,那么整个测试代码都需要改变。APIObject 设计模式借鉴了 PageObject 的设计模式,可以实现一个优雅、强大的接口测试框架。
霍格沃兹测试开发
2020/10/13
9350
PHP微信和企业微信签名
参与签名的参数有四个: noncestr(随机字符串), jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分)
fastmock
2022/07/13
15.9K0
企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充(Vue项目版)。。。
关于企业微信PC版应用跳转到默认浏览器,我之前写过一篇文章:企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充。。。
zhanyd
2024/07/30
6240
企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充(Vue项目版)。。。
vue开发企业微信_vue全局api
4、用 ticket + 随机字符串 + 时间戳 + 当前网页url 拼接成一串字符,然后进行sha1加密。
全栈程序员站长
2022/11/11
2K1
vue开发企业微信_vue全局api
分布式监控系统Zabbix-3.0.3-完整安装记录(6)-微信报警部署
Zabbix可以通过多种方式把告警信息发送到指定人,常用的有邮件,短信报警方式。 现在由于微信使用的广泛度,越来越多的企业开始使用zabbix结合微信作为主要的告警方式,这样可以及时有效的把告警信息推送到接收人,方便告警的及时处理。 前面介绍了zabbix的邮件报警的部署过程,这里继续说下zabbix的微信报警环境的部署。 废话不多说了,下面记录了微信报警的操作过程: 接下来详细记录如下: 1)微信企业号的申请过程 2)微信企业号登陆后的相关设置 3)zabbix结合微信报警脚本设置 -----------
洗尽了浮华
2018/01/22
1.1K0
分布式监控系统Zabbix-3.0.3-完整安装记录(6)-微信报警部署
企微获客链接 中文乱码问题处理
在开始今天的内容之前,先来带大家看一篇关于多线程的文章,文章标题【不懂这些,面试都不敢说自己熟悉多线程】,文章链接:https://cloud.tencent.com/developer/article/2466941 这篇文章详细介绍了线程池配置,如何创建线程池以及不同线程池创建方式的区别。后面又介绍了线程的等待/通知机制以及ReentrantLock与synchronized的不同之处等内容,介绍的相当详细,对于多线程不熟悉的小伙伴可以看详细看一下,一定会有所感悟。
六月的雨在Tencent
2024/11/19
2490
企业微信&小程序授权全链路打通指南
近期,我在致力于打造自己的小程序产品时,迎来了一项关键性的进展——微信相关授权流程的完整实现。从用户登录到权限获取,我们细致入微地梳理并实现了每一项授权机制,确保了用户体验的流畅与安全。
程序员海军
2024/12/22
2370
企业微信&小程序授权全链路打通指南
微信客服API接口对接-获取access_token-调用其他接口时都需要获取-【唯一客服】
调用任何其他接口的时候,都需要先获取access_token 并且不能频繁调用,需要有缓存机制 package wechat_kf_sdk import ( "bytes" "encoding/json" "encoding/xml" "errors" "fmt" "github.com/patrickmn/go-cache" "io" "io/ioutil" "log" "net/http" "sync" "
唯一Chat
2023/03/18
8010
【微信告警脚本】python3企业微信告
#!/usr/bin/python # --*-- encoding=utf-8 --*-- import urllib.request import json import sys import
py3study
2020/01/03
2.7K0
JSSDK使用注意事项
1 如果要做朋友圈分享 ,除了'checkJsApi'请务必在config中加上
lilugirl
2019/05/28
9470
JSSDK使用注意事项
企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充。。。
我们公司内部用企业微信沟通,最近有个需求,一个应用在企业微信PC版打开时,要自动跳转到PC的默认浏览器。在开发过程中,我经历了几个坑,在这里记录一下,希望对你有所帮助。
zhanyd
2022/12/05
4.1K0
企业微信PC版应用跳转到默认浏览器,避坑指南,欢迎补充。。。
推荐阅读
相关推荐
Java企业微信开发_02_通讯录同步
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验