news 2026/5/19 14:15:37

使用飞书javaSDK拉取“成员活跃详情”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用飞书javaSDK拉取“成员活跃详情”

一、背景

到了年底,公司要对飞书使用情况进行总结、评比,需要导出飞书使用的数据,但是飞书管理员在后台只能导出最近1个月的数据。找了官方,说无法导出全年,他们紧急上线了1个年终总结的API。但是,我们公司跟他们发布的关注纬度不一样,最后导出数据的任务就掉我头上了。

二、思路

通过查阅官方文档《获取用户维度的用户活跃和功能使用数据》

三、过程

(一)在开发者后台创建应用

打开,创建应用并给予对应的权限。创建过程让初学者有点懵,我选的机器人。

(二)拿到应用凭证

(三)在线测试API

打开之前的《获取用户维度的用户活跃和功能使用数据》 ,点击右下角的“前往API调试台”,要先注意“切换应用”和“权限配置”。麻烦的是权限设置需要应用发布,但也没事。

测试成功的样子

(四)复制示例代码

(五)java工程创建

1.pom.xml

我为什么知道依赖什么呢?先看了javaSDK指南。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>potato</groupId> <artifactId>feishu-integration</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>feishu-integration-members</artifactId> <dependencies> <!-- 飞书Java SDK核心依赖 --> <dependency> <groupId>com.larksuite.oapi</groupId> <artifactId>oapi-sdk</artifactId> <version>2.1.0</version> <!-- 推荐使用最新稳定版,可在Maven中央仓库查询 --> </dependency> <!-- 可选:日志依赖,便于调试 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.36</version> <scope>test</scope> </dependency> </dependencies> </project>

2.具体的拉取代码

我的复制了代码后,让豆包完善,我再修改的。我的核心是尽量少使用依赖,导出csv文件,每天请求导出1次,每1万行追加写入1次。注意API对时间要求是跨度不超过31天,每页导出不超过60条,不同API要求不同。

