前言
在日常开发中,许多开发者会混淆微信公众号与企业微信的接口体系。实际上,这两个平台是完全独立的两套系统,不仅接口域名不同,参数名称、鉴权机制也存在显著差异。本文将系统对比两者在 access_token 与 jsapi_ticket 获取上的区别,并给出一份从公众号代码迁移到企业微信的完整改造实践。
一、access_token 接口对比
微信公众号/小程序使用 https://api.weixin.qq.com/cgi-bin/token 获取全局凭据 access_token,而企业微信则使用完全独立的域名和路径。
1.1 核心差异
两者的核心差异集中在域名、请求地址和参数名称三个方面:
| 特性 | 微信公众号 / 小程序 | 企业微信 |
|---|---|---|
| 接口地址 | https://api.weixin.qq.com/cgi-bin/token |
https://qyapi.weixin.qq.com/cgi-bin/gettoken |
| 参数 | appid 和 secret |
corpid 和 corpsecret |
| 主要使用场景 | 公众号、小程序、开放平台 | 企业微信内部应用、第三方应用 |
1.2 请求示例
微信公众号 / 小程序:用于获取调用公众号、小程序相关功能(如消息回复、用户管理、支付等)的全局凭证。
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET
企业微信:用于获取调用企业微信内部应用或第三方应用接口(如通讯录管理、消息发送、打卡数据等)的凭证。
GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=CORPID&corpsecret=CORPSECRET
⚠ 重要提醒:这两个平台是完全独立的体系,微信公众号的 AppId/Secret 无法在企业微信中使用,反之亦然。请务必根据实际开发平台(公众平台 mp.weixin.qq.com 或企业微信管理后台 work.weixin.qq.com)选择对应的接口和参数。
二、jsapi_ticket 接口对比
微信公众号通过 https://api.weixin.qq.com/cgi-bin/ticket/getticket 获取 jsapi_ticket,用于 JS-SDK 签名与分享功能。企业微信的接口地址与此完全不同,域名同样从 api.weixin.qq.com 换成了 qyapi.weixin.qq.com。
2.1 核心区别:两张”票”,各司其职
企业微信的 JS-SDK 使用了两套权限体系,因此存在两种不同的 jsapi_ticket,开发者需要根据业务场景选择使用:
| 特性 | 微信公众号 | 企业微信(企业票) | 企业微信(应用票) |
|---|---|---|---|
| 接口地址 | /cgi-bin/ticket/getticket |
/cgi-bin/get_jsapi_ticket |
/cgi-bin/ticket/get |
| 参数 | access_token |
access_token=ACCESS_TOKEN |
access_token=ACCESS_TOKEN&type=agent_config |
| 用途 | 调用大部分 JS-SDK 功能(分享、图像、音频等) | 调用企业级别的 JS-SDK 接口,通常不依赖特定应用(如选择成员) | 调用特定应用的 JS-SDK 接口(如 agentConfig 配置),用于访问应用私有数据或功能 |
| 使用场景举例 | 所有需要调用微信 JS-SDK 的网页 | 1. wx.config 初始化2. 调用基础接口(如 chooseImage、openEnterpriseChat)3. 第三方应用在为授权企业进行 config 配置时 |
1. wx.agentConfig 初始化2. 调用应用私有接口(如发送应用消息、访问应用可见范围内的通讯录等) |
注:以上企业微信接口的完整域名均为 https://qyapi.weixin.qq.com。
2.2 使用与签名流程
在网页中使用 JS-SDK 的全链路流程如下:
- 服务端获取 access_token:使用
corpid和corpsecret调用GET https://qyapi.weixin.qq.com/cgi-bin/gettoken获取access_token。 - 服务端获取 jsapi_ticket 并生成签名:
- 如需调用企业级别的 JS-SDK 接口,使用
access_token调用GET https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN获取企业jsapi_ticket。 - 如还需调用应用级别的接口(如
agentConfig),则额外调用GET https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=ACCESS_TOKEN&type=agent_config获取应用jsapi_ticket。 - 服务端使用获取到的
jsapi_ticket、nonceStr(随机字符串)、timestamp(时间戳)以及当前页面url按规则拼接后进行 SHA1 签名,得到signature。
- 如需调用企业级别的 JS-SDK 接口,使用
- 前端页面引入并配置:
- 引入企业微信的 JS-SDK 文件。
- 调用
wx.config进行企业级配置,其中signature使用企业 jsapi_ticket 生成的签名。 - 如需使用应用私有能力,在
wx.config的success回调中继续调用wx.agentConfig,其signature需使用应用 jsapi_ticket 生成的签名。
简单总结:
- 接口一(
/get_jsapi_ticket)获取的 ticket → 用于整个企业维度的 JS-SDK 鉴权(wx.config)。 - 接口二(
/ticket/get?type=agent_config)获取的 ticket → 用于特定应用维度的鉴权(wx.agentConfig)。
2.3 常见问题与注意事项
- IP 白名单:获取
jsapi_ticket时会校验 IP,请确保服务器 IP 已加入企业微信应用详情中的”企业可信 IP”列表。 - 时效与缓存:
jsapi_ticket的有效期为 7200 秒,同样需要服务端缓存并定时刷新,避免触发频率限制。 - 签名算法:签名算法与公众号类似,但务必使用企业微信要求的参数顺序和当前页面的完整 URL(不包含
#及其后面部分)。
三、代码改造实践:从公众号迁移到企业微信
以下示例展示如何将一段基于微信公众号的 access_token 与 jsapi_ticket 获取逻辑改造为企业微信版本。改造涉及三个核心部分:API 地址、参数名称(appid→corpid,secret→corpsecret)以及 ticket 类型获取方式(企业微信有两种 ticket)。
3.1 修改配置常量(WxWorkApiUrl)
public static final String API_QY_HOST_URL = "https://qyapi.weixin.qq.com";
// 获取企业微信 access_token(注意参数名不同)
public static final String GET_ACCESS_TOKEN_URL = API_QY_HOST_URL + "/cgi-bin/gettoken?corpid=%s&corpsecret=%s";
// 获取企业级 jsapi_ticket(用于 wx.config)
public static final String GET_JSAPI_TICKET_URL = API_QY_HOST_URL + "/cgi-bin/get_jsapi_ticket?access_token=%s";
// 获取应用级 jsapi_ticket(用于 wx.agentConfig)
public static final String GET_AGENT_TICKET_URL = API_QY_HOST_URL + "/cgi-bin/ticket/get?access_token=%s&type=agent_config";
3.2 修改 getAccessToken 方法
主要变化:参数名从 appid/secret 改为 corpid/corpsecret,API 地址相应改变。
private String getAccessToken(WxWorkProperties.WorkConfig workConfig, int retryTimes) {
// Redis key 建议也修改,避免与公众号缓存冲突
if (redisService.hasKey(Constant.REDIS_WORK_ACCESS_TOKEN_KEY.concat(workConfig.getCorpId()))) {
return (String) redisService.get(Constant.REDIS_WORK_ACCESS_TOKEN_KEY.concat(workConfig.getCorpId()));
}
logger.info("请求企业微信请求参数{}", FastJsonUtils.toJson(workConfig));
String responseText = restTemplate.getForObject(
String.format(WxWorkApiUrl.GET_ACCESS_TOKEN_URL, workConfig.getCorpId(), workConfig.getCorpSecret()),
String.class
);
logger.info("请求企业微信返回{}", responseText);
JSONObject jsonObject = FastJsonUtils.toJSONObject(responseText);
// 企业微信错误码处理(与公众号略有不同)
if (jsonObject.containsKey("errcode") && jsonObject.getInteger("errcode") != 0) {
throw new RuntimeException("获取 access_token 失败:" + jsonObject.getString("errmsg"));
}
String accessToken = jsonObject.getString("access_token");
redisService.set(
Constant.REDIS_WORK_ACCESS_TOKEN_KEY.concat(workConfig.getCorpId()),
accessToken,
jsonObject.getInteger("expires_in") - 200
);
return accessToken;
}
3.3 修改 getJsapiTicket 方法(关键点)
企业微信需根据使用场景获取不同的 ticket,这里拆分为两个方法:
/**
* 获取企业级 jsapi_ticket(用于 wx.config 初始化)
*/
private String getCorpJsapiTicket(String corpId, String accessToken) {
if (redisService.hasKey(Constant.REDIS_WORK_CORP_TICKET_KEY.concat(corpId))) {
return (String) redisService.get(Constant.REDIS_WORK_CORP_TICKET_KEY.concat(corpId));
}
logger.info("请求企业微信企业级 ticket,accessToken:{}", accessToken);
String responseContent = restTemplate.getForObject(
String.format(WxWorkApiUrl.GET_JSAPI_TICKET_URL, accessToken),
String.class
);
logger.info("请求企业微信返回{}", responseContent);
JSONObject jsonObject = FastJsonUtils.toJSONObject(responseContent);
if (jsonObject.getInteger("errcode") != 0) {
throw new RuntimeException("获取企业级 ticket 失败:" + jsonObject.getString("errmsg"));
}
String jsapiTicket = jsonObject.getString("ticket");
redisService.set(
Constant.REDIS_WORK_CORP_TICKET_KEY.concat(corpId),
jsapiTicket,
jsonObject.getInteger("expires_in") - 200
);
return jsapiTicket;
}
/**
* 获取应用级 jsapi_ticket(用于 wx.agentConfig)
*/
private String getAgentJsapiTicket(String corpId, String accessToken) {
if (redisService.hasKey(Constant.REDIS_WORK_AGENT_TICKET_KEY.concat(corpId))) {
return (String) redisService.get(Constant.REDIS_WORK_AGENT_TICKET_KEY.concat(corpId));
}
logger.info("请求企业微信应用级 ticket,accessToken:{}", accessToken);
String responseContent = restTemplate.getForObject(
String.format(WxWorkApiUrl.GET_AGENT_TICKET_URL, accessToken),
String.class
);
logger.info("请求企业微信返回{}", responseContent);
JSONObject jsonObject = FastJsonUtils.toJSONObject(responseContent);
if (jsonObject.getInteger("errcode") != 0) {
throw new RuntimeException("获取应用级 ticket 失败:" + jsonObject.getString("errmsg"));
}
String jsapiTicket = jsonObject.getString("ticket");
redisService.set(
Constant.REDIS_WORK_AGENT_TICKET_KEY.concat(corpId),
jsapiTicket,
jsonObject.getInteger("expires_in") - 200
);
return jsapiTicket;
}
3.4 修改签名生成逻辑(适配双 ticket)
企业微信前端需要两次配置(wx.config 和 wx.agentConfig),因此服务端需生成两个签名:
public WxWorkJsapiSignature getSignatures(String url, String corpId, String agentId) {
// 1. 获取 access_token
String accessToken = getAccessToken(corpId, corpSecret);
// 2. 获取企业级 ticket(用于 wx.config)
String corpTicket = getCorpJsapiTicket(corpId, accessToken);
long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = StringUtils.getRandomStr(16);
// 3. 生成 wx.config 的签名
String corpSignature = SHA1.genWithAmple(
"jsapi_ticket=" + corpTicket,
"noncestr=" + nonceStr,
"timestamp=" + timestamp,
"url=" + url
);
// 4. 获取应用级 ticket(用于 wx.agentConfig)
String agentTicket = getAgentJsapiTicket(corpId, accessToken);
String agentSignature = SHA1.genWithAmple(
"jsapi_ticket=" + agentTicket,
"noncestr=" + nonceStr,
"timestamp=" + timestamp,
"url=" + url
);
// 5. 返回双签名结果
WxWorkJsapiSignature signature = new WxWorkJsapiSignature();
signature.setCorpId(corpId);
signature.setAgentId(agentId); // 企业微信需要传递 agentId
signature.setNonceStr(nonceStr);
signature.setTimestamp(timestamp);
signature.setCorpSignature(corpSignature); // 用于 wx.config
signature.setAgentSignature(agentSignature); // 用于 wx.agentConfig
return signature;
}
3.5 前端 JS-SDK 调用示例
前端收到双签名后,需依次调用 wx.config 与 wx.agentConfig:
wx.config({
beta: true,
debug: true,
appId: '{{corpId}}', // 企业微信的 corpid
timestamp: '{{timestamp}}',
nonceStr: '{{nonceStr}}',
signature: '{{corpSignature}}', // 使用企业级签名
jsApiList: ['chooseImage', 'openEnterpriseChat']
});
wx.ready(function() {
// config 验证成功后,再进行 agentConfig
wx.agentConfig({
corpid: '{{corpId}}',
agentid: '{{agentId}}',
timestamp: '{{timestamp}}',
nonceStr: '{{nonceStr}}',
signature: '{{agentSignature}}', // 使用应用级签名
jsApiList: ['sendChatMessage'],
success: function(res) {
console.log('agentConfig 成功');
}
});
});
四、关键注意事项
- 参数名称变化:企业微信使用
corpid和corpsecret,而非公众号的appid/secret。 - API 地址不同:域名从
api.weixin.qq.com改为qyapi.weixin.qq.com。 - 双 ticket 机制:必须同时实现两种 ticket 的获取,前端才能完整使用 JS-SDK。
- 错误码处理:企业微信成功时返回
errcode=0,公众号则是直接返回数据。 - Redis 隔离:建议使用不同的缓存 Key 前缀,避免与公众号数据冲突。
- IP 白名单:确保服务器 IP 已添加到企业微信应用的”企业可信 IP”中。
欢迎关注公众号
