先贴一张企业微信接入场景图,解释下这篇文章的使用场景。

如图所示,本方案是第三方应用开发的demo实现,因为企业内部开发比较简单,不在该篇文章中做过多介绍。

一、准备阶段:

1、申请企业微信号

2、点击banner栏处的服务商平台,开通服务商资格

3、如果涉及到认证,请分别认证企业微信号及服务商开发资格

二、安装介绍

1、应用集成效果图如下,我们要做的就是在第三方这块,安装已上线的应用,当然,这里点击添加第三方应用,只能安装那些已经上架的应用(注意,上线成功不代表上架,企业微信会有一定的安装要求,只有达到了标准的应用才有资格上架,且被客户搜索到,具体可以查看下企业微信的文档),我们的demo中就包括使用连接的形式,让客户安装应用,别急,我们接着往下看

2、随便点开一个第三方应用,我这里打开的是“随心”,你会看到这样的图,注意下我标注出来的①②③,我们会在后面介绍这三个第三方分别是在哪里设置生成的

三、服务商应用介绍

1、进入服务商平台 > 网页应用 > 创建应用

这里如果不是特殊要求,这里按照图片创建即可,敏感信息处慎重选择,否则会影响后面的审核进度

2、下一步

下一步的信息填写,是本文的重点,涉及到应用必须正常运行,所以可以在此处按照格式随便填写,后面再修改(目的是先生成应用的SuiteID和Secret,开发时会使用到)

到此的介绍基本结束。接下来进入代码阶段

四、代码介绍

我大概画了下服务架构图,我们要实现的就是图中红色框部分

重点说明:该方案可实现单应用授权多家,多应用授权区分且经实践可行,并已正式投入生产环境,但因为授权服务器与应用服务器分离,而应用又是前后端分离的项目设计,必然会涉及到跨项目跳转及跨域问题,前期可能会因为配置导致项目启动失败或授权异常,具体问题不在此文档中过多描述,如果有此方面的疑问,可以给我留言

1、代码结构如下图

1.1 SuiteCallback:推送更新token,保证第三方应用的token(suite_access_token)不过期,同步更新预授权码(pre_auth_code),如果是测试安装,在该类中会实现测试权企业分配永久授权码的逻辑

1.2 ProviderAuthorize: 从服务商网站发起授权,安装授权应用,会根据上一步中的预授权码,为授权企业分配永久授权码

1.3 MessagePush:企业微信的消息推送实现

1.4 OAuth2Authorize:用户身份授权,包含我们上面在第二.2中提到的前往服务商后台实现

1.5 JSSDK 分享逻辑的实现

SuiteCallback:

