企业微信 JSSDK 进阶实战:用 Senparc.Weixin 完成 wx.agentConfig 双签名与审批流唤起
发布时间:2026/9/25 15:57:25来源:尧图网络
后端即时通讯金融科技【免费下载链接】WeiXinMPSDK微信全平台 .NET SDK Senparc.Weixin for C#支持 .NET Framework 及 .NET Core、.NET 10.0。已支持微信公众号、小程序、小游戏、微信支付、企业微信/企业号、开放平台、JSSDK、微信周边等全平台。 WeChat SDK for C#.项目地址https://gitcode.com/gh_mirrors/we/WeiXinMPSDK点击查看免费下载wx.agentConfig()是企业微信 JSSDK 中面向特定 JS 接口如自定义审批流thirdPartyOpenPage、剪切板接口等的进阶配置项它建立在常规wx.config()之上且要求使用另一套独立的应用 jsapi_ticket重新计算签名。本文基于 Senparc.Weixin 开源仓库中的企业微信指南与配套示例工程完整讲解如何从appsettings.json中读取多应用配置、如何用JSSDKHelper.GetJsApiUiPackageAsync()分别生成常规与 agentConfig 两套签名参数包以及网页端如何先后执行wx.config()与wx.agentConfig()最终唤起审批流程并深入源码剖析签名算法与 Ticket 容器的生命周期管理。agentConfig 与常规 JSSDK 的关系在开始之前需要先厘清概念wx.agentConfig()用于某些特定的接口如审批流接口、剪切板接口等但一般前提都需要先使用到常规的 JSSDK可参考本项目指南中的 JSSDK常规因此这是一个增加项而非替代项。wx.agentConfig()同样分为服务器端和客户端两部分且有两个关键差异点票据不同常规wx.config()使用普通 jsapi_ticket而wx.agentConfig()必须使用应用 jsapi_ticket即带agentid维度获取的票据。SDK 中通过isAgentConfig布尔参数区分两者ID 不同wx.agentConfig()的agentid参数必须传WeixinCorpAgentId应用 ID如 1000247而不是WeixinCorpId。服务端获取签名信息双票据 双签名包的服务端代码后端除了通过JSSDKHelper.GetJsSdkUiPackageAsync()系列方法获取常规 JSSDK 运行所需参数以外还需要使用同一个方法传入不同参数来获取agentConfig的对应参数。仓库示例工程中的完整实现如下源自 JSSDKController.cs 的AgentConfig()方法public async TaskActionResult AgentConfig() { //此处演示同时支持多个应用的注册请参考 appsettings.json 文件 var workSetting Senparc.Weixin.Config.SenparcWeixinSetting[企业微信审批] as ISenparcWeixinSettingForWork; var url https://sdk.weixin.senparc.com/Work/Approval; //获取 UI 信息包 /* 注意 * 所有应用中jsApiUiPackage 是必备的 */ var jsApiTicket await JsApiTicketContainer.GetTicketAsync(workSetting.WeixinCorpId, workSetting.WeixinCorpSecret, false); var jsApiUiPackage await JSSDKHelper.GetJsApiUiPackageAsync(workSetting.WeixinCorpId, workSetting.WeixinCorpSecret, url, jsApiTicket, false); ViewData[jsApiUiPackage] jsApiUiPackage; /* 注意 * 1、这里需要使用 WeixinCorpAgentId而不是 WeixinCorpId * 2、agentJsApiUiPackage 是否需要提供请参考官方文档此处演示了最复杂的情况 */ ViewData[thirdNo] DateTime.Now.Ticks Guid.NewGuid().ToString(n); ViewData[corpId] workSetting.WeixinCorpId; ViewData[agentId] workSetting.WeixinCorpAgentId; var agentConfigJsApiTicket await JsApiTicketContainer.GetTicketAsync(workSetting.WeixinCorpId, workSetting.WeixinCorpSecret, true); var agentJsApiUiPackage await JSSDKHelper.GetJsApiUiPackageAsync(workSetting.WeixinCorpId, workSetting.WeixinCorpSecret, url, agentConfigJsApiTicket, true); ViewData[agentJsApiUiPackage] agentJsApiUiPackage; return View(); }这段代码中每次调用JsApiTicketContainer.GetTicketAsync()与JSSDKHelper.GetJsApiUiPackageAsync()的最后一个参数isAgentConfig就是区分两套票据体系的开关第一次传false获取普通 jsapi_ticket 并生成jsApiUiPackage用于页面加载时的wx.config()第二次传true获取应用 jsapi_ticket并生成agentJsApiUiPackage用于用户触发时的wx.agentConfig()。另外ViewData[thirdNo] DateTime.Now.Ticks Guid.NewGuid().ToString(n)用于生成审批单的第三方单号thirdNo保证每次发起审批的唯一性。多应用配置appsettings.json 中的 Items 注册上述方法中使用了Senparc.Weixin.Config.SenparcWeixinSetting[企业微信审批]来获取与默认主应用不同的微信配置。这一写法依赖于appsettings.json中SenparcWeixinSetting.Items下的多应用注册参考 appsettings.json 中的实际设置Items: { //添加多个企业微信应用 企业微信审批: { WeixinCorpId: #{WeixinCorpId2}#, WeixinCorpAgentId: #{WeixinCorpAgentId2}#, WeixinCorpSecret: #{WeixinCorpSecret2}#, WeixinCorpToken: #{WeixinCorpToken2}#, WeixinCorpEncodingAESKey: #{WeixinCorpEncodingAESKey2}#} }其中#{...}#是示例工程的占位符写法实际部署时替换为真实的 CorpId / AgentId / Secret / Token / EncodingAESKey 即可。需要注意WeixinCorpAgentId是 agentConfig 场景的核心字段必须与当前网页所属应用一致顶层的WeixinCorpId/WeixinCorpSecret等是默认主应用配置Items下的是附加应用配置二者可并存此按名称索引配置的方法对所有其他模块也通用如公众号、小程序、微信支付等。SDK 签名生成的源码级实现GetJsApiUiPackageAsync一个方法覆盖两种场景从源码看JSSDKHelper.csGetJsApiUiPackageAsync的实现非常精简isAgentConfig只影响向哪套票据取数签名算法本身两者完全相同public static async TaskJsApiUiPackage GetJsApiUiPackageAsync(string appId, string secret, string url, string jsApiTicket, bool isAgentConfig) { var nonceStr GetNoncestr(); var timeStamp GetTimestamp(); jsApiTicket ?? await JsApiTicketContainer.GetTicketAsync(appId, secret, isAgentConfig); var sign GetSignature(jsApiTicket, nonceStr, timeStamp, url); JsApiUiPackage jsApiUiPackage new(appId, timeStamp.ToString(), nonceStr, sign); return jsApiUiPackage; }几个值得注意的细节若调用方已自行获取票据如示例代码中先调GetTicketAsync再传入方法会直接复用票据为null时才按isAgentConfig回源获取因此两套票据的缓存键不同、互不干扰返回的 JsApiUiPackage 实体包含AppId、Timestamp、NonceStr、Signature四个属性正好与前端wx.config/wx.agentConfig的必填参数一一对应。GetSignatureSHA1 签名与 URL 哈希片段剔除签名算法在 JSSDKHelper.cs 中实现与官方JS-SDK使用权限签名算法一致public static string GetSignature(string jsapi_ticket, string noncestr, long timestamp, string url) { StringBuilder sb new StringBuilder(); sb.Append(jsapi_ticket).Append(jsapi_ticket).Append() .Append(noncestr).Append(noncestr).Append() .Append(timestamp).Append(timestamp).Append() .Append(url).Append(url.IndexOf(#) 0 ? url.Substring(0, url.IndexOf(#)) : url); return EncryptHelper.GetSha1(sb.ToString()).ToLower(); }关键约束签名串按jsapi_ticket、noncestr、timestamp、url四个参数按字典序拼接后做 SHA1 并转小写URL 必须不包含#及其后面部分——源码中url.IndexOf(#) 0 ? url.Substring(0, url.IndexOf(#)) : url会自动截断哈希片段。如果你的页面是 SPA 路由带#路径签名 URL 与前端window.location.href不一致是最常见的验签失败原因nonceStr由GetNoncestr()基于Guid.NewGuid()去横杠生成timestamp为 Unix 秒级时间戳二者必须与传给wx.config/wx.agentConfig的值严格一致。JsApiTicketContainer票据自动注册、过期刷新与分布式锁两套票据的获取都经过 JsApiTicketContainer 管理。从GetTicketResultAsync的源码结构看第 306-330 行它解决了生产环境的三个问题缓存键隔离AccessTokenContainer.BuildingKey(appId, appSecret, isAgentConfig)将isAgentConfig编入缓存键因此普通票据与应用票据在同一容器内各自独立缓存、独立过期过期自动刷新若jsApiTicketBag.ExpireTime SystemTime.Now则调用CommonApi.GetTicketAsync重新拉取并按expires_in更新过期时间写回缓存调用方无需关心票据生命周期并发安全刷新动作包裹在Cache.BeginCacheLockAsync分布式锁中且获锁后重新读取并二次检查过期状态double-check避免多实例部署下的惊群刷新。此外RegisterAsync方法第 238-270 行在为某个企业注册时会同时为isAgentConfig true与false两套票据分别建立注册回调并可在提供name时把 CorpId/CorpSecret 同步记录到SenparcWeixinSetting.Items[name]中——这正是前文SenparcWeixinSetting[企业微信审批]索引写法能工作起来的机制之一。GetTicketAsync类方法在票据未注册且传入完整凭证时会通过TryGetTicketAsync自动注册因此示例代码无需手动Register即可直接取票。网页端配置 JSSDK网页端除了进行常规的 JSSDK 配置以外还需要在执行特定的 JsApi 方法之前添加wx.agentConfig()的配置。仓库示例视图 AgentConfig.cshtml 的完整客户端流程如下。第一步加载 SDK 脚本并执行常规 wx.config页面需同时引入企业微信 JS-SDK 相关脚本jweixin-1.2.0.js与jwxwork-1.0.0.js并在页面加载时用服务端下发的jsApiUiPackage执行wx.configscript src//res.wx.qq.com/open/js/jweixin-1.2.0.js/script script src//open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js/scriptwx.config({ beta: true, // 必须这么写否则wx.invoke调用形式的jsapi会有问题 debug: true, appId: jsApiUiPackage.AppId, // 必填企业微信的corpID timestamp: jsApiUiPackage.Timestamp, nonceStr: jsApiUiPackage.NonceStr, signature: jsApiUiPackage.Signature, jsApiList: [thirdPartyOpenPage] // 必填需要使用的JS接口列表 });注意beta: true为必填项注释明确指出否则 wx.invoke 调用形式的 jsapi 会有问题。同时建议保留wx.error回调用于诊断签名过期等验签失败场景SPA 场景可在该回调中重新拉取签名。第二步用户触发前执行 wx.agentConfig 并调用目标接口wx.agentConfig()使用服务端第二套参数包agentJsApiUiPackage成功后立即在success回调中执行wx.invoke(thirdPartyOpenPage, ...)发起审批源自 jssdk-agent-config.md 与示例视图function invoke(){ wx.agentConfig({ corpid: ViewData[corpId], // 必填企业微信的corpid必须与当前登录的企业一致 agentid: ViewData[agentId], // 必填企业微信的应用id e.g. 1000247 timestamp: agentJsApiUiPackage.Timestamp, // 必填生成签名的时间戳 nonceStr: agentJsApiUiPackage.NonceStr, // 必填生成签名的随机串 signature: agentJsApiUiPackage.Signature,// 必填签名见附录-JS-SDK使用权限签名算法 jsApiList: [thirdPartyOpenPage], //必填传入需要使用的接口名称 success: function(res) { // 回调 wx.invoke(thirdPartyOpenPage, { oaType: 10001,// String //templateId: C4NxepvGj51gbkeGXHQgYRArW96WrxRinNfyCxo7N,//SYS templateId:247bcb886d0374a0a1f749c52794ba1a_622421053,// Open thirdNo: ViewData[thirdNo],// String extData: { fieldList: [{ title: 审批类型, type: text, value: 文章审批, }, { title: 预览, type: link, value: https://weixin.senparc.com, }], } }, function(res) { // 输出接口的回调信息 console.log(res); alert(wx.invoke result:JSON.stringify(res)); }); }, fail: function(res) { if(res.errMsg.indexOf(function not exist) -1){ alert(版本过低请升级) } else{ alert(wx.invoke fail:JSON.stringify(res)); } } }); }参数要点corpid必须与当前登录的企业一致agentid传WeixinCorpAgentId示例中由workSetting.WeixinCorpAgentId注入ViewData[agentId]timestamp/nonceStr/signature必须取自应用 jsapi_ticketisAgentConfig: true计算出的agentJsApiUiPackage误用常规票据的签名会验签失败templateId区分系统审批模板SYS如注释掉的C4NxepvG...与自定义审批模板Open带_后缀需与企业后台配置一致extData.fieldList用于给审批表单预填字段文本、链接等fail回调中通过errMsg判断function not exist提示版本过低其余错误原样透出便于排错。HTML 页面进行触发a hrefjavascript:void(0) onclickinvoke()点击唤起审批流程/a易错点小结问题正确做法agentConfig 用了普通 jsapi_ticket 的签名对JsApiTicketContainer.GetTicketAsync(..., true)/GetJsApiUiPackageAsync(..., true)传isAgentConfig: trueagentid误传 CorpId从ISenparcWeixinSettingForWork.WeixinCorpAgentId取值页面带#哈希片段导致验签失败签名 URL 截断#之后部分SDK 的GetSignature已自动处理但传参 URL 需与实际页面一致只做了wx.config直接wx.invoke在wx.config成功之后、调用特定 jsapi 之前先执行wx.agentConfig并在其success回调中发起wx.invoke缺少beta: truewx.config必须显式写beta: true否则wx.invoke形式的 jsapi 异常多应用共用一套配置在SenparcWeixinSetting.Items[应用名]下注册独立凭证再用SenparcWeixinSetting[应用名]索引参考文件索引文件说明docs/zh/guide/work/jssdk-agent-config.md本文主体指南agentConfig 服务端与客户端配置docs/zh/guide/work/jssdk-general.md常规 JSSDKwx.config配置指南agentConfig 的前置基础Samples/Work/Senparc.Weixin.Sample.Work/Controllers/JSSDKController.csIndex()常规与AgentConfig()双签名完整服务端示例Samples/Work/Senparc.Weixin.Sample.Work/Views/JSSDK/AgentConfig.cshtml客户端完整页面wx.config wx.agentConfig 唤起审批流Samples/Work/Senparc.Weixin.Sample.Work/appsettings.jsonItems 多应用注册的真实配置样例src/Senparc.Weixin.Work/Senparc.Weixin.Work/Helpers/JSSDK/JSSDKHelper.csGetJsApiUiPackageAsync、GetSignature签名算法实现src/Senparc.Weixin.Work/Senparc.Weixin.Work/Containers/JsApiTicketContainer.cs双票据容器注册、过期刷新、分布式锁刷新逻辑src/Senparc.Weixin.Work/Senparc.Weixin.Work/Entities/JsApiUiPackage.cs前端四参数包实体定义赞分享后端即时通讯金融科技【免费下载链接】WeiXinMPSDK微信全平台 .NET SDK Senparc.Weixin for C#支持 .NET Framework 及 .NET Core、.NET 10.0。已支持微信公众号、小程序、小游戏、微信支付、企业微信/企业号、开放平台、JSSDK、微信周边等全平台。 WeChat SDK for C#.项目地址https://gitcode.com/gh_mirrors/we/WeiXinMPSDK点击查看免费下载相关推荐Senparc.Weixin 微信支付 V3 JSAPI 公众号网页支付实战从商品页到 prepay_id 签名唤起Senparc.Weixin 微信支付 V3 JSAPI 公众号网页支付实战从商品页到 prepay_id 签名唤起 本文基于当前仓库的 TenPay V3后端即时通讯金融科技Senparc.WeixinWeiXinMPSDK微信支付 V2 JSAPI 支付实战商品页、预支付订单与 WeixinJSBridge 唤起全流程Senparc.WeixinWeiXinMPSDK微信支付 V2 JSAPI 支付实战商品页、预支付订单与 WeixinJSBridge 唤起全流程 本文后端即时通讯金融科技WxJava 企业微信流程审批开发指南提交申请、查询详情与审批流程引擎实战WxJava 企业微信流程审批开发指南提交申请、查询详情与审批流程引擎实战 本篇指南基于 WxJava 仓库中的企业微信 OA 审批模块系统讲解如何通过 S后端即时通讯上一篇Betterfox Firefox 字体渲染优化4 步把网页小字调清晰下一篇散点变模型、两次扫描怎么对齐CloudCompare 上手实操创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网