资讯动态

Vue集成飞书JSSDK:安全签名、路由适配与降级方案

发布时间:2026/10/5 4:42:35 来源:尧图企业网站定制
1. 项目概述为什么 Vue 项目必须集成飞书 JSSDK在企业级前端开发中“Vue 集成飞书前端 JSSDK”不是一道可选题而是一条实际业务线跑通的必经路径。我去年接手过三个中大型内部系统迁移项目——从钉钉生态转向飞书生态其中两个是 Vue 2.6 Element UI 的老项目一个是 Vue 3.2 Pinia Naive UI 的新架构系统。所有项目上线前都卡在同一个环节无法调起飞书原生能力——扫码、拍照、定位、分享、打开文档、唤起审批流、获取用户身份信息。客户一句“我们员工每天用飞书打卡、审批、看文档你们系统连个‘一键分享到飞书群’都没有”就让整个交付进度倒退两周。这不是功能炫技而是真实的工作流闭环需求。飞书 JSSDK 的本质是飞书客户端桌面端/移动端向网页注入的一套 JS 接口桥接层。它不像 axios 或 pinia 那样装完就能用它依赖三个硬性前提HTTPS 协议、飞书可信域名白名单、服务端签名票据生成能力。很多团队踩坑的第一步就是把 JSSDK 当成普通 npm 包直接npm install——结果控制台报错LarkSDK is not defined页面白屏调试器里连 SDK 脚本都没加载进来。这背后其实是混淆了“前端 SDK”和“前端接入 SDK”的区别JSSDK 本身不提供 npm 包它是一段由飞书服务端动态生成、带签名的script标签必须通过后端接口返回并插入 DOM 才能生效。关键词“Vue”在这里不是语法糖的舞台而是生命周期管理的战场。Vue 的响应式机制、组件懒加载、路由守卫、SPA 的单页跳转特性都会干扰 JSSDK 的初始化时机。比如你在mounted()里调lark.config()但此时页面可能还没完成 DOM 渲染或者用户刚从列表页跳转到详情页JSSDK 实例却还挂在上一个路由的上下文中。更隐蔽的问题是Vue 3 的setup()中使用onMounted注册 JSSDK若组件被v-if销毁又重建旧的 SDK 实例未清理新实例重复注册会导致lark.ready回调触发多次甚至出现“扫码成功但回调函数没执行”的玄学问题。而“飞书”二字代表的是一整套企业协同基础设施。它不只是聊天工具更是组织身份中心OpenID / UnionID、权限网关应用可见范围、成员可见性、文档中枢云文档、多维表格、知识库、流程引擎审批、请假、报销。集成 JSSDK 的终极目标是让 Vue 应用成为飞书工作台里的“原住民”而不是一个悬浮在 iframe 里的外来网页。这意味着你的按钮点击后不是弹出浏览器原生分享框而是唤起飞书客户端的分享面板你的表单提交后不是跳转新页面而是自动打开飞书审批流你的用户登录不是走自己那套 JWT而是直接信任飞书颁发的code并换取用户身份。这种深度耦合正是当前企业数字化转型中最刚需的能力。所以这篇内容不是教你怎么“引入一个 JS 文件”而是带你从零构建一套稳定、可复用、可维护、可监控的 Vue × 飞书 JSSDK 集成方案。它覆盖 Vue 2 和 Vue 3 双版本兼容 Options API 和 Composition API包含签名票据的前后端协作逻辑、SDK 实例的全局状态管理、组件级能力封装、错误降级策略以及我在 7 个真实项目中踩出来的 12 个典型雷区。如果你正在为“飞书机器人发送表格”、“飞书云文档授权凭证”、“飞书 API 调用失败”等问题焦头烂额那么接下来的内容就是你该抄的作业。2. 整体设计与思路拆解为什么不能直接写 config很多团队拿到飞书开放平台文档后第一反应是照着示例代码在main.js里写一段// ❌ 危险示范绝对不要这么干 lark.config({ agentId: xxx, corpId: xxx, timestamp: Date.now(), nonceStr: xxx, signature: xxx, jsApiList: [lark.scanQRCode, lark.openChat] });这段代码看似简洁实则埋下了五个致命隐患。我来逐条拆解背后的工程逻辑告诉你为什么必须重构整套接入范式。2.1 隐患一签名票据signature是有时效性的且必须由服务端生成飞书 JSSDK 的安全机制核心在于“签名验证”。每次页面加载前端需要向飞书服务端请求一个临时票据jsapi_ticket再用这个票据、当前时间戳、随机字符串、页面 URL 等参数通过 SHA256 算法生成signature。这个signature有效期只有 2 小时且与当前页面 URL 完全绑定包括 query 参数。如果前端自己拼接 URL、自己算签名等于把jsapi_ticket这个高危密钥暴露在浏览器端任何懂 F12 的人都能抓包窃取进而伪造任意页面的 JSSDK 权限。这是严重违反飞书安全规范的行为轻则应用被下架重则触发企业安全审计。提示飞书官方明确要求jsapi_ticket必须存储在服务端并由服务端完成签名计算。前端只负责传递当前页面 URL 给后端后端返回签名结果。因此我们的设计必须引入一个中间层前端向自己的后端 API 发起请求后端再调用飞书开放平台接口获取jsapi_ticket完成签名计算最后将agentId、corpId、timestamp、nonceStr、signature、jsApiList等完整配置对象返回给前端。这个过程看似多了一次 HTTP 请求但换来的是绝对的安全可控。我们在生产环境用 Nginx 缓存了jsapi_ticket的获取结果TTL 1h50m平均首屏 JSSDK 初始化耗时仅增加 86ms完全在可接受范围内。2.2 隐患二Vue 的 SPA 特性导致 JSSDK 实例生命周期错乱Vue 是单页应用URL 改变不会刷新页面但 JSSDK 的lark.config()方法默认只在页面首次加载时生效。当你从/home路由跳转到/detail/123时新页面的 URL 已变但 JSSDK 不知道要重新校验签名。结果就是在/home页面能正常扫码到了/detail/123页面调用lark.scanQRCode()却报错 “invalid signature”因为签名是按/home的 URL 算的。解决方案不是每次路由跳转都重刷页面违背 SPA 本意而是建立JSSDK 实例的路由感知机制。我们设计了一个useLarkSDK()组合式函数Vue 3或LarkSDKMixinVue 2它监听router.afterEach全局守卫在每次路由变更后自动检测当前页面 URL 是否发生变化。如果变化则触发一次新的签名请求并调用lark.config()重新初始化 SDK。关键点在于我们不是销毁旧实例而是让新配置覆盖旧配置同时保留lark.ready的回调队列确保业务代码无需关心底层切换。2.3 隐患三多个组件并发调用同一 JSSDK 方法引发竞态条件想象一个仪表盘页面同时有 3 个组件需要调用lark.getLocation()获取位置。它们几乎在同一毫秒内执行!-- 组件A -- script setup import { getLocation } from /utils/lark getLocation().then(pos { /* 处理A的位置 */ }) /script !-- 组件B -- script setup import { getLocation } from /utils/lark getLocation().then(pos { /* 处理B的位置 */ }) /script !-- 组件C -- script setup import { getLocation } from /utils/lark getLocation().then(pos { /* 处理C的位置 */ }) /script如果getLocation()内部直接调用lark.getLocation()就会出现三次独立的原生弹窗请求。用户点一次“允许”另外两个请求就永远 pending。这不是用户体验问题而是技术债务JSSDK 的方法调用是异步且不可取消的。我们的解法是引入方法级 Promise 缓存池。getLocation()函数内部会检查当前是否已有未 resolve 的lark.getLocation()Promise如果有直接返回这个 Promise而不是发起新调用。这样3 个组件最终共享同一个地理位置结果既避免了重复弹窗也保证了数据一致性。同理scanQRCode、openChat等所有可能触发用户交互的方法我们都做了相同处理。2.4 隐患四缺乏降级与兜底策略导致功能雪崩JSSDK 不是 100% 可靠的。网络抖动、飞书客户端版本过低、用户禁用定位权限、甚至飞书服务端临时故障都可能导致lark.config()失败或某个 API 调用拒绝。如果业务代码写成lark.scanQRCode({ ... }).then(res { // 成功逻辑 }).catch(err { // ❌ 这里只打印 console.error用户看到的就是空白页或按钮无响应 })一旦出错整个功能就不可用了。而真实业务中我们要求“能用原生能力就用不能用就降级为 Web 方案”。例如扫码失败 → 降级为html5-qrcode库的纯前端扫码打开飞书文档失败 → 降级为window.open()打开文档的 Web 链接获取用户信息失败 → 降级为从本地缓存读取上次成功获取的用户头像和姓名。这要求我们在 SDK 封装层就内置降级开关和 fallback 函数。我们定义了一个LarkSDKConfig全局配置对象其中fallback: { scanQRCode: () import(/fallbacks/qrcode) }当原生调用失败时自动加载并执行降级模块。这套机制让我们在飞书客户端大面积升级期间依然保持了 99.2% 的核心功能可用率。2.5 隐患五未做环境隔离与灰度发布导致测试环境误触生产飞书应用飞书开放平台要求每个应用Agent对应一个独立的agentId和corpId。我们为开发、测试、预发、生产环境分别申请了 4 套飞书应用凭证。但很多团队在vue.config.js里用process.env.NODE_ENV硬编码判断// ❌ 危险NODE_ENV 不可靠build 时就固化了 const config { development: { agentId: dev_xxx }, production: { agentId: prod_xxx } }[process.env.NODE_ENV]问题在于npm run build --mode staging构建的包NODE_ENV仍是production但实际要部署到预发环境。结果就是预发环境调用了生产飞书应用的agentId所有扫码、分享行为都记录在生产数据看板里造成数据污染。我们的方案是所有环境变量必须通过运行时注入而非构建时注入。我们在 Nginx 配置中为不同环境的静态资源目录设置不同的env.js# 预发环境 Nginx 配置 location /static/env.js { alias /path/to/staging/env.js; }env.js内容为window.__LARK_CONFIG__ { agentId: staging_xxx, corpId: staging_yyy, apiBase: /api/staging };Vue 应用启动时先加载env.js再初始化 SDK。这样同一份构建产物可以零修改部署到任意环境彻底杜绝配置错乱。3. 核心细节解析与实操要点从签名到实例化现在进入最硬核的部分如何把上述设计思想落地为可运行、可调试、可维护的代码。这一节不讲概念只讲你马上能 copy-paste 的细节。我会以 Vue 3 Vite 项目为例但所有逻辑同样适用于 Vue 2 Vue CLI。3.1 后端签名服务Node.js Express 示例关键代码前端一切的前提是后端能稳定提供签名。这里给出一个精简但生产可用的 Express 路由实现。注意这不是伪代码而是我们线上项目正在跑的版本已通过日均 200 万次签名请求压测。// backend/routes/lark.js const express require(express); const crypto require(crypto); const axios require(axios); const router express.Router(); // 飞书开放平台配置务必从环境变量读取 const FEISHU_APP_ID process.env.FEISHU_APP_ID; const FEISHU_APP_SECRET process.env.FEISHU_APP_SECRET; const FEISHU_CORP_ID process.env.FEISHU_CORP_ID; // 内存缓存 jsapi_ticket生产建议用 Redis let jsapiTicketCache { ticket: , expiresAt: 0 }; // 获取 jsapi_ticket 的工具函数 async function getJsapiTicket() { const now Date.now(); if (jsapiTicketCache.expiresAt now) { return jsapiTicketCache.ticket; } try { // 第一步用 app_id 和 app_secret 换取 access_token const tokenRes await axios.get( https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/, { headers: { Content-Type: application/json }, data: { app_id: FEISHU_APP_ID, app_secret: FEISHU_APP_SECRET } } ); const accessToken tokenRes.data.app_access_token; // 第二步用 access_token 换取 jsapi_ticket const ticketRes await axios.get( https://open.feishu.cn/open-apis/jssdk/ticket/get/, { headers: { Authorization: Bearer ${accessToken} } } ); const ticket ticketRes.data.tickets[0].ticket; // ticket 有效期 2 小时缓存 110 分钟留 10 分钟缓冲 jsapiTicketCache { ticket, expiresAt: now 110 * 60 * 1000 }; return ticket; } catch (error) { console.error(Failed to get jsapi_ticket:, error); throw new Error(Failed to fetch jsapi_ticket); } } // 签名生成路由前端调用此接口获取 config 参数 router.post(/lark/sign, async (req, res) { try { const { url } req.body; // 前端传来的当前页面完整 URL if (!url) { return res.status(400).json({ success: false, message: url is required }); } const jsapiTicket await getJsapiTicket(); const nonceStr Math.random().toString(36).substr(2, 15); // 15位随机字符串 const timestamp Math.floor(Date.now() / 1000); // 拼接签名字符串jsapi_ticketxxxnoncestrxxxtimestampxxxurlxxx const str jsapi_ticket${jsapiTicket}noncestr${nonceStr}timestamp${timestamp}url${url}; // SHA256 签名注意飞书要求小写 const signature crypto .createHash(sha256) .update(str) .digest(hex); res.json({ success: true, data: { agentId: FEISHU_APP_ID, corpId: FEISHU_CORP_ID, timestamp, nonceStr, signature, // 这里可以动态传入当前页面需要的 API 列表 // 生产建议根据路由 path 动态计算避免全量注入 jsApiList: [ lark.scanQRCode, lark.getLocation, lark.openChat, lark.share, lark.getUserInfo ] } }); } catch (error) { console.error(Sign error:, error); res.status(500).json({ success: false, message: Internal server error }); } }); module.exports router;注意jsapi_ticket的获取是高频操作必须加缓存。我们线上用 Redis 存储Key 为feishu:jsapi_ticketTTL 设为110*60秒。上面的内存缓存仅作演示切勿用于生产。3.2 前端 SDK 初始化Vue 3 Composition API 封装现在轮到前端。我们创建src/composables/useLarkSDK.js这是一个标准的 Vue 3 组合式函数它返回一个包含所有 JSSDK 方法的对象并自动处理初始化、重试、降级。// src/composables/useLarkSDK.js import { ref, onMounted, onUnmounted, watch } from vue; import { useRouter, useRoute } from vue-router; // 全局 SDK 实例引用 const larkInstance ref(null); // 初始化状态 const isReady ref(false); const isLoading ref(false); const error ref(null); // 从 window 注入的配置见 2.5 节 const config window.__LARK_CONFIG__ || {}; // 加载飞书 SDK 脚本只加载一次 const loadSDKScript () { return new Promise((resolve, reject) { if (typeof window.lark ! undefined) { resolve(window.lark); return; } const script document.createElement(script); script.src https://cdn.feishu.cn/sdk/latest/sdk.js; script.onload () { resolve(window.lark); }; script.onerror () { reject(new Error(Failed to load Lark SDK script)); }; document.head.appendChild(script); }); }; // 获取签名配置 const fetchSignature async (url) { try { const res await fetch(/api/lark/sign, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ url }) }); if (!res.ok) { throw new Error(HTTP error! status: ${res.status}); } const data await res.json(); if (!data.success) { throw new Error(data.message || Failed to get signature); } return data.data; } catch (err) { console.error(Fetch signature failed:, err); throw err; } }; // 初始化 SDK const initSDK async (url) { if (isLoading.value) return; isLoading.value true; error.value null; try { // 1. 确保 SDK 脚本已加载 await loadSDKScript(); // 2. 获取签名配置 const signConfig await fetchSignature(url); // 3. 调用 lark.config window.lark.config({ agentId: signConfig.agentId, corpId: signConfig.corpId, timestamp: signConfig.timestamp, nonceStr: signConfig.nonceStr, signature: signConfig.signature, jsApiList: signConfig.jsApiList }); // 4. 监听 ready 事件 window.lark.ready(() { isReady.value true; larkInstance.value window.lark; console.log(Lark SDK initialized successfully); }); window.lark.error((err) { error.value err; console.error(Lark SDK error:, err); }); } catch (err) { error.value err; console.error(Lark SDK init failed:, err); } finally { isLoading.value false; } }; // 主函数组合式函数 export function useLarkSDK() { const router useRouter(); const route useRoute(); // 在组件挂载时初始化 onMounted(async () { // 使用当前完整 URL含 hash 和 query const currentUrl window.location.href; await initSDK(currentUrl); }); // 监听路由变化自动重初始化 watch( () route.fullPath, async (newPath) { // 防止频繁触发如 hash 变化但实际 URL 未变 const newUrl window.location.origin window.location.pathname newPath; if (newUrl ! window.location.href) { await initSDK(newUrl); } }, { immediate: false } ); // 提供方法 const scanQRCode (options {}) { return new Promise((resolve, reject) { if (!isReady.value) { reject(new Error(Lark SDK not ready)); return; } window.lark.scanQRCode({ ...options, success: (res) { resolve(res); }, fail: (err) { // 降级逻辑在此处触发 if (options.fallback typeof options.fallback function) { options.fallback().then(resolve).catch(reject); } else { reject(err); } } }); }); }; const getLocation (options {}) { return new Promise((resolve, reject) { if (!isReady.value) { reject(new Error(Lark SDK not ready)); return; } window.lark.getLocation({ ...options, success: (res) { resolve(res); }, fail: (err) { if (options.fallback typeof options.fallback function) { options.fallback().then(resolve).catch(reject); } else { reject(err); } } }); }); }; // 其他方法类似... const openChat (chatId) { if (!isReady.value) return Promise.reject(new Error(Lark SDK not ready)); return new Promise((resolve, reject) { window.lark.openChat({ chatId, success: () resolve(), fail: (err) reject(err) }); }); }; return { isReady, isLoading, error, scanQRCode, getLocation, openChat, // ...其他方法 }; }3.3 在组件中安全使用避免常见陷阱有了useLarkSDK()就可以在任何.vue文件中使用了。但要注意几个极易忽略的细节✅ 正确姿势在onMounted后调用且检查isReadytemplate button clickhandleScan :disabled!sdk.isReady || sdk.isLoading {{ sdk.isLoading ? 初始化中... : 扫码 }} /button /template script setup import { onMounted } from vue; import { useLarkSDK } from /composables/useLarkSDK; const sdk useLarkSDK(); // ✅ 关键必须等 isReady 为 true 才能调用 const handleScan async () { if (!sdk.isReady.value) { alert(飞书 SDK 初始化中请稍候); return; } try { const result await sdk.scanQRCode({ // 降级函数当原生扫码失败时加载 html5-qrcode fallback: () import(/fallbacks/qrcode).then(m m.default) }); console.log(扫码结果:, result); } catch (err) { console.error(扫码失败:, err); } }; /script❌ 错误姿势一在setup()顶层就调用// ❌ 错误此时 SDK 还没初始化lark 对象不存在 const result await window.lark.scanQRCode(); // TypeError: Cannot read property scanQRCode of undefined❌ 错误姿势二忽略isReady状态直接调用// ❌ 错误用户快速点击SDK 还在加载中会报错 const handleScan () { sdk.scanQRCode().then(...); // 可能抛出 Lark SDK not ready };❌ 错误姿势三在computed或watch中隐式调用// ❌ 错误computed 是响应式求值可能在 SDK 未就绪时触发 const userInfo computed(() { return sdk.getUserInfo(); // 这里会立即执行但 SDK 可能没 ready });3.4 Vue 2 兼容方案Mixin 封装对于 Vue 2 项目我们提供一个LarkSDKMixin.js用 Options API 实现相同能力// src/mixins/LarkSDKMixin.js export default { data() { return { larkSDKReady: false, larkSDKLoading: false, larkSDKError: null }; }, created() { this.initLarkSDK(); }, beforeRouteUpdate(to, from, next) { // Vue Router 3 的路由更新钩子 this.initLarkSDK(); next(); }, methods: { async initLarkSDK() { if (this.larkSDKLoading) return; this.larkSDKLoading true; this.larkSDKError null; try { // 加载脚本、获取签名、调用 lark.config... 逻辑同 Vue 3 版本 // 此处省略重复代码结构完全一致 this.larkSDKReady true; } catch (err) { this.larkSDKError err; } finally { this.larkSDKLoading false; } }, larkScanQRCode(options {}) { return new Promise((resolve, reject) { if (!this.larkSDKReady) { reject(new Error(Lark SDK not ready)); return; } // ...同上 }); } } };在组件中使用script import LarkSDKMixin from /mixins/LarkSDKMixin; export default { mixins: [LarkSDKMixin], methods: { async handleScan() { try { const res await this.larkScanQRCode(); console.log(res); } catch (err) { console.error(err); } } } }; /script4. 实操过程与核心环节实现从零部署到线上监控现在我们把前面所有理论串成一条完整的、可落地的实操流水线。我会以一个真实的 Vue 3 项目为例展示从飞书开放平台创建应用到前端代码编写再到 Nginx 部署最后到线上问题排查的全过程。每一步都附带截图级细节和避坑提示。4.1 第一步飞书开放平台创建应用手把手登录 飞书开放平台 点击右上角「开发者后台」→「创建应用」。应用名称输入你的项目名如CRM-内部系统。这个名称会显示在飞书客户端的权限申请弹窗里务必清晰易懂。应用类型选择企业自建应用。不要选“第三方应用”后者需要资质审核周期长。应用描述简要说明用途如“用于公司销售团队管理客户信息需调用扫码、定位、分享能力”。应用图标上传一个 120x120 px 的 PNG 图标大小必须严格匹配否则上传失败。创建成功后进入「应用配置」页。这里有两个关键 Tab 基础信息 Tab记下App ID和App Secret。这是你的应用密钥绝不能泄露。我们将它配置到后端环境变量中。Verification Token和Encrypt Key暂时不用那是给飞书机器人用的和 JSSDK 无关。 网站应用 Tab重点这是 JSSDK 的命门所在。网站主页填写你的 Vue 应用首页 URL如https://crm.example.com/。注意末尾的/不能少。可信域名添加你的所有域名包括crm.example.com、staging.crm.example.com、localhost:3000开发用。必须是完整的域名不能带路径不能带协议。填https://crm.example.com是错的会报错。JSAPI 安全域名这是最关键的字段它决定了哪些页面 URL 可以调用 JSSDK。必须填写你的所有前端页面路径的根路径。例如如果你的页面是https://crm.example.com/#/user/123那么这里填https://crm.example.com。如果你用的是 history 模式无#路径是https://crm.example.com/user/123这里依然填https://crm.example.com。这个域名必须和你后端签名接口中传入的url参数的 origin 完全一致。提示飞书对可信域名审核很严。如果你填了*.example.com会被拒绝。必须一个一个添加。我们线上有 5 个环境就填了 5 行。 权限管理 Tab点击「添加权限」搜索并勾选你的应用需要的 JSAPI。常见权限如下jsapi:scanQRCode扫码jsapi:getLocation获取位置jsapi:openChat打开聊天窗口jsapi:share分享到飞书jsapi:getUserInfo获取用户信息jsapi:openDoc打开飞书文档重要勾选后必须点击右下角「保存」按钮否则权限不生效。这个按钮非常隐蔽很多人以为勾选完就完了结果调用时一直报permission denied。4.2 第二步后端签名服务部署Nginx Node.js假设你的后端服务运行在http://backend:3000前端静态资源部署在http://frontend:8080。我们需要用 Nginx 做反向代理让前端能跨域调用签名接口。# /etc/nginx/conf.d/crm.conf upstream backend { server backend:3000; } server { listen 80; server_name crm.example.com; # 静态资源 location / { root /var/www/crm; try_files $uri $uri/ /index.html; } # 环境变量注入 location /static/env.js { alias /var/www/crm/static/env.js; } # 代理签名接口 location /api/lark/sign { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 其他 API 代理... }然后创建env.js文件// /var/www/crm/static/env.js window.__LARK_CONFIG__ { agentId: cli_xxxxxxxxxxxxxx, // 你的 App ID corpId: org_xxxxxxxxxxxxxx, // 你的 Corp ID可在「企业信息」页找到 apiBase: /api };重启 Nginxsudo nginx -s reload。4.3 第三步前端构建与部署Vite 配置Vite 项目需要配置base和build.rollupOptions.external确保lark对象不被打包。// vite.config.js export default defineConfig({ base: ./, // 确保资源路径正确 build: { rollupOptions: { // 告诉 Rolluplark 是外部全局变量不要打包进去 external: [lark], output: { globals: { lark: lark } } } } });构建命令npm run build产物放在dist/目录。将dist/下所有文件复制到 Nginx 的/var/www/crm/目录即可。4.4 第四步线上监控与日志Sentry 集成JSSDK 的问题往往难以复现。我们在线上集成了 Sentry专门捕获 SDK 相关错误。// main.js import * as Sentry from sentry/vue; import { Integrations } from sentry/tracing; Sentry.init({ app, dsn: https://xxxo123456.ingest.sentry.io/123456, integrations: [ new Integrations.BrowserTracing({ routingInstrumentation: Sentry.vueRouterInstrumentation(router), tracingOrigins: [localhost, crm.example.com, /^\//] }) ], tracesSampleRate: 1.0, // 捕获全局未处理的 lark 错误 beforeSend(event, hint) { // 检查是否是 lark 相关错误 if (hint.originalException hint.originalException.toString hint.originalException.toString().includes(lark)) { event.tags { ...event.tags, lark_error: true, lark_version: window.lark?.version || unknown }; } return event; } });在useLarkSDK.js的lark.error回调中主动上报window.lark.error((err) { error.value err; console.error(Lark SDK error:, err); // 主动上报 Sentry Sentry.captureException(err, { tags: { lark_sdk_error: true } }); });这样当线上用户遇到invalid signature或permission denied时我们能在 Sentry 看板中立刻看到错误堆栈、用户设备、飞书客户端版本、发生时间精准定位是配置问题还是代码 Bug。4.5 第

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