企业微信自建应用获取用户信息

news2025/3/14 20:39:08

一.前言

开发企业微信自建应用的时候难免会有获取企微个人信息的业务需求,这篇博客将详细说明企微自建应用获取userId的具体流程.

二.基本概念介绍

2.1 corpid

每个企业都拥有唯一的corpid,获取此信息可在管理后台“我的企业”-“企业信息”下查看“企业ID”(需要有管理员权限)

在这里插入图片描述

2.2 userid

每个成员都有唯一的userid,即所谓“账号”。在管理后台->“通讯录”->点进某个成员的详情页,可以看到

在这里插入图片描述

2.3 部门id

每个部门都有唯一的id,在管理后台->“通讯录”->“组织架构”->点击某个部门右边的小圆点可以看到

在这里插入图片描述

2.4 agentid

每个应用都有唯一的agentid。在管理后台->“应用管理”->“应用”,点进某个应用,即可看到agentid

在这里插入图片描述

2.5 secret

secret是企业应用里面用于保障数据安全的“钥匙”,每一个应用都有一个独立的访问密钥,为了保证数据的安全,secret务必不能泄漏

在这里插入图片描述

2.6 access_token

access_token是企业后台去企业微信的后台获取信息时的重要票据,由corpid和secret产生。所有接口在通信时都需要携带此信息用于验证接口的访问权限

接口调用流程:

在这里插入图片描述

三.获取access_token

获取access_token是调用企业微信API接口的第一步,相当于创建了一个登录凭证,其它的业务API接口,都需要依赖于access_token来鉴权调用者身份。因此开发者,在使用业务接口前,要明确access_token的颁发来源,使用正确的access_token。

  • 请求方式: GET(HTTPS)
  • 请求地址: https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET

此处标注大写的单词 ID 和 SECRET,为需要替换的变量,根据实际获取值更新。其它接口也采用相同的标注,不再说明

3.1 请求参数说明:

参数必须说明
corpid企业ID
corpsecret应用的凭证密钥,注意应用需要是启用状态(secret)

权限说明:

每个应用有独立的secret,获取到的access_token只能本应用使用,所以每个应用的access_token应该分开来获取

3.2 返回结果:

{
   "errcode": 0,
   "errmsg": "ok",
   "access_token": "accesstoken000001",
   "expires_in": 7200
}

响应参数说明:

参数说明
errcode出错返回码,为0表示成功,非0表示调用失败
errmsg返回码提示语
access_token获取到的凭证,最长为512字节
expires_in凭证的有效时间(秒)

3.3 注意事项:

  • 开发者需要缓存access_token,用于后续接口的调用(注意:不能频繁调用gettoken接口,否则会受到频率拦截)。当access_token失效或过期时,需要重新获取。

  • access_token的有效期通过返回的expires_in来传达,正常情况下为7200秒(2小时),有效期内重复获取返回相同结果,过期后获取会返回新的access_token。

  • 由于企业微信每个应用的access_token是彼此独立的,所以进行缓存时需要区分应用来进行存储。

  • access_token至少保留512字节的存储空间。

  • 企业微信可能会出于运营需要,提前使access_token失效,开发者应实现access_token失效时重新获取的逻辑。

四.获取访问用户身份

该接口用于根据code获取成员信息,适用于自建应用与代开发应用

  • 请求方式:GET(HTTPS)
  • 请求地址:https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE

4.1 请求参数说明:

参数必须说明
access_token调用接口凭证
code通过成员授权获取到的code,最大为512字节。每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期

权限说明:

跳转的域名须完全匹配access_token对应应用的可信域名,否则会返回50001错误

在这里插入图片描述

4.2 返回结果:

{
   "errcode": 0,
   "errmsg": "ok",
   "userid":"USERID",
   "user_ticket": "USER_TICKET"
}

响应参数说明:

参数说明
errcode返回码
errmsg对返回码的文本描述内容
userid成员UserID。若需要获得用户详情信息,可调用通讯录接口:读取成员。如果是互联企业/企业互联/上下游,则返回的UserId格式如:CorpId/userid
user_tickett 成员票据,最大为512字节,有效期为1800s。scope为snsapi_privateinfo,且用户在应用可见范围之内时返回此参数。后续利用该参数可以获取用户信息或敏感信息,参见"获取访问用户敏感信息"。暂时不支持上下游或/企业互联场景

五.代码编写

本次业务需求我只要获取userId即可,所以在后端采用静态变量存储access_token,如不满足你的业务需求可采用redis进行存储

5.1 前端

