一切的前提是 要仔细阅读企业微信开发者中心的文档,内容和流程相对较多,但并不复杂。
1. 概念与流程 1.1 概念
1、企业内部应用 由企业内部的开发者自己开发部署,相当于是企业自己的资产,调用接口基本没有任何限制。
2、第三方应用
由SaaS服务商的开发者开发并部署在服务商侧,面向所有企业。需要企业授权使用(先试用后付费),服务商仅可获取企业授权部分的权限,相当于白名单控制。
3、代开发自建应用 由服务商的开发者开发,但部署在企业内部。一般是线下签约采购方式,因此权限几乎与自建应用无异,企业管理员只需要配置不对服务商开放的敏感权限,相当于黑名单控制。
服务商后台地址:企业微信服务商官网
企业管理后台地址:企业微信管理后台
服务商,可以理解为 SAAS 中的运营端。
企业管理端,可以理解为 SAAS 中的租户端。
因此:
1. 既是服务商管理员又是企业管理后台的管理员时,可以在两个后台之间互相切换,自动登录。
2. 服务商创建的第三方应用,企业管理端可以在管理后台【应用管理】的第三方应用点[+]搜索应用添加授权到企业微信工作台中,企业下用户即可快捷使用。
1.2 流程 1.2.1 全局流程 https://doc.weixin.qq.com/flowchart-addon
“接入” 即开发和处理下方 数据流程 内容。
1.2.2 应用配置 参考如下图。
回调配置:
两个 getData 接口,一个是 GET 请求,一个是 POST 请求。
1.2.3 数据流程 参考文档:企业微信应用接入指引 - 接口文档 - 企业微信开发者中心
Start
1.第三方服务商创建应用配置信息:https://open.work.weixin.qq.com/wwopen/developer#/sass/apps/list
2.企业微信服务指令回调:https://developer.work.weixin.qq.com/tutorial/detail/38
3.获取第三方应用凭证:https://developer.work.weixin.qq.com/tutorial/detail/39
4.获取预授权码:https://developer.work.weixin.qq.com/document/path/90601
5.设置授权配置:https://developer.work.weixin.qq.com/document/path/90602
6.拼接安装应用的授权页面:https://developer.work.weixin.qq.com/document/path/90597
7.授权成功拿到临时授权码页面 eg: redirect_uri?auth_code=xxx&expires_in=600&state=xx
8.获取永久授权码和企业信息: https://developer.work.weixin.qq.com/document/path/90603
9.获取企业凭证access_token后调用企业相关api: https://developer.work.weixin.qq.com/document/path/90605
10.拼接应用内用户自动登录授权页面: https://developer.work.weixin.qq.com/document/path/91120
11.授权成功拿到授权码页面 eg: redirect_uri?code=CODE&state=STATE
End
2. 核心文档 第三方应用开发前提:成为企业微信的服务商 - 企业微信开发者中心
第三方应用开发流程:教程 - 企业微信开发者中心
申请成为服务商:企业微信申请成为服务商
2.1 理解第三方应用开发流程和概念 从第三方应用整个项目周期的接入流程来看,主要分成两个阶段:2.1.1 & 2.1.2
2.1.1 应用开发阶段 1. 创建应用,配置基本信息
2. 开发应用,测试应用逻辑
3. 上线应用,提交审核信息
2.1.2 应用推广阶段 1. 服务商自有渠道推广
2. 企业微信应用市场推广
应用申请可搜:企业微信应用市场搜索指引
2.1.3 基本流程 官方流程图参考:
整体为四个阶段:
1)前期应用准备 1. 创建应用
2. 获取 suite_id 与 suite_scret
2)基础环境搭建 1. 获取第三方应用凭证
3)企业授权安装 1. 获取临时授权码
2. 获取永久授权码
4)调用企业接口 1. 获取登陆用户身份
2. 获取企业凭证
参考文档:理解第三方应用开发的流程与概念
2.2 如何创建第三方应用 参考文档:一:如何创建第三方应用
2.3 如何接受企业微信回调 参考文档:二:如何接收企业微信回调
2.4 如何获取第三方应用凭证 参考文档:三:如何获取第三方应用凭证
2.5 如何将应用安装到企业工作台 参考文档:四:如何将应用安装到企业工作台
2.6 如何获取登陆用户信息 参考文档:五:如何获取登录用户信息
2.7 如何向成员发送消息 参考文档:六:如何向成员发送消息
2.8 如何提交上架 参考文档:七:如何提交上线第三方应用
3. 接入实现 - Java3.1 配置 一、在企业微信中开发第三方应用,需要填写一些基础配置
二、这些配置需要登录到企业微信服务商后台进行配置
以上数据配置填写完后,点击“创建应用” 会得到下面3的信息
三、进入服务商后台->应用管理->网页应用:点击“创建应用” 配置包含:
1)基本信息
Secret:点击获取
1)使用配置
应用主页(我们浏览器访问的地址,例如:http://www.baidu.com )
桌面端独立主页(同应用主页)
可信域名(www.baidu.com)
安装完成回调域名(www.baidu.com)
业务设置URL(同应用主页)
2)回调配置
数据回调URL(http://www.baidu.com/test ) 必须验证通过
指令回调URL(http://www.baidu.com/test ) 必须验证通过
Token:点击按钮”随机获取”
EncodingAESKey:点击按钮”随机获取”
四、进入服务商后台->应用管理->通用开发参数 配置包含:
1)ProviderSecret:点击获取
2)系统事件接收URL(同”数据回调”)
3)Token:点击按钮”随机获取”
4)EncodingAESKey:点击按钮”随机获取”
五、进入服务商后台->应用管理->登录授权 登陆授权配置包含:
1)登录授权发起域名(www.baidu.com)
授权完成回调域名(www.baidu.com)
六、以上根据提示信息,把配置信息填写好,就可以创建第三方应用
3.2 api使用测试 api使用
一、获取ticket 服务商后台-应用管理-应用详情-刷新ticket
企业微信每10分钟
会自动刷新一次ticket,调用指令回调配置的接口;也可手动触发。
二、获取第三方应用凭证 作用:调用企业微信(网页授权登录)“获取访问用户身份”时候的入参”获取预授权码 入参,获取企业永久授权码 入参
调用接口及入参如下:
1 2 3 4 5 6 7 8 9 10 11 12 API: https: 入参:{ "suite_id" : "ww4f66fa544a32f920" , "suite_secret" : "vVv8JzaBlEVCTQkHKqmr57EAMs65AILWiI_4ANc25T4" , "suite_ticket" : "Uc8vAAYl6Rvb4-ShZ1i95l4okcan91cxg-eiXlPSX3rU5u7Cpp3O9C1fN5resTWw" } 出参:{ "suite_access_token" : "Uu3Nplvf50qU7mzFSh4sa5G_8-xSub-4NXsgc-3SUpucWjr_Ov84BJ3BukTCpNxSlX8FscrV7HeteHq_xTSt3nVt6sf_CKNgn8nhysZDtjcazgN21Hgd9Ub1K2ceTsxP" , "expires_in" : 7200 }
三、服务商的token 作用:调用企业微信(扫码)“获取访问用户身份”时候的入参
调用接口及入参如下:
1 2 3 4 5 6 7 8 9 10 11 API: https: 入参:{ "corpid" : "ww14438c6c07a317f2" , "provider_secret" : "RH7PehRJX3LIcw4axad_H2T9HSUG1finOBEpnLTVIioBrP-zgZrGsqJ9pHVw5vVj" } 出参:{ "provider_access_token" : "1GXKi47D10Ruu8kdKv1V1cXbNz3i6WjvsCF135XYv5aIN6oJyZ7TNhAYma60gWFihAlxBPwHBNxzsuGclPZ7QbhlUYr6jzkZ3F81xi6K2MJ-rZ4W_ChNzG9fo0mpwbQR" , "expires_in" : 7200 }
四、获取企业永久授权码 作用:获取“授权方(企业)access_token”值
获取企业永久授权码有两种方式:
1、会在授权成功时附加在redirect_uri中跳转回第三方服务商网站,
2、通过授权成功通知回调推送给服务商。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 API: https: SUITE_ACCESS_TOKEN替换成第三方应用凭证,如下 SUITE_ACCESS_TOKEN = Uu3Nplvf50qU7mzFSh4sa5G_8-xSub-4 NXsgc-3 SUpucWjr_Ov84BJ3BukTCpNxSlX8FscrV7HeteHq_xTSt3nVt6sf_CKNgn8nhysZDtjcazgN21Hgd9Ub1K2ceTsxP 入参:{ "auth_code" : "lEPN8e8WMf9wg0lN-Gc_a18mvwab3WXW-523-bxk7YMXPMTY_Pk4A" } 出参:{ "errcode" : 0 , "errmsg" : "ok" , "access_token" : "ScVYefHLkgC4pq0w-iBXRlOdLh9pUk4D8lxGT5ed_seCzqlG9PDz6gVGiB552SQlBCXMd7vaKAo_Fpu_obpZ70fgUYrvcvzt8ZG7a7fHJ1qPg-y7wbJjqSugobMSathNYb0_Eni3nB8hPTK8H5_RyBNn05cQ3yOd-AZIwxYbNejguuJ6FcINILu-slmf1ES8CCbtopkGy2lpmwrqUgimQ" , "expires_in" : 7200 , "permanent_code" : "04RGGCWltNhW_H0KoeT_mdyIKQ52nLqvoU6WV_TCz-c" , "auth_corp_info" : { "corpid" : "wpHXx7EAAAJODtytMO7Xpu7qWNPB-GFw" , "corp_name" : "wpHXx7EAAAJODtytMO7Xpu7qWNPB-GFw" , } , "auth_user_info" : { "userid" : "aa" , } }
五、获取企业凭证 作用:用于“获取用户信息”,“部门信息”,“信息发送”,“应用生成ticker”,“企业生成ticker”接口等入参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 API: https: SUITE_ACCESS_TOKEN 替换成第三方应用凭证,如下 SUITE_ACCESS_TOKEN = Uu3Nplvf50qU7mzFSh4sa5G_8-xSub-4 NXsgc-3 SUpucWjr_Ov84BJ3BukTCpNxSlX8FscrV7HeteHq_xTSt3nVt6sf_CKNgn8nhysZDtjcazgN21Hgd9Ub1K2ceTsxP 入参:{ "auth_corpid" : "wpHXx7EAAAJODtytMO7Xpu7qWNPB-GFw" , "permanent_code" : "04RGGCWltNhW_H0KoeT_mdyIKQ52nLqvoU6WV_TCz-c" } 出参:{ "access_token" : "0nqExjiBxP9XpD1nkWO6AwrUdqvFwNXxNi0lkbu-APYU0TFJkeW6agXPoopqorZyFYTYCf5q3iz0cyWS9sJgsnkpBfZZ94g3gj6d0Bel6C8i6guCnXCS-f0e0CfKea-NIRpH10Jv93T-g6dHMpyX_JVPb2eznMUISBVEbWgFIL6SmnzYSfPVjoz225mDwxOixM3mwMZUiHSt_axCDWIXrA" , "expires_in" : 7200 }
六、以上接口可以参考企业微信第三方应用api https://developer.work.weixin.qq.com/document/path/90600
如下图红色部分
3.3 获取ticket, auth_code
基于springboot项目。
1、构建springboot项目 新建一个模块(module):enterprise-wechat
新建一个子模块(module):wechat
目录结构如下:
结构描述:
common
controller
–> SystemController:控制层,接收请求
entity
service
–> IConfigService:调用企业微信服务层
pom.xml
pom.xml中需要导入commons.codec包
1 2 3 4 5 <dependency > <groupId > commons-codec</groupId > <artifactId > commons-codec</artifactId > <version > 1.9</version > </dependency >
2、方法描述 1)doGetCallback:
① 接收验证请求,用于验证通用开发参数系统事件接收URL、数据回调URL、指令回调URL。
② 企业微信后台录入回调URL点击保存时,微信服务器会立即发送一条GET请求到对应URL,该函数就对URL的signature进行验证。
2)doPostCallback:
① 用于获取 suite_ticket,安装应用时企业微信传递过来的auth_code:指令回调URL 。
② 当刷新ticket传递【SuitID】:指令回调URL 。
③ 当打开应用时传递【CorpID】:数据回调URL 。
3、代码编写 1)企业微信配置类:WeChatConstants
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package com.wechat.common;public class WeChatConstants { public static final Long EXPIRES_IN = 24 * 60 * 60 * 1000L ; public static final String CORP_ID = "wwxxx17f2" ; public static final String PROVIDER_SECRET = "RH7PehRJXxxx-zgZrGsqJ9pHVw5vVj" ; public static final String SUITE_ID = "wwxxxf920" ; public static final String SUITE_SECRET = "vVv8JzaBxxx_4ANc25T4" ; public static final String SUITE_TICKET = "SUITE_TICKET" ; public static final String AUTH_CODE = "AUTH_CODE" ; public static final String SUITE_TOKEN = "suiteToken" ; public static final String ACCESS_TOKEN = "ACCESS_TOKEN" ; public static final String PROVIDER_ACCESS_TOKEN = "PROVIDER_ACCESS_TOKEN" ; public static final String AUTH_CORPID = "AUTH_CORPID" ; public static final String CORP_NAME = "CORPNAME" ; public static final String AGENT_ID = "AGENTID" ; public static final String USER_ID = "userId" ; public static final String TOKENS = "E0sOXx4LqeE5BmDvMTAz3x" ; public static final String ENCODING_AES_KEY = "IESLPSyW4vyBB90jkzfwfYRtcMky6LIOevr4SVefz7I" ; public static final String REDIRECT_URI = "REDIRECT_URI" ; public static final String REDIRECT_URL = "www.baidu.com" ; public static final String APP_ID= "APPID" ; public static final String PERMANENT_CODE = "PERMANENT_CODE" ; }
2)企业微信api:WeChatUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package com.wechat.common;public class WeChatUtils { public final static String THIRD_BUS_WECHAT_SUITE_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token" ; public final static String THIRD_BUS_WECHAT_ACCESS_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token=SUITE_ACCESS_TOKEN" ; public final static String THIRD_BUS_WECHAT_LOGIN = "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=CORPID&redirect_uri=REDIRECT_URI&state=web_login&usertype=member" ; public final static String THIRD_BUS_WECHAT_GET_LOGIN_INFO = "https://qyapi.weixin.qq.com/cgi-bin/service/get_login_info?access_token=PROVIDER_ACCESS_TOKEN" ; public final static String THIRD_BUS_WECHAT_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_privateinfo&state=STATE#wechat_redirect" ; public final static String THIRD_BUS_WECHAT_GET_USER_INFO = "https://qyapi.weixin.qq.com/cgi-bin/service/getuserinfo3rd?suite_access_token=SUITE_TOKEN&code=CODE" ; public final static String THIRD_BUS_WECHAT_GET_USER_DETAIL3RD = "https://qyapi.weixin.qq.com/cgi-bin/service/getuserdetail3rd?suite_access_token=SUITE_ACCESS_TOKEN" ; public final static String THIRD_BUS_WECHAT_DEPART_LIST = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=ACCESS_TOKEN&id=ID" ; public final static String THIRD_BUS_WECHAT_DEPART_USER = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD" ; public final static String THIRD_BUS_WECHAT_DEPART_USER_DETAIL = "https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD" ; public final static String THIRD_BUS_WECHAT_GET_USER = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USERID" ; public final static String THIRD_BUS_WECHAT_GET_PROVIDER_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token" ; public final static String THIRD_BUS_WECHAT_GET_CORP_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token?suite_access_token=SUITE_ACCESS_TOKEN" ; public final static String THIRD_BUS_WECHAT_SEND = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN" ; public final static String THIRD_BUS_GET_JSAPI_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=ACCESS_TOKEN&type=agent_config" ; public final static String THIRD_BUS_GET_JSAPI_TICKET_BUS = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN" ; }
3)controller层:SystemController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 package com.wechat.controller;import com.wechat.service.IConfigService;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.ServletInputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.PrintWriter;@Slf4j @RestController @RequestMapping(value = "system") public class SystemController { @Autowired private IConfigService configService; @ApiOperation(value = "验证通用开发参数及应用回调") @GetMapping(value = "getEchostr") public void doGetCallback (HttpServletRequest request, HttpServletResponse response) throws Exception { String msgSignature = request.getParameter("msg_signature" ); String timestamp = request.getParameter("timestamp" ); String nonce = request.getParameter("nonce" ); String echoStr = request.getParameter("echostr" ); String sEchoStr= "" ; PrintWriter out; log.debug("msgSignature: " + msgSignature+"timestamp=" +timestamp+"nonce=" +nonce+"echoStr=" +echoStr); try { sEchoStr = configService.doGetCallback(msgSignature,timestamp,nonce,echoStr); log.debug("doGetCallback-> echostr: " + sEchoStr); out = response.getWriter(); out.print(sEchoStr); } catch (Exception e) { e.printStackTrace(); } } @ApiOperation(value = "刷新ticket,AuthCode") @PostMapping(value = "getEchostr") public String doPostCallback (HttpServletRequest request) throws Exception { String msgSignature = request.getParameter("msg_signature" ); String timestamp = request.getParameter("timestamp" ); String nonce = request.getParameter("nonce" ); String type = request.getParameter("type" ); String corpId = request.getParameter("corpid" ); ServletInputStream in = request.getInputStream(); String success = configService.doPostCallback(msgSignature, timestamp, nonce, type, corpId, in); return success; } }
4)Service层:IConfigService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.wechat.service;import javax.servlet.ServletInputStream;public interface IConfigService { String doGetCallback (String msgSignature, String timestamp, String nonce, String echoStr) ; String doPostCallback (String msgSignature, String timestamp, String nonce, String type, String corpId, ServletInputStream in) ; }
5)service实现类:ConfigServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 package com.wechat.service.impl;import com.alibaba.druid.support.json.JSONUtils;import com.wechat.common.StringUtils;import com.wechat.common.WeChatConstants;import com.wechat.common.WxUtil;import com.wechat.common.cache.CacheData;import com.wechat.entity.aes.AesException;import com.wechat.entity.aes.WXBizMsgCrypt;import com.wechat.service.IConfigService;import com.wechat.service.IWeChatService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import javax.servlet.ServletInputStream;import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.Map;@Slf4j @Service public class ConfigServiceImpl implements IConfigService { @Autowired private IWeChatService weChatService; @Override public String doGetCallback (String msgSignature, String timestamp, String nonce, String echoStr) { String sEchoStr="" ; try { log.debug(WeChatConstants.TOKENS, WeChatConstants.ENCODING_AES_KEY, WeChatConstants.CORP_ID); WXBizMsgCrypt wxcpt = new WXBizMsgCrypt (WeChatConstants.TOKENS, WeChatConstants.ENCODING_AES_KEY, WeChatConstants.CORP_ID); sEchoStr = wxcpt.VerifyURL(msgSignature, timestamp, nonce, echoStr); } catch (AesException e) { e.printStackTrace(); } return sEchoStr; } @Override public String doPostCallback (String msgSignature, String timestamp, String nonce, String type, String corpId, ServletInputStream in) { String id = "" ; if (!StringUtils.isNull(type) && type.equals("data" )){ id = corpId; log.debug("======corpId===" +id); } else { id = WeChatConstants.SUITE_ID; log.debug("======SuiteId===" + id); } try { WXBizMsgCrypt wxcpt = new WXBizMsgCrypt (WeChatConstants.TOKENS, WeChatConstants.ENCODING_AES_KEY, id); String postData="" ; BufferedReader reader = new BufferedReader (new InputStreamReader (in)); String tempStr = "" ; while (null != (tempStr=reader.readLine())){ postData+=tempStr; } log.debug("====msg_signature====" +msgSignature+"====timestamp===" +timestamp+"====nonce===" +nonce+"====postData===" +postData); String suiteXml = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, postData); log.debug("suiteXml: " + suiteXml); Map suiteMap = WxUtil.parseXml(suiteXml); log.debug("==suiteMap==" + JSONUtils.toJSONString(suiteMap)); if (suiteMap.get("SuiteTicket" ) != null ) { String suiteTicket = (String) suiteMap.get("SuiteTicket" ); CacheData.put(WeChatConstants.SUITE_TICKET, suiteTicket); log.debug("====SuiteTicket=====" + suiteTicket); } else if (suiteMap.get("AuthCode" ) != null ){ String authCode = (String) suiteMap.get("AuthCode" ); log.debug("doPostValid->AuthCode:" + authCode); weChatService.getPermanentCode(authCode); CacheData.put(WeChatConstants.AUTH_CODE, authCode); } } catch (Exception e) { e.printStackTrace(); } return "success" ; } }
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 <?xml version="1.0" encoding="UTF-8" ?> <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 > org.example</groupId > <artifactId > third-wechat</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <groupId > com.wechat</groupId > <artifactId > wechat</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > wechat</name > <description > wechat</description > <properties > <java.version > 1.8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.2.2</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.dom4j</groupId > <artifactId > dom4j</artifactId > <version > 2.0.0</version > </dependency > <dependency > <groupId > commons-codec</groupId > <artifactId > commons-codec</artifactId > <version > 1.9</version > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-lang3</artifactId > <version > 3.10</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.16</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.4</version > </dependency > <dependency > <groupId > io.swagger</groupId > <artifactId > swagger-annotations</artifactId > <version > 1.5.24</version > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > 5.7.5</version > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjrt</artifactId > <version > 1.9.6</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
logback.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <?xml version="1.0" encoding="UTF-8" ?> <configuration scan ="true" scanPeriod ="60 seconds" debug ="false" > <property name ="log.path" value ="logs/wechat" /> <property name ="log.pattern" value ="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" /> <appender name ="console" class ="ch.qos.logback.core.ConsoleAppender" > <encoder > <pattern > ${log.pattern}</pattern > </encoder > </appender > <appender name ="file_info" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/info.log</file > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern > <maxHistory > 60</maxHistory > </rollingPolicy > <encoder > <pattern > ${log.pattern}</pattern > </encoder > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > INFO</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <appender name ="file_error" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${log.path}/error.log</file > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern > <maxHistory > 60</maxHistory > </rollingPolicy > <encoder > <pattern > ${log.pattern}</pattern > </encoder > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > ERROR</level > <onMatch > ACCEPT</onMatch > <onMismatch > DENY</onMismatch > </filter > </appender > <logger name ="com.wechat" level ="debug" /> <logger name ="org.springframework" level ="warn" /> <root level ="debug" > <appender-ref ref ="console" /> </root > <root level ="info" > <appender-ref ref ="file_info" /> <appender-ref ref ="file_error" /> </root > </configuration >
4、验证 以上代码编写完成后,就可以打包到环境上面进行测试验证:
①:echostr验证
返回结果:返回 echostr,并显示已验证
1 2 16 :11 :46.940 [http-nio-9205-exec-7] INFO c.q .w .s .c .SystemController - [doGetValid,94] - doGetCallback->echostr: 577115934236344259 16 :11 :46.969 [http-nio-9205-exec-3] INFO c.q .w .s .c .SystemController - [doGetValid,94] - doGetCallback->echostr: 5267604771365158379
②:刷新Ticket:获取Ticket有两种方式,一是点击按钮 获取,二是企业微信每15分钟 会调用回调接口获取一次
点击“刷新Ticket” 会弹出如下图,然后点击确定
Ticket 有效期为30分钟;建议把Ticket放到数据库或者redis中
③:获取auth_code
安装第三方应用的时候,会获取auth_code
④:安装测试流程
通过企业微信扫码进行安装:
上面就是验证通过,及获取Ticket和auth_code.
5、总结 在第三方应用开发中,主要围绕三种类型的access_token
provider_access_token:服务商的token
suite_access_token:获取第三方应用凭证
access_token:授权方(企业)access_token
通过上面的代码及配置,我们获取到了suiteTicket和auth_code。
接下来我们要通过这些值获取到上面token。
3.4 获取3个token 前言 上一节已获取suite_ticket和auth_code 两个重要参数。
下面获取企业微信第三方应用的三种token 方式。
类型
描述
使用场景
获取服务商凭证provider_access_token
服务商的corpid,服务商的secret,在服务商管理后台可见,获取服务商凭证provider_access_token
用于登录授权等
第三方应用suite_access_token
suite_id(第三方应用ID,以ww或wx开头应用id)、suite_secret(应用secret)、suite_ticket(企业微信后台推送的ticket)来获取 suite_access_token,第三方应用access_token
用于获取第三方应用的预授权码等信息
授权方(企业)access_token
企业安装第三方应用后通过授权方corpid,永久授权码permanent_code获取
用于获取通讯录信息等
获取TOKEN
上面图是获取三种token所需要的参数
1、获取服务商凭证provider_access_token
WeChatThirdTokenController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.wechat.controller;import com.wechat.service.IWeChatThirdTokenService;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j @RestController @RequestMapping(value = "wechatToken") public class WeChatThirdTokenController { @Autowired private IWeChatThirdTokenService weChatThirdTokenService; @ApiOperation(value = "获取第三方应用凭证") @PostMapping(value = "getSuiteToken") public void getSuiteToken () { weChatThirdTokenService.getSuiteToken(); } @ApiOperation(value = "服务商的token") @PostMapping(value = "getProviderToken") public void getProviderToken () { weChatThirdTokenService.getProviderToken(); } @ApiOperation(value = "获取企业凭证") @PostMapping(value = "getCorpToken") public void getCorpToken () { weChatThirdTokenService.getCorpToken(); } }
IWeChatThirdTokenService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.wechat.service;public interface IWeChatThirdTokenService { void getSuiteToken () ; void getProviderToken () ; void getCorpToken () ; }
接口实现类WeChatThirdTokenServiceImpl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 package com.wechat.service.impl;import cn.hutool.http.ContentType;import cn.hutool.http.HttpRequest;import cn.hutool.json.JSONUtil;import com.alibaba.druid.support.json.JSONUtils;import com.wechat.common.StringUtils;import com.wechat.common.WeChatConstants;import com.wechat.common.WeChatUtils;import com.wechat.common.cache.CacheData;import com.wechat.entity.wechat.WeChatProviderAccessToken;import com.wechat.entity.wechat.WeChatReturn;import com.wechat.entity.wechat.WeChatSuiteReturn;import com.wechat.service.IWeChatThirdTokenService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import java.util.HashMap;import java.util.Map;@Slf4j @Service public class WeChatThirdTokenServiceImpl implements IWeChatThirdTokenService { @Override public void getSuiteToken () { String suiteTokenUrl = WeChatUtils.THIRD_BUS_WECHAT_SUITE_TOKEN; String suiteToken = "" ; try { Map<String,Object> map = new HashMap <>(); map.put("suite_id" , WeChatConstants.SUITE_ID); map.put("suite_secret" , WeChatConstants.SUITE_SECRET); map.put("suite_ticket" , CacheData.get(WeChatConstants.SUITE_TICKET)); log.debug("getSuiteToken获取第三方应用凭证url入参:" + JSONUtil.toJsonStr(map)); String body = HttpRequest.post(suiteTokenUrl).body(JSONUtil.toJsonStr(map), ContentType.JSON.getValue()).execute().body(); log.debug("getSuiteToken获取第三方应用凭证出参:" +JSONUtil.toJsonStr(body)); WeChatSuiteReturn weChat = JSONUtil.toBean(body, WeChatSuiteReturn.class); log.debug("getSuiteToken获取第三方应用凭证出参转换成bea:" +JSONUtil.toJsonStr(weChat)); if (weChat.getErrcode() == null || weChat.getErrcode() == 0 ){ suiteToken = weChat.getSuite_access_token(); CacheData.put(WeChatConstants.SUITE_TOKEN, suiteToken); } log.debug("获取suite token成功:" +suiteToken); } catch (Exception e) { log.debug("获取suite token失败errcode:" +suiteToken); throw new RuntimeException (); } } @Override public void getProviderToken () { String providerSecret = WeChatConstants.PROVIDER_SECRET; String corpId = WeChatConstants.CORP_ID; String providerTokenUrl = WeChatUtils.THIRD_BUS_WECHAT_GET_PROVIDER_TOKEN; String providerAccessToken = null ; try { Map<String, Object> map = new HashMap <>(); map.put("corpid" , corpId); map.put("provider_secret" , providerSecret); log.debug("getProviderToken入参:" + JSONUtils.toJSONString(map)); String body = HttpRequest.post(providerTokenUrl).body(JSONUtil.toJsonStr(map), ContentType.JSON.getValue()).execute().body(); log.debug("getProviderToken出参" +body); WeChatProviderAccessToken weChat = JSONUtil.toBean(body, WeChatProviderAccessToken.class); if (weChat.getErrcode() == null || weChat.getErrcode() == 0 ){ providerAccessToken = weChat.getProvider_access_token(); CacheData.put("PROVIDER_ACCESS_TOKEN" ,providerAccessToken); } log.debug("获取providerAccessTokenn成功:" + providerAccessToken); } catch (Exception e) { log.error("获取providerAccessToken失败:" + providerAccessToken); throw new RuntimeException (); } } @Override public void getCorpToken () { log.debug("获取企业凭证getCorpToken==========start============" ); String permanentCode = (String)CacheData.get(WeChatConstants.PERMANENT_CODE); String suiteAccessToken = (String) CacheData.get(WeChatConstants.SUITE_TOKEN); String authCorpId = (String)CacheData.get(WeChatConstants.AUTH_CORPID); String corpTokenUrl = WeChatUtils.THIRD_BUS_WECHAT_GET_CORP_TOKEN; corpTokenUrl = corpTokenUrl.replace("SUITE_ACCESS_TOKEN" , suiteAccessToken); String accessToken = null ; try { Map<String, Object> map = new HashMap <>(); map.put("auth_corpid" , authCorpId); map.put("permanent_code" , permanentCode); log.debug("获取企业凭证 getCorpToken 入参:" +suiteAccessToken+"==map:" +JSONUtils.toJSONString(map)); String body = HttpRequest.post(corpTokenUrl).body(JSONUtil.toJsonStr(map), ContentType.JSON.getValue()).execute().body(); WeChatReturn weChat = JSONUtil.toBean(body, WeChatReturn.class); log.debug("获取企业凭证 getCorpToken 出参转换成bean==" +JSONUtil.toJsonStr(weChat)); accessToken = weChat.getAccess_token(); CacheData.put(WeChatConstants.ACCESS_TOKEN,accessToken); CacheData.put(WeChatConstants.AUTH_CORPID,authCorpId); log.debug("获取accessToken成功:" + accessToken); } catch (Exception e) { log.debug("获取paccessToken失败:" + accessToken); throw new RuntimeException (); } log.debug("获取企业凭证getCorpToken==========end============" ); } }
验证:
getSuiteToken:
getProviderToken:
getCorpToken:
总结
企业微信三方开发的三个重要token已经成功获取!
3.5 实现登录及获取用户信息 前言
企业微信第三方应用登录有两种方式网页授权登录和扫码授权登录 :官网文档详解
登录操作,需要与前端进行合作开发,我这边只有写了后端的开发流程及代码
一、扫码授权登录 1、扫码授权登录需要进入服务商后台->应用管理->登录授权 配置我们发起授权的域名
2、需要用到的参数
3、用户进入登录授权页后,需要确认并同意将自己的企业微信和登录账号信息授权给企业或服务商,完成授权流程
4、授权后回调URI,得到授权码和过期时间,授权流程完成后,会进入回调URI,并在URL参数中返回授权码,跳转地址
5、获取登录用户信息
二、网页授权登录 1、构造网页授权链接
2、所需要用到的参数
3、获取访问用户身份
扫码授权登录和网页授权登录之后,获取访问用户的身份需用用到一个入参code,这个入参code需要前端给出 (点击登录按钮,访问OAuth2网页授权链接获取微信授权code【前端处理】)
三、开始开发 控制层:WeChatThirdController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.wechat.controller;import com.wechat.entity.wechat.WeChatLoginUrl;import com.wechat.entity.wechat.WeChatUserinfo3rd;import com.wechat.entity.wechat.WechatUserInfo;import com.wechat.service.IWeChatService;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j @RestController @RequestMapping(value = "wechat") public class WeChatThirdController { @Autowired private IWeChatService weChatService; @ApiOperation(value = "获取扫码登录地址") @PostMapping(value = "login") public WeChatLoginUrl login () { return weChatService.thirdLoginUrl(); } @ApiOperation(value = "企业微信内登录") @PostMapping(value = "wechatLogin") public WeChatLoginUrl wechatLogin () { return weChatService.wechatLoginUrl(); } @ApiOperation(value = "获取访问用户身份") @PostMapping(value = "getUserInfo") public WechatUserInfo getUserInfo (String code) { return weChatService.getUserInfo(code); } @ApiOperation(value = "获取访问用户身份") @PostMapping(value = "getUserinfo3rd") public WeChatUserinfo3rd getUserinfo3rd (String code) { return weChatService.getUserinfo3rd(code); } }
服务层:IWeChatService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.wechat.service;import com.wechat.entity.wechat.WeChatLoginUrl;import com.wechat.entity.wechat.WeChatUserinfo3rd;import com.wechat.entity.wechat.WechatUserInfo;public interface IWeChatService { void getPermanentCode (String authCode) ; WechatUserInfo getUserInfo (String code) ; WeChatUserinfo3rd getUserinfo3rd (String code) ; WeChatLoginUrl thirdLoginUrl () ; WeChatLoginUrl wechatLoginUrl () ; }
服务实现层impl:WeChatServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 package com.wechat.service.impl;import cn.hutool.http.ContentType;import cn.hutool.http.HttpRequest;import cn.hutool.json.JSONUtil;import com.wechat.common.WeChatConstants;import com.wechat.common.WeChatUtils;import com.wechat.common.cache.CacheData;import com.wechat.entity.wechat.WeChatLoginUrl;import com.wechat.entity.wechat.WeChatPermanentCodeReturn;import com.wechat.entity.wechat.WeChatUserinfo3rd;import com.wechat.entity.wechat.WechatUserInfo;import com.wechat.service.IWeChatService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import java.util.HashMap;import java.util.Map;@Slf4j @Service public class WeChatServiceImpl implements IWeChatService { @Override public WeChatLoginUrl thirdLoginUrl () { WeChatLoginUrl login = new WeChatLoginUrl (); String corpId = WeChatConstants.CORP_ID; String redirectUrl = WeChatConstants.REDIRECT_URL; log.debug("登录地址url:" +redirectUrl+"企业微信corpId->" +corpId); String redirectUri = "" ; try { redirectUri = URLEncoder.encode((redirectUrl), "UTF-8" ); } catch (UnsupportedEncodingException e) { e.printStackTrace(); throw new RuntimeException (e.getMessage()); } String getWechatLogin = WeChatUtils.THIRD_BUS_WECHAT_LOGIN; String loginUrl = getWechatLogin.replace(WeChatConstants.CORP_ID, corpId).replace(WeChatConstants.REDIRECT_URI,redirectUri); login.setLoginUrl(loginUrl); log.debug("重定向后登录地址url:" +login); return login; } @Override public WeChatLoginUrl wechatLoginUrl () { log.debug("wechatLogin->start" ); WeChatLoginUrl login = new WeChatLoginUrl (); String suiteId = WeChatConstants.SUITE_ID; String redirectUrl = WeChatConstants.REDIRECT_URL; log.debug("suiteId:" +suiteId+"==redirectUrl:" +redirectUrl); try { redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8" ); } catch (UnsupportedEncodingException e) { e.printStackTrace(); throw new RuntimeException (e.getMessage()); } String getWechatLogin = WeChatUtils.THIRD_BUS_WECHAT_AUTHORIZE_URL; String loginUrl = getWechatLogin.replace(WeChatConstants.APP_ID, suiteId).replace(WeChatConstants.REDIRECT_URI,redirectUrl); login.setLoginUrl(loginUrl); log.debug("企业微信内登录重定向url:" +loginUrl); return login; } @Override public void getPermanentCode (String authCode) { log.debug("获取企业永久授权码->getPermanentCode->start" ); String suiteToken = (String)CacheData.get("SUITE_TOKEN" ); String accessTokenUrl = WeChatUtils.THIRD_BUS_WECHAT_ACCESS_TOKEN; accessTokenUrl = accessTokenUrl.replace("SUITE_ACCESS_TOKEN" , suiteToken); try { Map<String, Object> map = new HashMap <>(); map.put("auth_code" , authCode); log.debug("获取企业永久授权码accessTokenUrl:" +accessTokenUrl+"->auth_code:" +authCode); String body = HttpRequest.post(accessTokenUrl).body(JSONUtil.toJsonStr(map), ContentType.JSON.getValue()).execute().body(); WeChatPermanentCodeReturn weChat = JSONUtil.toBean(body, WeChatPermanentCodeReturn.class); log.debug("获取企业永久授权码转换成bean->weChat:" +weChat); String accessToken = weChat.getAccess_token(); String corpId = weChat.getAuth_corp_info().getCorpid(); String corpName = weChat.getAuth_corp_info().getCorp_name(); Long agentId = weChat.getAuth_info().getAgent().get(0 ).getAgentid(); String userId = weChat.getAuth_user_info().getUserid(); String permanentCode = weChat.getPermanent_code(); CacheData.put(WeChatConstants.ACCESS_TOKEN, accessToken); CacheData.put(WeChatConstants.AUTH_CORPID, corpId); CacheData.put(WeChatConstants.CORP_NAME, corpName); CacheData.put(WeChatConstants.AGENT_ID, agentId); CacheData.put(WeChatConstants.USER_ID, userId); CacheData.put(WeChatConstants.PERMANENT_CODE, permanentCode); log.debug("获取企业永久授权码->PERMANENT_CODE:" +permanentCode); } catch (Exception e) { log.debug("获取accessToken失败errcode" ); throw new RuntimeException (); } log.debug("获取企业永久授权码->getPermanentCode->end" ); } @Override public WechatUserInfo getUserInfo (String code) { String providerSccessToken = (String) CacheData.get(WeChatConstants. PROVIDER_ACCESS_TOKEN); String getUserInfo = WeChatUtils.THIRD_BUS_WECHAT_GET_LOGIN_INFO; String getUserInfoUrl = getUserInfo.replace(WeChatConstants.PROVIDER_ACCESS_TOKEN, providerSccessToken); log.debug("getUserInfo->获取登录用户信息Url->" +getUserInfoUrl); Map<String, Object> mapCode = new HashMap <>(); mapCode.put("auth_code" , code); String body = HttpRequest.post(getUserInfoUrl).body(JSONUtil.toJsonStr(mapCode), ContentType.JSON.getValue()).execute().body(); WechatUserInfo userInfo = null ; String userId = "" ; try { userInfo = JSONUtil.toBean(body, WechatUserInfo.class); log.debug("getUserInfo->获取用户信息转换成bean:" +JSONUtil.toJsonStr(userInfo)); if (userInfo.getErrcode() == null || userInfo.getErrcode() == 0 ){ userId = userInfo.getUser_info().getUserid(); userInfo.setUserId(userId); } else { throw new RuntimeException (userInfo.getErrmsg()); } log.debug("获取访问用户身份成功" ); } catch (Exception e) { log.debug("获取访问用户身份失败" ); throw new RuntimeException (userInfo.getErrmsg()); } log.debug("getUserInfo->end->userInfo:" +JSONUtil.toJsonStr(userInfo)); return userInfo; } @Override public WeChatUserinfo3rd getUserinfo3rd (String code) { String suiteToken = (String) CacheData.get(WeChatConstants.SUITE_TOKEN); String getUserinfo3rdUrl = WeChatUtils.THIRD_BUS_WECHAT_GET_USER_INFO; getUserinfo3rdUrl = getUserinfo3rdUrl.replace(WeChatConstants.SUITE_TOKEN, suiteToken).replace(WeChatConstants.CODE, code); log.debug("获取访问用户身份url:" +getUserinfo3rdUrl); String body = HttpRequest.get(getUserinfo3rdUrl).execute().body(); WeChatUserinfo3rd userInfo = null ; String userId = "" ; try { userInfo = JSONUtil.toBean(body, WeChatUserinfo3rd.class); log.debug("获取访问用户身份userInfo转换成bean:" +JSONUtil.toJsonStr(userInfo)); if (userInfo.getErrcode() == null || userInfo.getErrcode() == 0 ){ userId = userInfo.getUserId(); userInfo.setUserId(userId); } else { throw new RuntimeException (userInfo.getErrmsg()); } String success = String.format("获取访问用户身份成功" , userId ,userInfo.getErrcode()); log.debug(success); } catch (Exception e) { String error = String.format("获取访问用户身份失败" , userInfo.getErrcode() ,userInfo.getErrmsg()); log.debug(error); throw new RuntimeException (userInfo.getErrmsg()); } log.debug("getUserinfo3rd->end:" +JSONUtil.toJsonStr(userInfo)); return userInfo; } }
实体类:WechatUserInfo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package com.wechat.entity.wechat;import lombok.Data;import lombok.NoArgsConstructor;@NoArgsConstructor @Data public class WechatUserInfo { private Integer errcode; private String errmsg; private String userId; private UserInfo user_info; private CorpInfo corp_info; private Integer status; public static class UserInfo { private String userid; private String open_userid; private String name; public String getUserid () { return userid; } public void setUserid (String userid) { this .userid = userid; } public String getOpen_userid () { return open_userid; } public void setOpen_userid (String open_userid) { this .open_userid = open_userid; } public String getName () { return name; } public void setName (String name) { this .name = name; } } public static class CorpInfo { private String corpid; public String getCorpid () { return corpid; } public void setCorpid (String corpid) { this .corpid = corpid; } } }
实体类:WeChatUserinfo3rd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.wechat.entity.wechat;import lombok.Data;import lombok.NoArgsConstructor;@NoArgsConstructor @Data public class WeChatUserinfo3rd { private Integer errcode; private String errmsg; private String CorpId; private String UserId; private String DeviceId; private String user_ticket; private String open_userid; private Integer status; }
实体类:WeChatLoginUrl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.wechat.entity.wechat;import lombok.Data;import lombok.NoArgsConstructor;@NoArgsConstructor @Data public class WeChatLoginUrl { private String loginUrl; }
验证:
1、扫码方式
用企业微信扫码登录成功后,会回调用户信息接口,如下图:
2、网页方式
总结
以上就是实现登录和获取用户信息的功能!
1、企业微信第三方应用如何配置
2、api接口如何调用
3、如何获取三种token
4、实现两种登录方式
5、获取用户信息
上面五个内容完成了基础的企业微信第三方应用的开发,后面可以根据实际业务去调用企业微信第三方应用的api,方式同上面类似。
GitHub源码地址:https://github.com/18606199546/third-wechat/
Gitee源码地址:https://gitee.com/allenxiao/third-wechat
3.6 接口调用许可应用 前言:企业微信服务商收费模式已于2022年5月16日调整为接口调用许可
此文档是基于接口调用许可应用讲解
企业微信官网描述
一、安装测试 1、首先我们先安装配置企业微信第三方应用
2、开发的时候,需要对第三方应用进行安装测试,根据提示一步一步操作即可安装成功,如下图
安装成功后,会在列表中展示当前你所给某个企业安装的应用信息
3、安装成功后,在“测试企业配置” ,添加刚刚所添加的测试企业
4、以上配置操作完成后,就可以对这个企业进行开发测试
二、购买接口许可 1、购买接口许可
2、购买
3、测试企业开通“基础账号”,“互通账号”是不收费的,只有上线的时候,开通才会收费
4、点击“提交订单”,会得到购买的接口调用许可账号,点击“导出账号”,即可得到“帐号激活码”,入下图
三、激活帐号 1 2 3 4 5 6 7 8 9 10 11 12 API: https: 入参:{ "active_code" : "XXXXXXXX" , "corpid" : "CORPID" , "userid" : "USERID" } 出参:{ "errcode" : "0" , "errmsg" : ok}
provider_access_token :应用服务商的接口调用凭证
active_code :帐号激活码,刚刚导出的Excel里面有
corpid :待绑定激活的成员所属企业corpid,只支持加密的corpid(corpid:在安装的时候已经保存到数据库或者缓存中,可在数据库或者缓存中查找)
userid :待绑定激活的企业成员userid 。只支持加密的userid(userid:在安装的时候已经保存到数据库或者缓存中,可在数据库或者缓存中查找)
postman测试结果:
四、测试登录 账号激活成功后,就可以在企业微信登录第三方应用了,入下图
五、接口调用许可官方收费说明
3.7 权限与白名单 一、应用权限 根据需要配置所需要的权限:
二、白名单配置
附1: 验证第三方应用api接入调用流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 ①刷新ticket凭证: 服务商管理后台应用中【刷新ticket】(手动,线上10 分钟自动) SuiteTicket=====UaL6DLn8BLaeXB66bdggjysABAizgiAxWnQDpne4yJeaY6LnjQqWklSMXZIsr4PW ②获取第三方应用凭证token: /webtenant/wechatToken/getSuiteToken{ "suite_access_token" : "xxx" , "expires_in" : 7200 } ③获取与授权码: curl https: { "errcode" : 0 , "errmsg" : "ok" , "pre_auth_code" : "xxx" , "expires_in" : 3600 } ④设置授权配置:https: 接口: https: 入参是:suite_access_token 和 pre_auth_code 和 应用认证类型auth_type{ "pre_auth_code" : "xxx" , "session_info" : { "auth_type" : 1 } } 返回:{ "errcode" : 0 , "errmsg" : "ok" } ⑤拼接第三方应用安装地址,拿临时授权码: https: 用户(也就是默认的管理员)操作安装应用(注意:不要从企业微信打开,要贴到浏览器中访问!!!) 重定向后拿到 auth_code: https: ⑥临时授权码获取企业永久授权码:https: https: { "auth_code" : "xxx" } 返回值:{ "access_token" : "xxx" , "expires_in" : 7200 , "permanent_code" : "xxx" , "auth_corp_info" : { "corpid" : "xxx" , "corp_name" : "测试企业" , "corp_type" : "verified" , "corp_round_logo_url" : "xxx" , "corp_square_logo_url" : "xxx" , "corp_user_max" : 1000 , "corp_wxqrcode" : "xxx" , "corp_full_name" : "测试企业" , "subject_type" : 1 , "verified_end_time" : 1749375120 , "corp_scale" : "501-1000人" , "corp_industry" : "教育" , "corp_sub_industry" : "培训机构" , "location" : "" } , "auth_info" : { "agent" : [ { "agentid" : xxx, "name" : "测试应用" , "square_logo_url" : "xxx" , "privilege" : { "level" : 1 , "allow_party" : [ 131 ] , "allow_user" : [ "JiangYuan" ] , "allow_tag" : [ ] , "extra_party" : [ ] , "extra_user" : [ ] , "extra_tag" : [ ] } , "auth_mode" : 0 , "is_customized_app" : false } ] } , "auth_user_info" : { "userid" : "JiangYuan" , "name" : "woSRCpDQAAjYP5YMX2RpCcOdCDv9d3hg" , "avatar" : "https://xxx.png" , "open_userid" : "xxx" } , "edition_info" : { "agent" : [ { "agentid" : xxx, "edition_id" : "xxx" , "edition_name" : "基础版" , "app_status" : 1 , "user_limit" : 888 , "expired_time" : 999 , "is_virtual_version" : false } ] } } ⑦获取企业凭证access_token: https: 接口: https: 入参: suite_access_token { "auth_corpid" : "xxx" , "permanent_code" : "xxx" } 返回: { "access_token" : "xxx" , "expires_in" : 7200 } ⑧应用内自动登录的用户授权地址拼接(此时的appid为第三方应用的suiteId): https: 用户同意: https: code: xxx ⑨获取用户信息: 接口文档: https: { "errcode" : 0 , "errmsg" : "ok" , "corpid" : "xxx" , "userid" : "JiangYuan" , "user_ticket" : "xxx" , "expires_in" : 1800 , "parents" : [ ] , "open_userid" : "xxx" } 接口文档: https: { "errcode" : 0 , "errmsg" : "ok" , "corpid" : "xxx" , "userid" : "JiangYuan" , "name" : "JiangYuan" , "department" : [ 131 ] , "gender" : "1" , "avatar" : "xxx" , "qr_code" : "xxx" , "open_userid" : "xxx" }
附2:接口逻辑代码实现 回调中处理业务逻辑。
1 2 3 4 5 6 回调: GET /corpWx/callback/getData POST /corpWx/callback/getData 自动登录: POST /qyWeChat/getUserInfoByCode POST /login (通过企业微信 userid 自动登录返回token)