package com.ffxl.hi.controller.qywx;import io.swagger.annotations.Api;import java.io.BufferedReader;import java.io.IOException;import java.util.Date;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.DocumentHelper;import org.dom4j.Element;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import com.ffxl.cloud.model.SQywxApplication;import com.ffxl.cloud.service.SQywxApplicationAuthorizerService;import com.ffxl.cloud.service.SQywxApplicationService;import com.ffxl.hi.controller.BaseUtil;import com.ffxl.hi.controller.qywx.util.EnterpriseAPI;import com.ffxl.hi.controller.qywx.util.EnterpriseConst;import com.ffxl.hi.controller.qywx.util.EnumType;import com.ffxl.hi.controller.qywx.util.aes.AesException;import com.ffxl.hi.controller.qywx.util.aes.WXBizMsgCrypt;import com.ffxl.platform.qywx.model.ApiSuiteTokenRequest;import com.ffxl.platform.util.DateUtil;import com.ffxl.platform.util.StringUtil;/*** 推送更新token* * @author wison*/@Controller@RequestMapping(value = "/qy/suite")@Api(value = "/qy/suite")public class SuiteCallback extends BaseUtil {    private static final Logger logger = LoggerFactory.getLogger(SuiteCallback.class);    @Autowiredprivate SQywxApplicationService sqywxApplicationService;    @Autowiredprivate SQywxApplicationAuthorizerService sqywxApplicationAuthorizerService;    /*** 指令接收 企业微信服务器会定时(每十分钟)推送ticket。ticket会实时变更,并用于后续接口的调用。* * @param request* @param response* @throws IOException* @throws AesException* @throws DocumentException*/@RequestMapping(value = "/directive/receive")    public void acceptAuthorizeEvent(String suiteid, HttpServletRequest request, HttpServletResponse response) throws IOException, AesException,DocumentException {logger.debug("企业微信服务器开始推送suite_ticket---------10分钟一次-----------");logger.debug("获取到的第三方应用suiteid:" + suiteid);processAuthorizeEvent(suiteid, request, response);}    /*** 服务商处理指令回调,解析suite_ticket数据* * @param request* @throws IOException* @throws AesException* @throws DocumentException*/public void processAuthorizeEvent(String suitedid, HttpServletRequest request, HttpServletResponse response) throws IOException,DocumentException, AesException {        // 第三方事件回调WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(EnterpriseConst.STOKEN, EnterpriseConst.SENCODINGAESKEY, suitedid);        // 解析出url上的参数值如下:String nonce = request.getParameter("nonce");String timestamp = request.getParameter("timestamp");String msgSignature = request.getParameter("msg_signature");String echostr = request.getParameter("echostr");logger.debug("-----------------------suitedid:" + suitedid);logger.debug("-----------------------nonce:" + nonce);logger.debug("-----------------------timestamp:" + timestamp);logger.debug("-----------------------msg_signature:" + msgSignature); // 签名串logger.debug("-----------------------echostr:" + echostr);// 随机串String sEchoStr; // url验证时需要返回的明文if (StringUtil.isEmpty(msgSignature)) return;        // 回调if (StringUtil.isEmpty(echostr)) {StringBuilder sb = new StringBuilder();BufferedReader in = request.getReader();String line;            while ((line = in.readLine()) != null) {sb.append(line);}String xml = sb.toString();logger.debug("-----------------------服务商接收到的原始 Xml=" + xml);String exml = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, xml);logger.debug("-----------------------服务商接收到的xml解密后:" + exml);processAuthorizationEvent(request, exml);logger.debug("-----------------------解析成功,返回success");output(response, "success"); // 输出响应的内容。} else {            // 校验,此处的receiveid为企业的corpidWXBizMsgCrypt wxcorp = new WXBizMsgCrypt(EnterpriseConst.STOKEN, EnterpriseConst.SENCODINGAESKEY, EnterpriseConst.SCORPID);sEchoStr = wxcorp.VerifyURL(msgSignature, timestamp, nonce, echostr);logger.debug("-----------------------URL验证成功,返回解析后的的echostr:" + sEchoStr);output(response, sEchoStr); // 输出响应的内容。}}    /*** 对解密后的xml信息进行处理* * @param xml*/void processAuthorizationEvent(HttpServletRequest request, String echoXml) {Document doc;        try {doc = DocumentHelper.parseText(echoXml);Element rootElt = doc.getRootElement();            // 消息类型String infoType = rootElt.elementText("InfoType");String suiteId = rootElt.elementText("SuiteId");// 第三方应用的SuiteIdswitch (EnumType.InfoType.valueOf(infoType)) {            // 授权成功,从企业微信应用市场发起授权时,企业微信后台会推送授权成功通知。// 从第三方服务商网站发起的应用授权流程,由于授权完成时会跳转第三方服务商管理后台,因此不会通过此接口向第三方服务商推送授权成功通知。case create_auth:String authCode = rootElt.elementText("AuthCode");// 授权的auth_code,最长为512字节。用于获取企业的永久授权码。5分钟内有效logger.debug("》》》》》》》》》》授权码AuthCode:" + authCode);                // 换取企业永久授权码EnterpriseAPI.getPermanentCodeAndAccessToken(suiteId, authCode);                break;            // 变更授权,服务商接收到变更通知之后,需自行调用获取企业授权信息进行授权内容变更比对。case change_auth:String changeAuthCorpid = rootElt.elementText("AuthCorpId");// 授权方的corpid// 获取并更新本地授权的企业信息EnterpriseAPI.getAuthInfo(suiteId, changeAuthCorpid);                break;            // 取消授权,当授权方(即授权企业)在企业微信管理端的授权管理中,取消了对应用的授权托管后,企业微信后台会推送取消授权通知。case cancel_auth:                // TODO 删除企业授权信息String cancelAuthCorpid = rootElt.elementText("AuthCorpId");// 授权方的corpidsqywxApplicationAuthorizerService.deleteBySuiteAndCorpId(suiteId, cancelAuthCorpid);                break;            // 企业微信服务器会定时(每十分钟)推送ticket。ticket会实时变更,并用于后续接口的调用。case suite_ticket:String suiteTicket = rootElt.elementText("SuiteTicket");String timeStamp = rootElt.elementText("TimeStamp"); // 时间戳-秒logger.debug("》》》》》》》》》》》》》》》》》》》》》》》》TimeStamp时间戳=========================" + timeStamp);                // 存储ticketlogger.debug("推送SuiteTicket协议-----------suiteTicket = " + suiteTicket);EnterpriseConst suiteConst = new EnterpriseConst("suite");suiteConst.setKey(suiteId);String suiteSecret = suiteConst.getValue();SQywxApplication entity = sqywxApplicationService.getQYWeixinApplication(suiteId, suiteSecret);entity.setSuiteTicket(suiteTicket);logger.debug("》》》》》》》》》》》》》》》》》》》》》》》》TimeStamp时间戳(毫秒)=========================" + Long.parseLong(timeStamp) * 1000);Date date = new Date(Long.parseLong(timeStamp) * 1000);logger.debug("》》》》》》》》》》》》》》》》》》》》》》》》TimeStamp时间戳=========================" + DateUtil.formatStandardDatetime(date));Date ticketDate = DateUtil.parseDate(DateUtil.formatStandardDatetime(date));entity.setTicketTime(ticketDate);                int ret = sqywxApplicationService.updateByPrimaryKeySelective(entity);                // 获取第三方应用凭证,有效期2小时ApiSuiteTokenRequest apiSuiteToken = new ApiSuiteTokenRequest();apiSuiteToken.setSuite_id(suiteId);apiSuiteToken.setSuite_secret(suiteSecret);                // 授权事件接收会每隔10分钟检验一下ticket的有效性,从而保证了此处的ticket是长期有效的apiSuiteToken.setSuite_ticket(suiteTicket);                // 验证token有效性(2小时)String suiteAccessToken = EnterpriseAPI.getSuiteAccessToken(apiSuiteToken);                // 验证预授权码有效性(10分钟)EnterpriseAPI.getPreAuthCode(suiteId, suiteAccessToken);                break;            // 变更通知,根据ChangeType区分消息类型case change_contact:                // TODO 更新令牌等break;            default:                break;}} catch (DocumentException e) {e.printStackTrace();}}
}

ProviderAuthorize:

package com.ffxl.hi.controller.qywx;import io.swagger.annotations.Api;import java.io.IOException;import java.net.URLEncoder;import java.util.HashMap;import java.util.Map;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.dom4j.DocumentException;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.alibaba.fastjson.JSONObject;import com.ffxl.cloud.model.SQywxApplication;import com.ffxl.cloud.service.SQywxApplicationService;import com.ffxl.hi.controller.qywx.util.EnterpriseAPI;import com.ffxl.hi.controller.qywx.util.aes.AesException;import com.ffxl.platform.util.JsonResult;import com.ffxl.platform.util.Message;import com.ffxl.platform.util.StringUtil;/*** 企业授权应用 方式一:从服务商网站发起* * @author wison*/@Controller@RequestMapping(value = "/qy/auth")@Api(value = "/qy/auth")public class ProviderAuthorize {    @Autowiredprivate SQywxApplicationService sqywxApplicationService;    // 授权页网址public static final String INSTALL_URL = "https://open.work.weixin.qq.com/3rdapp/install?suite_id=";    /*** 一键授权功能,主动引入用户进入授权页后,通过用户点击调用此方法* * @param request* @param suiteId*            应用id* @throws IOException* @throws AesException* @throws DocumentException*/@RequestMapping(value = "/goAuthor")    @ResponseBodypublic JsonResult goAuthor(HttpServletRequest request, String suiteId) throws IOException, AesException, DocumentException {        if (StringUtil.isEmpty(suiteId)) {            return new JsonResult(Message.M4003);}String baseUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();String redirectUri = baseUrl + "/qy/auth/authorCallback";String redirectUriEncode = URLEncoder.encode(redirectUri, "UTF-8");        // Map<String,String> stateMap = new HashMap<String, String>();// stateMap.put("suiteId", suiteId);// 查询第三方应用,获取预授权码SQywxApplication application = sqywxApplicationService.queryBySuiteId(suiteId);        if (application == null || StringUtil.isEmpty(application.getPreAuthCode())) {            return new JsonResult(Message.M3001, "suiteId:" + suiteId + "对应的第三方应用尚未初始化,请等待10分钟或联系服务商", null);}        // 获取预授权码,有效期10分钟String preAuthCode = application.getPreAuthCode();String url = INSTALL_URL + suiteId + "&pre_auth_code=" + preAuthCode + "&redirect_uri=" + redirectUriEncode + "&state=" + suiteId;        return new JsonResult(Message.M2000, url);}    /*** 引导授权回调 根据临时授权码(10分钟有效),换取永久授权码* * @param request* @param response* @throws IOException* @throws AesException* @throws DocumentException*/@RequestMapping(value = "/authorCallback")    public void authorCallback(HttpServletRequest request, HttpServletResponse response) throws IOException, AesException, DocumentException {String authCode = request.getParameter("auth_code");String expires_in = request.getParameter("expires_in");String state = request.getParameter("state");        // //解析state// JSONObject js = JSONObject.parseObject(state);// String suiteId = js.getString("suiteId");String suiteId = state;        // 换取永久授权码EnterpriseAPI.getPermanentCodeAndAccessToken(suiteId, authCode);}}

MessagePush:

package com.ffxl.hi.controller.qywx;import io.swagger.annotations.Api;import java.io.BufferedReader;import java.io.IOException;import java.util.Calendar;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.DocumentHelper;import org.dom4j.Element;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import com.ffxl.hi.controller.BaseUtil;import com.ffxl.hi.controller.qywx.util.EnterpriseConst;import com.ffxl.hi.controller.qywx.util.aes.AesException;import com.ffxl.hi.controller.qywx.util.aes.WXBizMsgCrypt;import com.ffxl.platform.util.StringUtil;/*** 企业微信开放了消息发送接口,企业可以使用这些接口让自定义应用与企业微信后台或用户间进行双向通信。* * @author wison*/@Controller@RequestMapping(value = "/qy/message")@Api(value = "/qy/message")public class MessagePush extends BaseUtil {    private static final Logger logger = LoggerFactory.getLogger(MessagePush.class);    /*** 授权公众号的回调地址 处理消息等用户操作时,请务必使用appid进行匹配* * @param appid* @param request* @param response* @throws IOException* @throws AesException* @throws DocumentException*/@RequestMapping(value = "{corpid}/callback")    public void acceptMessageAndEvent(@PathVariable String corpid, HttpServletRequest request, HttpServletResponse response) throws IOException,DocumentException, AesException {String nonce = request.getParameter("nonce");String timestamp = request.getParameter("timestamp");String msgSignature = request.getParameter("msg_signature");String echostr = request.getParameter("echostr");logger.info("-----------------------corpid:" + corpid);logger.info("-----------------------nonce:" + nonce);logger.info("-----------------------timestamp:" + timestamp);logger.info("-----------------------msg_signature:" + msgSignature); // 签名串logger.info("-----------------------echostr:" + echostr);// 随机串String sEchoStr; // url验证时需要返回的明文if (StringUtil.isEmpty(msgSignature)) return;// 微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息if (StringUtil.isEmpty(echostr)) {            // 消息处理StringBuilder sb = new StringBuilder();BufferedReader in = request.getReader();String line;            while ((line = in.readLine()) != null) {sb.append(line);}in.close();String xml = sb.toString();logger.info("接收到的xml信息----------" + xml);checkWeixinAllNetworkCheck(request, response, corpid, xml);} else {            // 校验,此处的receiveid为企业的corpidWXBizMsgCrypt wxcorp = new WXBizMsgCrypt(EnterpriseConst.STOKEN, EnterpriseConst.SENCODINGAESKEY, corpid);sEchoStr = wxcorp.VerifyURL(msgSignature, timestamp, nonce, echostr);logger.info("-----------------------URL验证成功,返回解析后的的echostr:" + sEchoStr);output(response, sEchoStr); // 输出响应的内容。}}    /*** 解密消息* * @param appid* @param request* @param response* @param xml* @throws DocumentException* @throws IOException* @throws AesException*/public void checkWeixinAllNetworkCheck(HttpServletRequest request, HttpServletResponse response, String corpid, String xml)throws DocumentException, IOException {Document doc = DocumentHelper.parseText(xml);Element rootElt = doc.getRootElement();String toUserName = rootElt.elementText("ToUserName"); // 企业微信的CorpID,当为第三方套件回调事件时,CorpID的内容为suiteidString agentID = rootElt.elementText("AgentID"); // 接收的应用id,可在应用的设置页面获取String encrypt = rootElt.elementText("Encrypt"); // 消息结构体加密后的字符串try {WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(EnterpriseConst.STOKEN, EnterpriseConst.SENCODINGAESKEY, corpid);            // 解析出url上的参数值如下:String sVerifyNonce = request.getParameter("nonce");String sVerifyTimeStamp = request.getParameter("timestamp");String sVerifyMsgSig = request.getParameter("msg_signature");            // 检验消息的真实性,并且获取解密后的明文.String sEncryptXml = wxcpt.DecryptMsg(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, xml);parsingMsg(request, response, sEncryptXml);} catch (Exception e) {            // 验证URL失败,错误原因请查看异常e.printStackTrace();}}    /*** 对解密后的xml信息进行处理* * @param xml*/void parsingMsg(HttpServletRequest request, HttpServletResponse response, String sEncryptXml) throws DocumentException, IOException {Document doc = DocumentHelper.parseText(sEncryptXml);Element rootElt = doc.getRootElement();String msgType = rootElt.elementText("MsgType");String toUserName = rootElt.elementText("ToUserName");String fromUserName = rootElt.elementText("FromUserName");logger.info("---消息类型msgType:" + msgType + "-----------------企业微信CorpID:" + toUserName + "-----------------成员UserID:" + fromUserName);        if ("event".equals(msgType)) {String event = rootElt.elementText("Event");logger.info("开始解析事件消息--------,事件类型:" + event);            // replyEventMessage(request, response, event, toUserName, fromUserName);} else if ("text".equals(msgType)) {logger.info("开始解析文本消息--------");String content = rootElt.elementText("Content");processTextMessage(request, response, content, toUserName, fromUserName);}}    /*** 事件消息* * @param request* @param response* @param event* @param toUserName* @param fromUserName* @param appid* @throws DocumentException* @throws IOException*/public void replyEventMessage(HttpServletRequest request, HttpServletResponse response, String event, String toUserName, String fromUserName)throws DocumentException, IOException {        switch (event) {        case "":            break;        default:            break;}String content = event + "from_callback";logger.info("---全网发布接入检测------step.4-------事件回复消息  content=" + content + "   toUserName=" + toUserName + "   fromUserName=" + fromUserName);replyTextMessage(request, response, content, toUserName, fromUserName);}    /*** 文本消息* * @param request* @param response* @param content* @param toUserName* @param fromUserName* @param appid* @throws IOException* @throws DocumentException*/public void processTextMessage(HttpServletRequest request, HttpServletResponse response, String content, String toUserName, String fromUserName)throws IOException, DocumentException {String reContent = content + "from_callback";logger.info("---全网发布接入检测------step.4-------文本回复消息  content=" + content + "   toUserName=" + toUserName + "   fromUserName=" + fromUserName);replyTextMessage(request, response, content, toUserName, fromUserName);}    /*** 回复微信服务器"文本消息"* * @param request* @param response* @param content* @param toUserName* @param fromUserName* @throws DocumentException* @throws IOException*/public void replyTextMessage(HttpServletRequest request, HttpServletResponse response, String content, String toUserName, String fromUserName)throws DocumentException, IOException {Long createTime = Calendar.getInstance().getTimeInMillis() / 1000;StringBuffer sb = new StringBuffer();sb.append("<xml>");sb.append("<ToUserName><![CDATA[" + fromUserName + "]]></ToUserName>");sb.append("<FromUserName><![CDATA[" + toUserName + "]]></FromUserName>");sb.append("<CreateTime>" + createTime + "</CreateTime>");sb.append("<MsgType><![CDATA[text]]></MsgType>");sb.append("<Content><![CDATA[" + content + "]]></Content>");sb.append("</xml>");String replyMsg = sb.toString();String returnvaleue = "";        try {            // 此处的receiveid 随便定义均可通过加密算法,联系企业微信未得到合理解释,暂时按照文档,此处参数使用企业对应的corpidlogger.info("===================>>>企业coidId:" + toUserName);WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(EnterpriseConst.STOKEN, EnterpriseConst.SENCODINGAESKEY, toUserName);returnvaleue = wxcpt.EncryptMsg(replyMsg, createTime.toString(), "easemob");} catch (AesException e) {e.printStackTrace();}output(response, returnvaleue);}}

OAuth2Authorize:

package com.ffxl.hi.controller.qywx;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import java.io.UnsupportedEncodingException;import java.util.List;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.servlet.ModelAndView;import com.ffxl.cloud.model.SQywxApplication;import com.ffxl.cloud.service.SQywxApplicationAuthorizerService;import com.ffxl.cloud.service.SQywxApplicationService;import com.ffxl.hi.controller.qywx.util.EnterpriseAPI;import com.ffxl.hi.controller.qywx.util.EnterpriseConst;import com.ffxl.platform.exception.BusinessException;import com.ffxl.platform.qywx.model.ApiUserDetailResponse;import com.ffxl.platform.qywx.model.ApiUserInfoResponse;import com.ffxl.platform.util.JsonResult;import com.ffxl.platform.util.Message;import com.ffxl.platform.util.StringUtil;/*** 用户在不告知第三方自己的帐号密码情况下,通过授权方式,让第三方服务可以获取自己的资源信息 网页授权登陆 第三方应用oauth2链接* * @author wison*/@Controller@RequestMapping(value = "/qy/oauth2")@Api(value = "/qy/oauth2")public class OAuth2Authorize {    private static final Logger logger = LoggerFactory.getLogger(OAuth2Authorize.class);    @Autowiredprivate SQywxApplicationService sqywxApplicationService;    @Autowiredprivate SQywxApplicationAuthorizerService sqywxApplicationAuthorizerService;    // oauth2地址public static final String OAUTH2_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=";    /*** 企业微信网页授权API* * @param appId*            第三方应用id(即ww或wx开头的suite_id)。注意与企业的网页授权登录不同* @param pageView* @param scope*            应用授权作用域。* @param request* @param response* @return*/@RequestMapping(value = "/getRedirectUrl")    @ResponseBodypublic JsonResult getRedirectUrl(String suiteId, String pageView, String scope, HttpServletRequest request, HttpServletResponse response) {        if (StringUtil.isEmpty(suiteId, pageView, scope)) {            throw new BusinessException(Message.M4003);}EnterpriseConst suiteConst = new EnterpriseConst("suite");suiteConst.setKey(suiteId);String suiteSecret = suiteConst.getValue();        if (StringUtil.isEmpty(suiteSecret)) {            throw new BusinessException(Message.M4004);}        // 第一步:引导用户进入授权页面,根据应用授权作用域,获取codeString basePath = request.getScheme() + "://" + request.getServerName();String backUrl = basePath + "/third/qy/oauth2/redirect/" + suiteId;logger.info("------------------------回调地址:----" + backUrl);        // 微信授权地址String redirectUrl = oAuth2Url(suiteId, backUrl, scope, pageView);logger.info("------------------------授权地址:----" + redirectUrl);        return new JsonResult(true, redirectUrl);}    /*** 构造带员工身份信息的URL* * @param appid*            第三方应用id(即ww或wx开头的suite_id)* @param redirect_uri*            授权后重定向的回调链接地址,请使用urlencode对链接进行处理* @param state*            重定向后会带上state參数,企业能够填写a-zA-Z0-9的參数值* @return*/private String oAuth2Url(String suiteId, String redirect_uri, String scope, String state) {        try {redirect_uri = java.net.URLEncoder.encode(redirect_uri, "utf-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}String oauth2Url = OAUTH2_URL + suiteId + "&redirect_uri=" + redirect_uri + "&response_type=code" + "&scope=" + scope + "&state=" + state+ "#wechat_redirect";System.out.println("oauth2Url=" + oauth2Url);        return oauth2Url;}    /*** 微信回调地址* * @param request* @return*/@RequestMapping(value = "/redirect/{suiteId}")    @ApiOperation(value = "微信回调地址", httpMethod = "GET", hidden = true, notes = "微信回调地址")    public ModelAndView redirectDetail(@PathVariable String suiteId, HttpServletRequest request, HttpServletResponse response) {response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");        if (StringUtil.isEmpty(suiteId)) {            throw new BusinessException(Message.M4003);}ModelAndView mv = new ModelAndView();        try {String code = request.getParameter("code");            // state为跳转路径,微信不识别&符号,顾前端如有参数,请使用@代替,在此处做转换// 页面路径必须在jsp目录下,后缀名可自定义String state = request.getParameter("state");logger.debug("------------------------state:----" + state);state = state.replaceAll("@", "&");logger.debug("------------------------state:----" + state);            // 用户授权SQywxApplication application = sqywxApplicationService.queryBySuiteId(suiteId);            if (application == null) {                throw new BusinessException("suiteId:" + suiteId + "对应的第三方应用尚未初始化,请等待10分钟或联系服务商管理员");}String suiteAccessToken = application.getSuiteAccessToken();logger.info("参数==================================");logger.info("appid:" + suiteId);logger.info("code:" + code);logger.info("suiteAccessToken:" + suiteAccessToken);            // 第一个第三方应用if (EnterpriseConst.SUITE_ID_1.equals(suiteId)) {                // 授权ApiUserInfoResponse userInfo = EnterpriseAPI.getuserinfo3rd(suiteAccessToken, code);                if (userInfo != null && StringUtil.isEmpty(userInfo.getUserId()) && StringUtil.isEmpty(userInfo.getUserTicket())) {mv.addObject("code", "5001");mv.addObject("msg", "成员没有加入企业微信~");mv.setViewName("qywx/error.jsp"); // 跳转等待页面,然后再跳回之前页面return mv;}String corpId = userInfo.getCorpId(); // 授权方的企业idApiUserDetailResponse userDetail3rd = EnterpriseAPI.getUserDetail3rd(suiteAccessToken, userInfo.getUserTicket());                // 验证用户是否咨询师身份boolean isConsole = false;String accessToken = EnterpriseAPI.getCorpAccessToken(suiteAccessToken, suiteId, corpId);ApiUserDetailResponse userDetail = EnterpriseAPI.getUserDepartment(accessToken, userInfo.getUserId());List<Integer> departmentList = userDetail.getDepartmentList();List<String> departmentNameList = userDetail.getDepartmentNameList();                if (departmentList.contains(EnterpriseConst.SUITE_ID_1_DEPARTMENT_CONSOLE)) {isConsole = true;}mv.addObject("error", false);mv.addObject("isConsole", isConsole);mv.addObject("userId", userInfo.getUserId());mv.addObject("corpId", corpId);mv.addObject("departmentName", departmentNameList);mv.addObject("name", userDetail3rd.getName());mv.addObject("avatar", userDetail3rd.getAvatar());mv.addObject("pageView", state);} else if (EnterpriseConst.SUITE_ID_2.equals(suiteId)) {                // 授权ApiUserInfoResponse userInfo = EnterpriseAPI.getuserinfo3rd(suiteAccessToken, code);                if (userInfo != null && StringUtil.isEmpty(userInfo.getUserId()) && StringUtil.isEmpty(userInfo.getUserTicket())) {mv.addObject("code", "5001");mv.addObject("msg", "成员没有加入企业微信~");mv.setViewName("qywx/error.jsp"); // 跳转等待页面,然后再跳回之前页面return mv;}String corpId = userInfo.getCorpId(); // 授权方的企业idApiUserDetailResponse userDetail3rd = EnterpriseAPI.getUserDetail3rd(suiteAccessToken, userInfo.getUserTicket());                // 验证用户是否咨询师身份boolean isConsole = false;String accessToken = EnterpriseAPI.getCorpAccessToken(suiteAccessToken, suiteId, corpId);ApiUserDetailResponse userDetail = EnterpriseAPI.getUserDepartment(accessToken, userInfo.getUserId());List<Integer> departmentList = userDetail.getDepartmentList();List<String> departmentNameList = userDetail.getDepartmentNameList();                if (departmentList.contains(EnterpriseConst.SUITE_ID_2_DEPARTMENT_CONSOLE)) {isConsole = true;}logger.info("返回用户======================" + userDetail);logger.info("返回部门======================" + departmentNameList);mv.addObject("error", false);mv.addObject("isConsole", isConsole);mv.addObject("userId", userInfo.getUserId());mv.addObject("corpId", corpId);mv.addObject("departmentName", departmentNameList);mv.addObject("name", userDetail3rd.getName());mv.addObject("avatar", userDetail3rd.getAvatar());mv.addObject("pageView", state);} else if (EnterpriseConst.SUITE_ID_3.equals(suiteId)) {                // 授权ApiUserInfoResponse userInfo = EnterpriseAPI.getuserinfo3rd(suiteAccessToken, code);                if (userInfo != null && StringUtil.isEmpty(userInfo.getUserId()) && StringUtil.isEmpty(userInfo.getUserTicket())) {mv.addObject("code", "5001");mv.addObject("msg", "成员没有加入企业微信~");mv.setViewName("qywx/error.jsp"); // 未加入企业微信return mv;}String corpId = userInfo.getCorpId(); // 授权方的企业idApiUserDetailResponse userDetail3rd = EnterpriseAPI.getUserDetail3rd(suiteAccessToken, userInfo.getUserTicket());                // 验证用户是否咨询师身份boolean isConsole = false;String accessToken = EnterpriseAPI.getCorpAccessToken(suiteAccessToken, suiteId, corpId);ApiUserDetailResponse userDetail = EnterpriseAPI.getUserDepartment(accessToken, userInfo.getUserId());List<Integer> departmentList = userDetail.getDepartmentList();List<String> departmentNameList = userDetail.getDepartmentNameList();                if (departmentList.contains(EnterpriseConst.SUITE_ID_3_DEPARTMENT_CONSOLE)) {isConsole = true;}logger.info("返回用户======================" + userDetail);logger.info("返回部门======================" + departmentNameList);mv.addObject("error", false);mv.addObject("isConsole", isConsole);mv.addObject("userId", userInfo.getUserId());mv.addObject("corpId", corpId);mv.addObject("departmentName", departmentNameList);mv.addObject("name", userDetail3rd.getName());mv.addObject("avatar", userDetail3rd.getAvatar());mv.addObject("pageView", state);} else {mv.addObject("error", true);}mv.setViewName("qywx/loading.jsp"); // 跳转等待页面,然后再跳回之前页面return mv;} catch (BusinessException e) {mv.addObject("code", "5000");mv.addObject("msg", "出错了,点击返回首页");mv.setViewName("qywx/error.jsp"); // 跳转等待页面,然后再跳回之前页面return mv;}}    /*** 企业微信后台回调地址* * @param request* @return*/@RequestMapping(value = "/admin/redirect")    public ModelAndView adminRedirectDetail(String auth_code, HttpServletRequest request, HttpServletResponse response) {response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");        if (StringUtil.isEmpty(auth_code)) {            throw new BusinessException(Message.M4003);}ModelAndView mv = new ModelAndView();logger.info("参数==================================");logger.info("code:" + auth_code);        try {String providerAccessToken = EnterpriseAPI.getProviderToken(EnterpriseConst.SCORPID, EnterpriseConst.PROVIDERSECRET);ApiUserDetailResponse userDetail = EnterpriseAPI.getLoginInfo(providerAccessToken, auth_code);String url = "http://wxadmin.feifanxinli.com/admin/wechat_user/login?" + "wechatUserId=" + userDetail.getUserId() + "&userName="+ userDetail.getName() + "&headlImg=" + userDetail.getAvatar();mv.addObject("error", false);mv.addObject("url", url);mv.setViewName("qywx/admin/wuxigongdian.jsp"); // 跳转等待页面,然后再跳回之前页面return mv;} catch (Exception e) {mv.addObject("error", true);mv.setViewName("qywx/admin/wuxigongdian.jsp"); // 跳转等待页面,然后再跳回之前页面return mv;}}
}

JSSDK:

package com.ffxl.hi.controller.qywx;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import java.io.IOException;import java.util.HashMap;import java.util.Map;import javax.servlet.http.HttpServletResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.ffxl.cloud.model.SQywxApplication;import com.ffxl.cloud.service.SQywxApplicationService;import com.ffxl.hi.controller.qywx.util.EnterpriseAPI;import com.ffxl.platform.util.JsonResult;import com.ffxl.platform.util.Message;import com.ffxl.platform.util.StringUtil;@Controller@RequestMapping(value = "/qy/jssdk")@Api(value = "/qy/jssdk")public class JSSDK {    private static final Logger logger = LoggerFactory.getLogger(JSSDK.class);    @Autowiredprivate SQywxApplicationService sqywxApplicationService;    /*** 企业微信使用jssdk参数* * @return* @throws IOException*/@RequestMapping(value = "/config/{suiteId}/{authCorpId}")    @ResponseBody@ApiOperation(value = "公众号回调地址", httpMethod = "GET", hidden = true)    public JsonResult jssdkconfig(@PathVariable String suiteId, @PathVariable String authCorpId, String requestUrl, HttpServletResponse response) {        if (StringUtil.isEmpty(suiteId, authCorpId, requestUrl)) {            return new JsonResult(Message.M4003);}        // 根据suiteId查询第三方信息SQywxApplication application = sqywxApplicationService.queryBySuiteId(suiteId);        if (application == null || StringUtil.isEmpty(application.getSuiteAccessToken())) {            return new JsonResult(Message.M3001, "suiteId:" + suiteId + "对应的第三方应用尚未初始化,请等待10分钟或联系服务商管理严", null);}String accessToken = EnterpriseAPI.getCorpAccessToken(application.getSuiteAccessToken(), suiteId, authCorpId);Map<String, String> tickMap = EnterpriseAPI.getJsTicket(accessToken, suiteId, authCorpId);String corpTicket = tickMap.get("corpTicket");String ticket = tickMap.get("ticket");String agentId = tickMap.get("agentId");        // 企业js-sdk签名logger.info("requestUrl:" + requestUrl);logger.info("suiteId:" + suiteId);logger.info("authCorpId:" + authCorpId);logger.info("corpTicket:" + corpTicket);Map<String, Object> corpObjMap = EnterpriseAPI.getWxConfig(requestUrl, suiteId, authCorpId, corpTicket);logger.info("corpSignature:" + corpObjMap.get("signature"));        // js-sdk签名logger.info("requestUrl:" + requestUrl);logger.info("suiteId:" + suiteId);logger.info("authCorpId:" + authCorpId);logger.info("ticket:" + ticket);logger.info("agentId:" + agentId);Map<String, Object> objMap = EnterpriseAPI.getWxConfig(requestUrl, suiteId, authCorpId, ticket);objMap.put("agentId", agentId);        // 查询企业的logger.info("signature:" + objMap.get("signature"));Map<String, Object> restObj = new HashMap<String, Object>();restObj.put("config", corpObjMap); // 企业restObj.put("agentConfig", objMap); // 应用return new JsonResult(true, restObj);}
}

五、项目启动成功后,打包发布到外网,我们继续修改第三步中随便填写的配置信息

应用主页:本项目不涉及,应该填写你的Web项目的index.html页面地址

可信域名:这里要填授权服务器以及前端项目对应的域名

安装完成回调域名:可填写授权服务器的域名

业务设置URL: http://xxx.com/third/qy/oauth2/admin/redirect

数据回调URL: http://xxx.com/third/qy/message/$CORPID$/callback

指令回调URL:http://xxx.com/third/qy/suite/directive/receive?suiteid=xxxxxx  注:该处填写应用的suitedId,方便动态获取

自定义菜单,这里要提前设置,审核后可在通知进入企业微信的消息列表,快速进入应用,如果是审核后设置,无效

源码下载地址:http://www.demodashi.com/demo/15312.html

企业微信服务商集成解决方案相关推荐

  1. 为企业微信“服务商应用”更改微信插件中的消息弹出样式

    一. 背景 1. 企业微信"服务商应用" 企业微信的"自建应用"类目中,可以选择添加完全自建的应用,也可以选择添加服务商提供的代开发应用. 代开发应用是由企微认 ...

  2. H5与企业微信jssdk集成

    H5与企业微信jssdk集成 一.公众号设置 注册企业微信,在应用与小程序栏目中,设置可信域名,配置公众号菜单.可信域名不得不说下,在最初开发时,认为设置并验证后,微信认证接口会实现跨域请求,其实并没 ...

  3. 企业微信三方开发:注册企业微信服务商

    其他链接 初识微信开发 企业微信三方开发:注册企业微信服务商 企业微信三方开发(一):回调验证及重要参数获取 企业微信三方开发(二):获取access_token 企业微信三方开发(三):网页授权登录 ...

  4. 神策发布丨企业微信数字化营销解决方案!

    当当当!神策数据企业微信数字化营销解决方案来袭! 随着企业微信能力的不断延展,越来越多的企业开始选择企业微信来沉淀流量.营销触达以及复购拉新,作为专注数字化经营的大数据分析与营销科技服务提供商,神策数 ...

  5. 企业微信服务商扫码登录

    准备步骤 申请注册企业微信: 企业注册后,需要申请微信服务商 企业微信服务商官网: PS:上述不是本章的主要内容,不做过多详解 进入服务商后台 应用管理–> 登录授权–>设置登录授权发起域 ...

  6. 关于企业微信服务商入门考试v2.0题库

    企业微信服务商入门考试v2.0题库 判断题: 相关文档地址:文档详情 1. 一个成员最多创建多少个团队? (2 分) A. 1 B. 5 C. 10 D. 无上限 2. 关于朋友圈,以下哪些说法是错的 ...

  7. 神策数据丨企业微信数字化营销解决方案

     当当当!神策数据企业微信数字化营销解决方案来袭! 随着企业微信能力的不断延展,越来越多的企业开始选择企业微信来沉淀流量.营销触达以及复购拉新,作为专注数字化经营的大数据分析与营销科技服务提供商,神策 ...

  8. 分享:如何成为企业微信服务商?

    怎么申请成为企业微信服务商, 企业微信拉新项目,企业微信怎么做代理. 自从企业微信成为企业进行管理的一大利器后,企业微信服务商也如雨后春笋般冒出来,为企业提供了很多专业的服务,专业的应用管理功能.大大 ...

  9. odoo与企业微信深度集成

    odoo与企业微信深度集成 基础数据:部门.员工 考勤:考勤管理.排版管理.出勤管理 休假:休假额度.休假申请 审批:审批模板.审批引擎 * 微信扫码登录 1.基础数据:部门.员工 部门 员工 2.考 ...

最新文章

  1. [WinAPI] API 11 [创建目录]
  2. jQuery的jquery-1.10.2.min.map触发404(未找到)
  3. 一条龙奇迹私服WEB系统后门及bug
  4. 异常:java.util.ConcurrentModificationException
  5. C# Settings使用小结
  6. 查看网关物理地址命令
  7. gulp通过http-proxy-middleware开启反向代理,实现跨域
  8. linux运行c程序a. out,无法运行已编译的文件 – bash:./ a.out:权限被拒绝. (我试过chmod)...
  9. 音视频开发(7)---流媒体服务器原理和架构解析
  10. 华为Mate 30 Pro相机要上天了:主摄或达8100万像素
  11. webgis之相关工具
  12. python约瑟夫环_Python语言之如何实现约瑟夫环问题
  13. jdk8銝要onematch_JDK8老特性详解(二)
  14. 基于微信的买菜小程序 毕业设计毕业论文 开题报告和效果图(基于微信小程序毕业设计题目选题课题)
  15. 实验过程分析1——数据集为什么需要按一定比例划分
  16. 【jqprint打印】js两种超简单的打印方法
  17. Filter为什么会在一次请求执行多次doFilter?
  18. Audience Insights被下架后,Facebook广告定位的最佳替代方案
  19. RocketMQ生产者组topic和消费组的关系
  20. Redis常用命令速查

热门文章

  1. 自定义Android视频播放器 - 切换横竖屏
  2. 用 LCD1602 显示的时钟
  3. 新息自适应卡尔曼滤波matlab代码,基于自适应卡尔曼滤波的弱信号跟踪方法与流程...
  4. 亲测有效,一招解决错误:This application failed to start because not Qt platform plugin could be initialized.
  5. 不想将就,所以竭尽所能。
  6. SmartRF04EB修复与修改ID号
  7. 2017-4-15,16
  8. 手机停机照样可以免费无限量上网
  9. p-sum结构解释+代码 二叉区间树
  10. 启动redis出现闪退(已解决)