created() {
	this.getUserIdByCode()
}

methods: {
// 获取userID
getUserIdByCode() {
	console.log('钩子函数执行了')
	const code = this.GetQueryString('code')
	if (code) {
	// 通过code获取用户信息
	this.getUserId(code)
}
},
// 获取code
GetQueryString(name) {
	var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)')
	var r = window.location.search.substr(1).match(reg)
	if (r != null) return unescape(r[2]);
	return null
},
getUserId(code) {
	uni.request({
		url: 'http://localhost:13889/wechat/getUserId/' + code,
		method: 'POST',
		success: (res) => {
			this.userId = res.data.data
			console.log("员工工号:" + this.userId);
		}
	})
}
}

5.2 后端

5.2.1 企微常量参数
/**
 * 描述:企微常量参数
 * 创建人: 黎明
 * 创建时间: 2023/10/19
 * 版本: 1.0.0
 */
public interface WeChatConstant {

    // 企业ID
    public final static String CORP_ID = "企业ID";
    // 应用的凭证密钥
    public final static String SOD_SECRET = "应用的凭证密钥";
    // 获取access_token
    public final static String ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET";
    // 获取访问用户身份
    public final static String CODE_URL = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE";

}
5.2.2 工具类
/**
 * 描述:http工具类
 * 创建人: 黎明
 * 创建时间: 2023/12/25
 * 版本: 1.0.0
 */
@Slf4j
public class HttpUtils {

    /**
     * 获取企业微信的access_token
     *
     * @param corpid     企业ID
     * @param corpsecret 应用的凭证密钥
     * @return AccessToken对象,包含access_token等信息
     */
    public static AccessToken getAccessToken(String corpid, String corpsecret) {

        AccessToken accessToken = null;

        // 构造请求URL,将其中的ID和SECRET替换为实际参数
        String requestUrl = WeChatConstant.ACCESS_TOKEN_URL.replace("ID", corpid).replace("SECRET", corpsecret);
        // 发送HTTP GET请求,获取响应结果
        String resResult = HttpUtil.get(requestUrl);
        // 将响应结果转换为JSON对象
        JSONObject obj = new JSONObject(resResult);
        // 如果请求成功
        if (null != obj) {
            accessToken = new AccessToken();
            if (obj.getInt("errcode").equals(0)) {
                // 设置AccessToken对象的属性值
                accessToken.setErrcode(obj.getInt("errcode"));
                accessToken.setErrmsg(obj.getStr("errmsg"));
                accessToken.setAccessToken(obj.getStr("access_token"));
                accessToken.setExpiresIn(obj.getInt("expires_in"));
                accessToken.setTokenTime(DateUtil.date());
            } else {
                // 如果错误码不为0,表示请求失败,将accessToken设置为null
                accessToken = null;
                // 记录错误日志
                log.error("获取token失败 errcode:{} errmsg:{}", obj.getInt("errcode"), obj.getStr("errmsg"));
            }
        }
        // 返回AccessToken对象
        return accessToken;
    }
}
/**
 * 描述:日期工具类
 * 创建人: 黎明
 * 创建时间: 2023/12/25
 * 版本: 1.0.0
 */
public class DateUtils {

    /**
     * 根据date转换成localDateTime
     *
     * @param date 日期
     * @return {@link LocalDateTime}
     */
    public static LocalDateTime dateConvertLocalDateTime(Date date) {
        // 将Date对象转换为Instant对象,然后根据系统默认时区转换为LocalDateTime对象
        LocalDateTime localDateTime = Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime();
        return localDateTime;
    }

    /**
     * 根据localDateTime转换成date
     *
     * @param localDateTime 本地日期时间
     * @return Date对象
     */
    public static Date localDateTimeConvertDate(LocalDateTime localDateTime) {
        // 调用getZonedDateTimeByLocalDateTime方法将LocalDateTime对象转换为ZonedDateTime对象,然后将ZonedDateTime对象转换为Instant对象,最后将Instant对象转换为Date对象
        return Date.from(getZonedDateTimeByLocalDateTime(localDateTime).toInstant());
    }