package cn.potato.feishu; import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.lark.oapi.Client; import com.lark.oapi.service.admin.v1.model.AdminUserStat; import com.lark.oapi.service.admin.v1.model.ListAdminUserStatReq; import com.lark.oapi.service.admin.v1.model.ListAdminUserStatResp; import com.lark.oapi.service.admin.v1.model.ListAdminUserStatRespBody; public class FeishuEmployeeFetcher { // 替换为你的应用凭证 private static final String APP_ID = "你的APP_ID"; private static final String APP_SECRET = "你的APP_SECRET"; // 分批阈值:每1万条数据写入一次 private static final int BATCH_SIZE = 10000; // 标记是否是第一次写入(用于判断是否需要写入表头) private static boolean isFirstWrite = true; // 日期格式化器(适配飞书API日期格式要求) private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 全局查询范围:2025-01-01 至 2025-12-20 private static final LocalDate GLOBAL_START_DATE = LocalDate.of(2025, 1, 1); private static final LocalDate GLOBAL_END_DATE = LocalDate.of(2025, 12, 20); // 初始化飞书Client private static Client initFeishuClient() { // 创建Client,自动处理token缓存与刷新 Client client = Client.newBuilder(APP_ID, APP_SECRET).logReqAtDebug(true)// 开启日志,便于调试 .build(); return client; } /** * 按天分段获取所有数据(核心入口方法) */ public static void getAllDataByDay() throws Exception { Client client = initFeishuClient(); LocalDate currentDate = GLOBAL_START_DATE; // 循环遍历每一天,直到超过全局结束日期 while (!currentDate.isAfter(GLOBAL_END_DATE)) { System.out.println(String.format("=====================================")); System.out.println(String.format("开始查询【%s】的员工数据", currentDate.format(DATE_FORMATTER))); // 获取当天的数据(开始日期和结束日期都是当前天) fetchDataBySingleDay(client, currentDate); // 日期递增1天,处理下一天 currentDate = currentDate.plusDays(1); } System.out.println(String.format("=====================================")); System.out.println("所有日期数据查询并写入完毕!"); } /** * 根据指定日期获取当天数据并分批写入CSV * @param client 飞书Client * @param targetDate 目标日期(当天) */ private static void fetchDataBySingleDay(Client client, LocalDate targetDate) throws Exception { // 临时缓存批次数据 List<AdminUserStat> batchAusList = new ArrayList<AdminUserStat>(); String pageToken = "0"; boolean hasMore = true; String dateStr = targetDate.format(DATE_FORMATTER); while (hasMore) { System.out.println(String.format(" 分页查询中,pageToken:%s", pageToken)); // 创建请求对象(开始日期和结束日期均为当天,满足API时间范围要求) ListAdminUserStatReq req = ListAdminUserStatReq.newBuilder() .startDate(dateStr) .endDate(dateStr) .pageToken(pageToken) .pageSize(50) .build(); // 发起请求 ListAdminUserStatResp resp = client.admin().adminUserStat().list(req); // 处理服务端错误 if (!resp.success()) { System.out.println(String.format(" 【%s】数据查询失败:code:%s,msg:%s,reqId:%s", dateStr, resp.getCode(), resp.getMsg(), resp.getRequestId())); return; } ListAdminUserStatRespBody lausrb = resp.getData(); AdminUserStat[] aus = lausrb.getItems(); if (aus != null && aus.length > 0) { batchAusList.addAll(Arrays.asList(aus)); System.out.println(String.format(" 本次分页获取到%s条数据,当前批次累计%s条", aus.length, batchAusList.size())); } else { System.out.println(" 本次分页无数据"); } // 判断是否达到批次阈值,达到则写入文件并清空临时列表 if (batchAusList.size() >= BATCH_SIZE) { appendToCsvFile(batchAusList, "d:/1.csv"); batchAusList.clear(); // 清空临时列表,继续缓存后续数据 System.out.println(" 已写入1万条数据,继续分页查询..."); } // 更新分页信息 pageToken = lausrb.getPageToken(); hasMore = lausrb.getHasMore(); } // 处理当天最后一批不足1万条的数据 if (!batchAusList.isEmpty()) { appendToCsvFile(batchAusList, "d:/1.csv"); batchAusList.clear(); System.out.println(String.format(" 【%s】最后一批数据写入完成", dateStr)); } else { System.out.println(String.format(" 【%s】无员工数据", dateStr)); } } /** * 追加写入CSV文件(支持分批写入,自动判断是否写入表头) * @param ausList 批次数据列表 * @param csvFilePath CSV文件路径 */ public static void appendToCsvFile(List<AdminUserStat> ausList, String csvFilePath) { // 1. 定义CSV表头 String[] headers = { "日期", "User ID", "姓名", "部门", "发送消息数", "创建文件数", "创建日程数", "会议时长(分钟)", "会议数", "创建任务数", "总发件量", "总收件量", "对外发件数", "对内发件数", "来自外部收件数", "来自内部收件数" }; // 2. 以追加模式打开文件(FileOutputStream第二个参数为true表示追加,不覆盖原有数据) try (BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(csvFilePath, true), StandardCharsets.UTF_8))) { // 第一次写入时,先写入表头(后续日期均为追加数据,不重复写表头) if (isFirstWrite) { writer.write(String.join(",", headers)); writer.newLine(); isFirstWrite = false; // 写入后标记为非首次 } // 遍历批次数据,追加写入每行内容 if (ausList != null && !ausList.isEmpty()) { for (AdminUserStat aus : ausList) { // 处理字段空值,避免空指针异常;转义双引号,防止破坏CSV格式 String date = aus.getDate() == null ? "" : aus.getDate().replace("\"", "\"\""); String userId = aus.getUserId() == null ? "" : aus.getUserId().replace("\"", "\"\""); String userName = aus.getUserName() == null ? "" : aus.getUserName().replace("\"", "\"\""); String departmentPath = aus.getDepartmentPath() == null ? "" : aus.getDepartmentPath().replace("\"", "\"\""); Long sendMessengerNum = aus.getSendMessengerNum() == null ? 0L : aus.getSendMessengerNum(); Long createDocsNum = aus.getCreateDocsNum() == null ? 0L : aus.getCreateDocsNum(); Long createCalNum = aus.getCreateCalNum() == null ? 0L : aus.getCreateCalNum(); Long vcDuration = aus.getVcDuration() == null ? 0L : aus.getVcDuration(); Long createTaskNum = aus.getCreateTaskNum() == null ? 0L : aus.getCreateTaskNum(); Long vcNum = aus.getVcNum() == null ? 0L : aus.getVcNum(); Long emailSendCount = aus.getEmailSendCount() == null ? 0L : Long.parseLong(aus.getEmailSendCount()); Long emailReceiveCount = aus.getEmailReceiveCount() == null ? 0L : Long.parseLong(aus.getEmailReceiveCount()); Long emailSendExtCount = aus.getEmailSendExtCount() == null ? 0L : Long.parseLong(aus.getEmailSendExtCount()); Long emailReceiveExtCount = aus.getEmailReceiveExtCount() == null ? 0L : Long.parseLong(aus.getEmailReceiveExtCount()); Long emailSendInCount = aus.getEmailSendInCount() == null ? 0L : Long.parseLong(aus.getEmailSendInCount()); Long emailReceiveInCount = aus.getEmailReceiveInCount() == null ? 0L : Long.parseLong(aus.getEmailReceiveInCount()); // 拼接字段值,用引号包裹避免逗号/换行符破坏CSV格式 String line = String.format( "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"", date, userId, userName, departmentPath, sendMessengerNum, createDocsNum, createCalNum, vcDuration, createTaskNum, vcNum, emailSendCount, emailReceiveCount, emailSendExtCount, emailReceiveExtCount, emailSendInCount, emailReceiveInCount); writer.write(line); writer.newLine(); } } System.out.println(String.format(" 成功追加%s条数据到CSV文件:%s", ausList.size(), csvFilePath)); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("追加写入CSV文件失败:" + e.getMessage()); } } // 主方法测试 public static void main(String[] args) throws Exception { getAllDataByDay(); } }

四、感受

1.总体来说,飞书的API比较完。

2.代码注释给力,开发者友好。

3.示例代码与依赖文档衔接友好,示例代码中有文档链接

4.在线API测试没有问题,先下就可以运行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/13 20:46:59

nrf52832的mdk下载程序环境搭建入门必看

从零开始搭建 nRF52832 的 Keil MDK 烧录环境&#xff1a;新手避坑全指南 你是不是也经历过这样的场景&#xff1f; 手里的 nRF52832 开发板接上电脑&#xff0c;打开 Keil&#xff0c;点下载按钮却弹出“Cannot access target”或“Flash algorithm failed”……明明代码写得…

作者头像 李华
网站建设 2026/5/13 20:46:56

AI大模型支持下的:SCI论文写作(含引言、文献综述、假设、方法、结果、讨论、结论)

随着学术竞争日益激烈&#xff0c;论文写作的效率与质量成为科研入门者、职场晋升者及在校学子的核心诉求&#xff0c;传统写作模式中文献检索繁琐、框架构建迷茫、数据分析低效等痛点愈发凸显&#xff0c;而AI技术的飞速发展为打破这些瓶颈提供了全新可能。精准对接学术写作需…

作者头像 李华
网站建设 2026/5/13 20:46:58

GPT-SoVITS语音模型导出格式说明(ONNX/TensorRT)

GPT-SoVITS语音模型导出格式说明&#xff08;ONNX/TensorRT&#xff09; 在个性化语音合成技术迅速普及的今天&#xff0c;用户不再满足于千篇一律的“机器音”&#xff0c;而是期待能听到亲人、偶像甚至虚拟角色的声音。如何用极少量样本快速克隆音色&#xff0c;并实现实时高…

作者头像 李华
网站建设 2026/5/15 23:02:04

hal_uart_transmit驱动移植到自定义平台的操作指南

如何把hal_uart_transmit移植到自定义平台&#xff1a;从原理到实战的完整指南在嵌入式开发中&#xff0c;串口通信就像“工程师的眼睛”——调试信息靠它输出&#xff0c;设备交互靠它传递。而HAL_UART_Transmit作为 STM32 HAL 库中最常用的阻塞式发送函数&#xff0c;早已成为…

作者头像 李华
网站建设 2026/5/16 3:56:08

Open-AutoGLM宣传视频哪里下载?资深工程师透露内部获取路径

第一章&#xff1a;智谱Open-AutoGLM 宣传视频下载 获取官方宣传资源 智谱AI推出的Open-AutoGLM是一款面向自动化代码生成与自然语言理解的开源大模型工具。为帮助开发者快速了解其核心功能&#xff0c;官方提供了高质量的宣传视频&#xff0c;涵盖模型架构、应用场景及部署演…

作者头像 李华