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

一.前言

开发企业微信自建应用的时候难免会有获取企微个人信息的业务需求,这篇博客将详细说明企微自建应用获取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.mfbz.cn/a/272648.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(1)llvm学习词法分析器

首先是Token取值 下面两个值记录当前识别出来的token单元的字面量 首先是空字符&#xff0c;然后如果是空字符&#xff0c;就读下一个字符&#xff0c;知道这个字符不是空为止&#xff0c;也就是LastChar出循环的时候是下一个不为空的字符,下面两个值是记录实际值用于后续处理 …

VR渲染器怎么用之自适应图像采样器,可解决渲染黑图问题

大家好&#xff0c;相信刚接触到vr渲染器时&#xff0c;会vr的图像采样器感到迷茫&#xff0c;如何使用图像采样器&#xff1f;图像采样器有何用呢&#xff1f; 今天便为大家讲解vr中的自适应DMC图像采样器的运用。 说明&#xff1a;高版本渲染器中的渲染块整合了原有的“固定…

PYTHON入门级游戏开发:宇宙飞船游戏两万字详析

手讲解超详细python入门游戏项目‘打外星飞船’手把&#xff08;一&#xff09; 由于内容比较多&#xff0c;这里会分为五篇文章来讲解&#xff0c;从页面的创建、飞船控制、射击、外星人创建、射杀外星人五片来展开。 做一个窗口和设置响应用户 import sysimport pygame&qu…

STM32逆变器方案

输入电压&#xff1a; 额定输入电压&#xff1a;DC110V 输入电压范围&#xff1a;DC77-137.5V 额定输出参数 电压&#xff1a;200V5%&#xff08;200VAC~240VAC 可调&#xff09; 频率&#xff1a; 42Hz0.5Hz&#xff08;35-50 可调&#xff09; 额定输出容量&#xff1a;1…

CentOS7搭建Elasticsearch与Kibana服务

1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器&#xff0c;因此需要让es和kibana容器互联。这里先创建一个网络&#xff1a; docker network create es-net 1.2拉取elasticsearch镜像 docker pull elasticsearch:7.11.1 1.3.运行 运行docker命令&#xff0c;部…

致远互联FE协作办公平台 editflow_manager.jsp SQL注入漏洞

漏洞描述 致远互联FE协作办公平台是一款为企业提供全方位协同办公解决方案的产品。它集成了多个功能模块&#xff0c;旨在帮助企业实现高效的团队协作、信息共享和文档管理。致远互联FE协作办公平台editflow_manager存在sql注入漏洞&#xff0c;攻击者可以获得敏感信息。 资产…

Vue框架引入Element-Ui

首先已经创建好了 Vue 框架&#xff0c;安装好了 node.js。 没有完成的可按照此博客搭建&#xff1a;搭建Vue项目 之后打开终端&#xff0c;使用命令。 1、命令引入 npm i element-ui -S2、package.json 查看版本 在 package.json 文件里可查看下载好的依赖版本。 3、在 ma…

Selenium自动化测试-设置元素等待

selenium中有三种时间等待&#xff1a; 强制等待&#xff1a;sleep 隐式等待&#xff1a;implicitly_wait 显示等待&#xff1a;WebDriverWait 1.sleep 让程序暂停运行一定时间&#xff0c;等待时间到达后继续运行。 使用sleep&#xff0c;需先导入time模块&#xff0c;im…

002、使用 Cargo 创建新项目,打印 Hello World

1. Cargo 简介 Cargo 是 Rust 的构建系统和包管理工具&#xff0c;比如构建代码、下载依赖的库、构建这些库等等。在安装 Rust 时&#xff0c;Cargo也会一起安装。 2. 创建新项目的具体步骤 步骤1&#xff1a; 我们在桌面新建一个文件夹&#xff0c;用于存放后面练习用的代码文…

vector的erase()方法遍历删除元素迭代器失效问题、及删除最后一个元素迭代器失效问题)

1.删除指定范围的元素 vector删除元素之pop_back(),erase(),remove() 向量容器vector的成员函数pop_back()可以删除最后一个元素. 而函数erase()可以删除由一个iterator指出的元素&#xff0c;也可以删除一个指定范围的元素。 还可以采用通用算法remove()来删除vector容器中的…