    /**
     * 根据localDateTime转换成ZonedDateTime对象,用于把localDatTime转成Date
     *
     * @param localDateTime 本地日期时间
     * @return ZonedDateTime对象
     */
    public static ZonedDateTime getZonedDateTimeByLocalDateTime(LocalDateTime localDateTime) {
        // 获取系统默认时区
        ZoneId zoneId = ZoneId.systemDefault();
        // 将LocalDateTime对象转换为ZonedDateTime对象
        ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
        return zonedDateTime;
    }

}
5.2.3 封装access_token返回结果
@Data
public class AccessToken implements Serializable {
    /**
     * 返回码
     */
    private Integer errcode;
    /**
     * 返回消息
     */
    private String errmsg;
    /**
     * token
     */
    private String accessToken;
    /**
     * 凭证有效时间,单位:秒
     */
    private Integer expiresIn;
    /**
     * 获取token的时间
     */
    private Date tokenTime;
}
5.2.4 定义接口
 * 描述:获取企微人员信息
 * 创建人: 黎明
 * 创建时间: 2023/12/25
 * 版本: 1.0.0
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/wechat")
public class UserInfoController {

    private final WeChatServiceImpl weChatService;

    /**
     * 获取用户id
     *
     * @return 员工工号
     */
    @PostMapping ("/getUserId/{code}")
    public Result<String> getUserId(@PathVariable String code){
        String userId = weChatService.getUserIdByCode(code);
        return Result.success(userId);
    }
}
5.2.5 编写业务层代码
@Service
@Slf4j
public class WeChatServiceImpl {

    // 定义access_token静态变量,用于存储获取到的access_token
    private static String ACCESS_TOKEN = "";
    // 定义获取access_token的时间
    private static Date ACCESS_TOKEN_TIMR = DateUtil.date();

    /**
     * 根据微信授权码获取用户ID
     *
     * @param code 微信授权码
     * @return 用户ID
     */
    public String getUserIdByCode(String code) {
        // 初始化成员变量userId为空字符串
        String userId = "";
        // 如果ACCESS_TOKEN为空或者过期,则重新获取access_token
        if (StrUtil.hasEmpty(ACCESS_TOKEN)) {
            ACCESS_TOKEN = getUserInfo();
        }
        boolean flag = checkAccessToken(ACCESS_TOKEN_TIMR);
        // 如果access_token已过期,则重新获取access_token
        if (!flag) {
            ACCESS_TOKEN = getUserInfo();
        }
        // 构造请求URL,替换其中的ACCESS_TOKEN和CODE
        String userUrl = WeChatConstant.CODE_URL.replace("ACCESS_TOKEN", ACCESS_TOKEN).replaceAll("CODE", code);
        // 发送HTTP请求,获取响应结果
        String res = HttpUtil.get(userUrl);
        // 将响应结果转换为JSON对象
        JSONObject response = new JSONObject(res);
        // 如果响应中的错误编码为0,表示成功获取到用户信息,将用户ID赋值给userId
        if (response.getStr("errcode").equals("0")) {
            userId = response.getStr("userid");
        } else {
            // 如果错误编码不为0,记录日志,输出错误编码和错误信息
            log.info("未获取到人员信息,错误编码:{},错误信息:{}", response.getStr("errcode"), response.getStr("errmsg"));
        }
        // 返回用户ID
        return userId;
    }

    /**
     * 获取access_token
     *
     * @return access_token字符串
     */
    private static String getUserInfo() {
        // 调用HttpUtils工具类的getAccessToken方法,传入企业ID和应用密钥,获取access_token
        return HttpUtils.getAccessToken(WeChatConstant.CORP_ID, WeChatConstant.SOD_SECRET).getAccessToken();
    }

