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

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

作者头像
geekfly
发布2022-05-06 19:44:48
2.2K0
发布2022-05-06 19:44:48
举报
文章被收录于专栏:geekflygeekfly

前言

众所周知,在微信公众平台开发中,其实就是一系列的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
复制
	影响:两次带来的影响都是致命的,犹如定时炸弹,完全不清楚下次会何时继续出现。

改进方案

这里写图片描述
这里写图片描述

总结:

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


代码展示

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

  • WorkWXAPI类: 定义了企业微信相关的API请求的URL地址,以及其他企业微信相关的常量等。
代码语言:javascript
复制
/**
 * @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
复制
/**
 * @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
复制
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
复制
	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
复制
Map<String, Object>  jsonMap = WXAPIUtil.updateContact(JsonUtil.toJson(wxUser));
代码语言:javascript
复制
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 删除。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 问题描述
  • 改进方案
  • 代码展示
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档