数字电子技术 一天速成

文章目录 一、数制与编码1. 数制转换2. BCD编码 二、逻辑代数1. 常见逻辑运算及逻辑门 三、化简逻辑表达式1. 卡诺图 求 表达式2. 表达式 画 卡诺图3. 卡诺图 化简 表达式4. 公式法 化简 表达式 ⭐⭐5. 表达式 求 反函数6. 卡诺图 求 反函数 四、组合逻辑电路的分析和设计1. 逻…

Mysql(5日志备份恢复)

一.日志管理 MySQL 的日志默认保存位置为 /usr/local/mysql/data 先看下mysql的日志文件有无&#xff1a; 修改配置文件添加&#xff1a;错误日志&#xff0c;用来记录当MySQL启动、停止或运行时发生的错误信息&#xff0c;默认已开启 修改配置文件添加&#xff1a;通用查…

.json文件转为.dll文件后还能读取吗?

(只是修改了后缀名而已&#xff0c;做一个伪装&#xff09; 测试&#xff1a; QFile file(QApplication::applicationDirPath() "/config.dll");qDebug()<<QApplication::applicationDirPath() "/config.dll";if (file.open(QIODevice::ReadOnly))…

设计模式--抽象工厂模式

实验4&#xff1a;抽象工厂模式 本次实验属于模仿型实验&#xff0c;通过本次实验学生将掌握以下内容&#xff1a; 1、理解抽象工厂模式的动机&#xff0c;掌握该模式的结构&#xff1b; 2、能够利用抽象工厂模式解决实际问题。 [实验任务]&#xff1a;人与肤色 使用抽象…

Netty—Reactor线程模型详解

文章目录 前言线程模型基本介绍线程模型分类Reactor线程模型介绍Netty线程模型&#xff1a; 传统阻塞IO的缺点Reactor线程模型单Reactor单线程模式单Reactor多线程模式主从Reactor多线程Reactor 模式小结 Netty 线程模型案例说明&#xff1a;Netty核心组件简介ChannelPipeline与…

服务运营 | 年终回顾:服务运营为您服务

文章作者&#xff1a;李舒湉&#xff0c;蔡君洋, Guo&#xff0c;陈盈鑫&#xff0c;王畅 编者按 在服务运营板块成立的第一年&#xff0c;给大家带来我们这一年中原创文章的年终回顾。迈向2024&#xff0c;服务运营继续为您服务 在服务运营板块成立的第一年&#xff0c;明确板…

预测块划分与亚像素精度:提升视频编码与图像处理的效率

在视频编码和图像处理中&#xff0c;预测块划分与亚像素精度是两项关键技术。本文将介绍预测块划分和亚像素精度的基本原理&#xff0c;探讨其在提高视频编码效率和图像处理精度方面的应用。 1. 预测块划分的基本原理 预测块划分是视频编码中的一项关键技术&#xff0c;它将图…

KCTF-Web-签到题

题目环境&#xff1a; 一道签到题 没有必要想那么麻烦 刚开始我以为是SQL注入 F12查源代码 在底部发现base64编码 进行base64解码ZmxhZ3t3ZTFjME1FX3RvXzB1Ul9jb050RVNUfQecho "ZmxhZ3t3ZTFjME1FX3RvXzB1Ul9jb050RVNUfQ" | base64 -d得到flag&#xff1a;flag{we1c0M…

spring状态机

1、概述 Spring State Machine 是一个用于处理状态机逻辑的框架&#xff0c;它提供了一种简洁的方法来定义状 态、转换以及在状态变更时触发的动作。 概念 状态 &#xff08; State &#xff09; &#xff1a;一个状态机至少要包含两个状态。例如自动门的例子&#xff0c;有 …

用芯片SIC8833可开发电子秤方案

SIC8833作为一款高性能的电子秤方案芯片&#xff0c;这款芯片是一个带24bitADC的8位RISC MCU&#xff0c;内置8k16位OTP程序存储器。具体24位双向I/O口的特性&#xff0c;广泛应用于电子衡器和精密测量及控制系统&#xff0c;能满足用户的不同需求和应用场景。 以下是电子秤方案…
最新文章