    /**
     * 检查access_token是否过期
     *
     * @param date 时间
     * @return 如果access_token未过期,返回true;否则返回false
     */
    private static boolean checkAccessToken(Date date) {
        // 将时间转换为LocalDateTime对象
        LocalDateTime localDateTimeNow = DateUtils.dateConvertLocalDateTime(date);
        // 将LocalDateTime对象转换为时间戳(毫秒)
        Long milliSecond = getTimestampOfDateTime(localDateTimeNow);
        // 将时间戳加上7200秒(2小时),得到新的过期时间
        milliSecond = milliSecond + (7200L * 1000L);
        // 将新的过期时间转换为LocalDateTime对象
        LocalDateTime dateTime = getDateTimeOfTimestamp(milliSecond);
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        // 如果当前时间在新的过期时间之前,说明access_token未过期,返回true;否则返回false
        if (now.isBefore(dateTime)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 将LocalDateTime对象转换为时间戳(毫秒)
     *
     * @param localDateTime 要转换的LocalDateTime对象
     * @return 对应的时间戳(毫秒)
     */
    private static long getTimestampOfDateTime(LocalDateTime localDateTime) {
        // 获取系统默认时区
        ZoneId zone = ZoneId.systemDefault();
        // 将LocalDateTime对象转换为Instant对象
        Instant instant = localDateTime.atZone(zone).toInstant();
        // 将Instant对象转换为时间戳(毫秒)并返回
        return instant.toEpochMilli();
    }

    /**
     * 将时间戳(毫秒)转换为LocalDateTime对象
     *
     * @param timestamp 要转换的时间戳(毫秒)
     * @return 对应的LocalDateTime对象
     */
    private static LocalDateTime getDateTimeOfTimestamp(long timestamp) {
        // 将时间戳(毫秒)转换为Instant对象
        Instant instant = Instant.ofEpochMilli(timestamp);
        // 获取系统默认时区
        ZoneId zone = ZoneId.systemDefault();
        // 将Instant对象转换为LocalDateTime对象并返回
        return LocalDateTime.ofInstant(instant, zone);
    }
}

六.构造网页授权链接

https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE&agentid=AGENTID#wechat_redirect

在这里插入图片描述

七.测试

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1337047.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode 1419. 数青蛙(medium)(优质解法)

链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚的技术成长平台 代码&#xff1a; class Solution {public int minNumberOfFrogs(String croakOfFrogs) {String t"croak";int tLengtht.length();// hash 数组用来存放青蛙喊的过程&#xf…

企业网银 相关注意事项合辑 不断更新中...

山西省农村信用社 (shanxinj.com) 企业网上银行 山西省农村信用社 企业网上银行&#xff0c;注意事项&#xff1a; 1、通过安装【网银向导】修复网银安全控件、密码控件等&#xff1b; 2、登录界面无Ukey验证&#xff0c;也就是输入企业号、用户编号、登录密码及验证码即可进…

ARM12.26

整理三个按键中断代码 key_it.h #ifndef __KEY_IT_H__ #define __KEY_IT_H__ #include"stm32mp1xx_gpio.h" #include"stm32mp1xx_gic.h" #include"stm32mp1xx_exti.h" #include"stm32mp1xx_rcc.h" #include"led.h" void k…

九、Seata的AT模式

目录 9.1 什么是弱一致性 &#xff1f;9.2 Seata的弱一致性9.3 Seata的AT模式介绍9.4 AT模式流程图9.5 AT模式注意点9.6 全局锁的理解1、认识全局锁2、注册全局锁3、校验&#xff08;获取&#xff09;全局锁4、释放锁5、结论 9.7 AT的多数据源场景 9.1 什么是弱一致性 &#xf…

算符优先语法分析设计原理与实现

前言&#xff1a; 作者的词法分析程序以及算符优先语法分析设计程序仓库链接 1、目标任务 **[实验项目] **以专题 1 词法分析程序的输出为语法分析的输入&#xff0c;实现算符优先分析算法&#xff0c;完成以下描述算术表达式的算符优先文法的算符优先分析过程。 G[E]:E→E…

PgSQL技术内幕 - ereport ERROR跳转机制

PgSQL技术内幕 - ereport ERROR跳转机制 使用客户端执行SQL的时候经常遇到报ERROR错误&#xff0c;然后SQL语句就退出了。当然&#xff0c;事务也会回滚掉。本文我们看下它是如何做到退出SQL语句并回滚事务的。 1、以insert一个numeric类型值为例 表一个字段为numeric(10,2)类型…

账号与权限管理

一、Linux用户 1.1用户类型 1.普通用户&#xff1a;权限受到限制的用户 2.超级管理员&#xff1a;至高无上的权限 3.程序用户&#xff1a;是给程序使用的&#xff0c;不允许登录(为了安全性考虑) 能不能打开文件和用户有关&#xff0c;用户有自己的权限 运行程序不能使用超…

关于chatglm3 function calling的理解

ChatGLM3-6B开源了工具调用&#xff0c;好奇他是怎么实现的&#xff0c;所以写了这个文章记录。 一、测试官方的示例 官方给的示例很简单&#xff0c;只不过给的两个函数 track 和 text-to-speech 没有具体的实现&#xff0c;模型的输出也只是给出了需要调用的函数名和参数。剩…

w16php系列之基础数组

一、索引数组 概念 索引数组 是指键名为整数的数组。默认情况下&#xff0c;索引数组的键名是从0开始&#xff0c;并依次递增。它主要适用于利用位置&#xff08;0、1、2……&#xff09;来标识数组元素的情况。另外&#xff0c;索引数组的键名也可以自己指定 示例代码 <…

网络调优,部署内网备份冗余和负载分担---实验

目录 网络调优&#xff0c;部署内网备份冗余和负载分担---实验 拓扑 需求 配置步骤&#xff1a; 配置命令: 网络调优&#xff0c;部署内网备份冗余和负载分担---实验 拓扑 需求 主机获取IP地址&#xff0c;访问WEB服务器&#xff0c;WEB服务器网关在SW5上SW5作为VLAN10,V…

【实施】共享目录防火墙

文章目录 一、共享文件1.1 为什么需要配置文件夹共享功能1.2 配置文件夹共享功能1.3 访问共享文件1.4 普通共享和高级共享 二、安装JDK和Tomcat2.1 安装JDK2.2 安装Tomcat 三、防火墙配置 一、共享文件 1.1 为什么需要配置文件夹共享功能 我们在工作和生活中经常有需要将自己…

2024年五个大胆的网络安全预测

纵观历史&#xff0c;网络安全行业不断对攻击者的转变以及运营现实的变化做出反应&#xff0c;新的一年也不例外。随着人工智能和云计算等新技术改变网络安全格局&#xff0c;攻击者和防御者之间的猫捉老鼠游戏肯定会加剧&#xff0c;并且会变得更加复杂。 再加上其他因素&…

带你学C语言~指针(3)

目录 ✍0.前言 &#x1f680;1.字符指针变量 &#x1f685;2.数组指针变量 &#x1f431;‍&#x1f3cd;2.1.数组指针变量是什么 &#x1f431;‍&#x1f3cd;2.2数组指针变量怎么初始化 &#x1f6a2;3.二维数组传参的本质 &#x1f680;4.函数指针变量 ✈4.1函数指…

15个主流设计灵感网站,激发你的创作灵感!

即时设计 即时设计是一种强大的云设计工具&#xff0c;已成为许多设计师、产品经理和开发人员的首选工具之一。即时设计用户可以使用内置的工具和功能快速创建和编辑设计&#xff0c;或与其他用户共享和合作。此外&#xff0c;即时设计还有一个丰富的资源社区&#xff0c;为用…

制造业数字化转型的核心不止是技术

一、制造业的数字化转型意味着什么&#xff1f; 在当今的制造业领域&#xff0c;数字化转型意味着通过集成数字技术来增强传统的制造方法、产品和劳动力的过程。这些技术包括一系列创新&#xff0c;如自动化软件、电子商务系统、传感器、工业机器人等。 二、制造业数字化转型的…

ubuntu 安装apisix -亲测可用

官方未提供在ubuntu系统中安装apisix的方式&#xff0c;似乎只能通过源码方式安装&#xff0c;但是并不推荐&#xff0c;非常容易失败&#xff0c; 具体操作方式如下&#xff1a; ubuntu和Debian其实类似的&#xff0c;可使用DEB方式安装&#xff0c;如下截图 注意&#xff1…

22000mAh 电池,这款国产新机来了场「续航」震撼

见惯了主流智能手机&#xff0c;是时候上一波离谱新机震撼了。 三防手机这一细分类型&#xff0c;咱们普通用户可能接触得比较少&#xff1b; 但对于极限运动、野外探险爱好者来说&#xff0c;这玩意儿可是关键时候能救命的必备神器。 在真正严苛环境面前&#xff0c;性能啥的…

《Vue2.X 进阶知识点》- 防 ElementUI Divider 分割线

前言 使用 el-divider 背景为白色是没问题的。 但当背景换成其它颜色&#xff0c;问题就出现了&#xff01;&#xff01; 仔细看原来是两层&#xff0c;默认背景色是白色。 想着把背景色改为透明应该能用&#xff0c;结果发现背面是一条实线&#xff0c;难怪要用白色遮挡…不符…

使用LLaMA-Factory微调ChatGLM3

1、创建虚拟环境 略 2、部署LLaMA-Factory &#xff08;1&#xff09;下载LLaMA-Factory https://github.com/hiyouga/LLaMA-Factory &#xff08;2&#xff09;安装依赖 pip3 install -r requirements.txt&#xff08;3&#xff09;启动LLaMA-Factory的web页面 CUDA_VI…

HarmonyOS4.0系统性深入开发05ArkTS卡片运行机制

ArkTS卡片运行机制 实现原理 图1 ArkTS卡片实现原理 卡片使用方&#xff1a;显示卡片内容的宿主应用&#xff0c;控制卡片在宿主中展示的位置&#xff0c;当前仅系统应用可以作为卡片使用方。卡片提供方&#xff1a;提供卡片显示内容的应用&#xff0c;控制卡片的显示内容、…