开发文档地址:https://work.weixin.qq.com/api/doc/90001/90142/90594
企业微信服务商后台:https://open.work.weixin.qq.com/wwopen/developer/#/index


一、前言

1.企业微信于2016年4月上线,是腾讯微信团队打造的以办公沟通工具为主打定位的移动办公平台,它的slogan:让每个企业都有自己的微信。
2.企业微信提供了通讯录管理、应用管理、消息推送、身份验证、移动端SDK、素材、OA数据接口、企业支付、电子发票等API,管理员可以使用这些API,为企业接入更多个性化的办公应用。
3.企业微信也是一个平台,是一个统一的办公入口,可以集成公司内部的系统(OA系统、HR系统、ERP系统、CRM系统等),直接在企业微信手机端就可以接收内部系统的消息和通知。
4.企业微信与微信企业号区别:其实两个产品最大的其别就是微信企业号是基于微信,而企业微信是一个独立app。企业微信,倾向于将工作和生活完全分开,以独立app的形式去使用,更有着丰富的办公应用,如预设打卡、审批、日报、公告等OA应用,如果你对这些应用不满足,还可以通过API接入和第三方应用满足更多个性需求。有一种说法:
微信企业号要合并到企业微信,然后慢慢淡化微信企业号的概念。

第三方应用接口旨在方便企业微信管理员通过简单的操作来使用第三方服务商的云应用。实现该目标的核心的机制是:服务商预先在第三方管理端注册登记应用信息。企业选择使用第三方应用时,通过授权流程来一键安装应用。

脚手架源码下载:下载


二、脚手架预览

2.1 项目结构

2.2 服务商后台自建应用

SuiteID
Secret
Token
EncodingAESKey
可信域名配置
业务设置URL
数据回调UR(应用中发送消息,会回调该url)
指令回调URL(各种事件)


安装完后,在应用管理第三方应用里面就会多出该应用


2.2 消息自动回复


2.4 添加好友自动回复


2.5 名片配置小程序


2.6 拆分链接,用户授权



2.7 js-sdk测试demo


三、关键的模块

3.1 回调配置

在集成企业微信与内部系统时,我们往往需要搭建一个回调服务。回调服务,可以实现:

1.自定义丰富的服务行为。比如,用户向应用发消息时,识别消息关键词,回复不同的消息内容;用户点击应用菜单时,转化为指令,执行自动化任务。
2.可以及时获取到状态变化。比如,通讯录发生变化时,不需要定时去拉取通讯录对比,而是实时地获取到变化的通讯录结点,进行同步。


回调服务需要作出正确的响应才能通过URL验证,具体操作如下:

1.对收到的请求,解析上述的各个参数值(参数值需要做Urldecode处理) 根据已有的token,结合第1步获取的参数timestamp,
2.nonce, echostr重新计算签名,然后与参数msg_signature检查是否一致,确认调用者的合法性。计算方法参考:消息体签名检验
3.解密echostr参数得到消息内容(即msg字段)
4.在1秒内响应GET请求,响应内容为上一步得到的明文消息内容(不能加引号,不能带bom头,不能带换行符)


3.2 应用授权

企业微信的系统管理员可以授权安装第三方应用,安装后企业微信后台会将授权凭证、授权信息等推送给服务商后台。然后我们获取到授权信息,将授权信息保存起来。授权可以有两种发起方式:

1.从服务商网站发起
2.从企业微信应用市场发起

从服务商网站发起

从企业微信应用市场发起


3.3 推送suite_ticket处理

在发生授权、通讯录变更、ticket变化等事件时,企业微信服务器会向应用的“指令回调URL”推送相应的事件消息。消息结构体将使用创建应用时的EncodingAESKey进行加密。服务商在收到推送后都必须直接返回字符串 “success”,若返回值不是 “success”,企业微信会把返回内容当作错误信息。

企业微信服务器会定时(每十分钟)推送ticket。ticket会实时变更,并用于后续接口的调用。

    <xml><SuiteId><![CDATA[ww4asffe99e54c0fxxxx]]></SuiteId><InfoType> <![CDATA[suite_ticket]]></InfoType><TimeStamp>1403610513</TimeStamp><SuiteTicket><![CDATA[asdfasfdasdfasdf]]></SuiteTicket></xml>

3.4 网页授权登录

企业微信提供了OAuth的授权登录方式,可以让从企业微信终端打开的网页获取成员的身份信息,从而免去登录的环节。

构造网页授权链接
获取访问用户身份
获取访问用户敏感信息


3.5 Js-Sdk配置

业微信JS-SDK是企业微信面向网页开发者提供的基于企业微信内的网页开发工具包。通过使用企业微信JS-SDK,网页开发者可借助企业微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用企业微信分享、扫一扫等企业微信特有的能力,为企业微信用户提供更优质的网页体验。

**** 所有的JS接口只能在企业微信应用的可信域名下调用(包括子域名),且可信域名必须有ICP备案且在管理端验证域名归属。验证域名归属的方法在企业微信的管理后台“我的应用”里,进入应用,设置应用可信域名。

JS-SDK使用权限签名算法:

获取企业的jsapi_ticket
签名算法
获取应用的jsapi_ticket

***生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是H5应用调用企业微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限(一小时内,一个企业最多可获取400次,且单个应用不能超过100次),频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket。


四、怎么让脚手架快速跑起来

4.1 修改配置 application.xml
logging:level:org.springframework.web: infocom.lxh.cp: debugcn.binarywang.wx.cp: debugserver:port: 80servlet:context-path: /wx:cptp:corpId: ww4007bc86bexxxxsuiteId: ww141f5c1b0bfexxxxsecret: NpR7i2nC_lAdr-nhm4ChxxxxxxxxxxxxxaesKey: kYkZ8YiM7Xze6HbyHTzmmOs1xxxxxxxxxxxxxx

4.2 启动服务


4.3 添加redis缓存支持

自己扩展功能,在tp包下面:

package com.lxh.cptp.tp;import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;/*** created by lanxinghua@2dfire.com on 2020/2/25* 企业微信第三方,缓存支持* 1.suiteAccessToken*/
public class WxCpTpRedisConfigImpl extends WxCpTpDefaultConfigImpl {// suite_ticket与suite_secret配套使用,用于获取suite_access_token 10minprivate static String SUITE_TICKET_KEY = "wx:cptp:suite_ticket";// 第三方应用凭证 2h (key按应用区分)private static String SUITE_ACCESS_TOKEN_KEY = "wx:cptp:suite_access_token:";// H5应用调用企业微信JS接口的临时票据 2h (跟着商家企业Id走)private static final String JS_API_TICKET_KEY = "wx:cptp:jsapi_ticket:";/*** 使用连接池保证线程安全.*/private final JedisPool jedisPool;@Overridepublic void setSuiteId(String corpId) {SUITE_ACCESS_TOKEN_KEY += corpId;super.setSuiteId(corpId);}public WxCpTpRedisConfigImpl(JedisPool jedisPool) {this.jedisPool = jedisPool;}/**-------------------------- suite ticket -------------------------**/@Overridepublic String getSuiteTicket() {if (isSuiteTicketExpired()){String result = "{\"errcode\":40085, \"errmsg\":\"invaild suite ticket\"}";System.err.println(result);return null;}try (Jedis jedis = this.jedisPool.getResource()) {return jedis.get(SUITE_TICKET_KEY);}}@Overridepublic boolean isSuiteTicketExpired() {try (Jedis jedis = this.jedisPool.getResource()) {return jedis.ttl(SUITE_TICKET_KEY) < 2;}}@Overridepublic synchronized void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {System.out.println("redis缓存更新成功:" + suiteTicket);try (Jedis jedis = this.jedisPool.getResource()) {jedis.setex(SUITE_TICKET_KEY, expiresInSeconds - 200, suiteTicket);}}@Overridepublic void expireSuiteTicket() {try (Jedis jedis = this.jedisPool.getResource()) {jedis.expire(SUITE_TICKET_KEY, 0);}}/**-------------------------- suite access token -------------------------**/@Overridepublic String getSuiteAccessToken() {if (isSuiteAccessTokenExpired()){return null;}try (Jedis jedis = this.jedisPool.getResource()) {return jedis.get(SUITE_ACCESS_TOKEN_KEY);}}@Overridepublic boolean isSuiteAccessTokenExpired() {try (Jedis jedis = this.jedisPool.getResource()) {return jedis.ttl(SUITE_ACCESS_TOKEN_KEY) < 2;}}@Overridepublic synchronized void updateSuiteAccessToken(WxAccessToken suiteAccessToken) {this.updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn());}@Overridepublic synchronized void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {try (Jedis jedis = this.jedisPool.getResource()) {jedis.setex(SUITE_ACCESS_TOKEN_KEY, expiresInSeconds - 200, suiteAccessToken);}}@Overridepublic void expireSuiteAccessToken() {try (Jedis jedis = this.jedisPool.getResource()) {jedis.expire(SUITE_ACCESS_TOKEN_KEY, 0);}}/**-------------------------- jsapi ticket -------------------------**/public String getJsApiTicket(String authCorpId) {System.out.println("缓存中获取jsApiTicket");if (isJsApiTicketExpired(authCorpId)){return null;}try (Jedis jedis = this.jedisPool.getResource()) {return jedis.get(JS_API_TICKET_KEY + authCorpId);}}public boolean isJsApiTicketExpired(String authCorpId) {try (Jedis jedis = this.jedisPool.getResource()) {return jedis.ttl(JS_API_TICKET_KEY + authCorpId) < 2;}}// authCorpId 使用第三方企业对应的idpublic synchronized void updateJsApiTicket(String jsApiTicket, int expiresInSeconds, String authCorpId) {try (Jedis jedis = this.jedisPool.getResource()) {jedis.setex(JS_API_TICKET_KEY + authCorpId, expiresInSeconds - 200, jsApiTicket);}}public void expireJsApiTicket(String authCorpId) {try (Jedis jedis = this.jedisPool.getResource()) {jedis.expire(JS_API_TICKET_KEY + authCorpId, 0);}}/*** This method will be destroy jedis pool*/public void destroy() {this.jedisPool.destroy();}
}


五、核心代码

5.1 WxCpTpConfiguration.java
package com.lxh.cptp.config;import com.google.common.collect.Maps;
import com.lxh.cptp.handler.*;
import com.lxh.cptp.tp.WxCpTpRedisConfigImpl;
import me.chanjar.weixin.cp.api.WxCpTpService;
import me.chanjar.weixin.cp.api.impl.WxCpTpServiceImpl;
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;import javax.annotation.PostConstruct;
import java.util.Map;/*** created by lanxinghua@2dfire.com on 2020/2/23* 支持一个第三方*/
@Configuration
@EnableConfigurationProperties(WxCpTpProperties.class)
public class WxCpTpConfiguration {private static final Logger logger = LoggerFactory.getLogger(WxCpTpConfiguration.class);public static String SUIT_ID = "ww141f5c1b0bfe0bc2";private static Map<String/*suitId*/, Map<String/*key*/, AbstractHandler>> routers = Maps.newHashMap();private static Map<String/*suitId*/, WxCpTpService> cpTpServices = Maps.newHashMap();@Autowiredprivate WxCpTpProperties properties;// 更新2020.2.25 加入redis@Autowiredprivate JedisPool jedisPool;public static WxCpTpService getCpTpService(String suitId) {return cpTpServices.get(suitId);}public static Map<String, AbstractHandler> getHandlerMap(){return routers.get(SUIT_ID);}public static AbstractHandler getHandler(String key){return routers.get(SUIT_ID).get(key);}/*** 初始化服务*/@PostConstructpublic void initServices() {WxCpTpService tpService = new WxCpTpServiceImpl();WxCpTpDefaultConfigImpl configStorage = new WxCpTpDefaultConfigImpl();// 加入redis缓存if (redisIsOk()){configStorage = new WxCpTpRedisConfigImpl(jedisPool);}configStorage.setSuiteId(properties.getSuiteId());configStorage.setAesKey(properties.getAesKey());configStorage.setToken(properties.getToken());configStorage.setSuiteSecret(properties.getSecret());configStorage.setCorpId(properties.getCorpId());tpService.setWxCpTpConfigStorage(configStorage);cpTpServices.put(properties.getSuiteId(), tpService);routers.put(properties.getSuiteId(), initHandler());}/**** @return*/private Map<String, AbstractHandler> initHandler(){Map<String, AbstractHandler> handlerMap = Maps.newHashMap();handlerMap.put("log", new LogHandler());handlerMap.put("msg", new MsgHandler());return handlerMap;}/*** redis是否可用* @return*/private boolean redisIsOk(){try {Jedis jedis = jedisPool.getResource();jedis.ping();return true;}catch (Exception e){e.printStackTrace();return false;}}
}

5.2 各种事件Handler


5.3 构建器


5.4 WxCpTpController.java(关键)
package com.lxh.cptp.controller;import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Pair;
import cn.hutool.json.JSONUtil;
import com.lxh.cptp.config.WxCpTpConfiguration;
import com.lxh.cptp.constant.ChangeTypeEnum;
import com.lxh.cptp.constant.InfoTypeEnum;
import com.lxh.cptp.constant.WxCpTpInnerConstant;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.cp.api.WxCpTpService;
import me.chanjar.weixin.cp.bean.WxCpTpCorp;
import me.chanjar.weixin.cp.bean.WxCpTpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpTpXmlPackage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.util.crypto.WxCpTpCryptUtil;
import me.chanjar.weixin.cp.util.xml.XStreamTransformer;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.json.XML;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;/*** created by lanxinghua@2dfire.com on 2020/2/14* 企业微信第三方应用开发*/
@RestController
public class WxCpTpController extends BaseController{private final Logger logger = LoggerFactory.getLogger(this.getClass());private String SUIT_ID = "ww1410bfe0bc2";private WxCpTpService tpService;/*** 数据回调url,应用中发送消息接收* @param signature* @param timestamp* @param nonce* @param echostr* @return*/@RequestMapping("/callback/data")public String data(@RequestParam(name = "msg_signature", required = false) String signature,@RequestParam(name = "timestamp", required = false) String timestamp,@RequestParam(name = "nonce", required = false) String nonce,@RequestParam(name = "echostr", required = false) String echostr,HttpServletRequest request) {logger.info("数据回调");logger.info("signature = [{}], timestamp = [{}], nonce = [{}], echostr = [{}]", signature, timestamp, nonce, echostr);tpService = WxCpTpConfiguration.getCpTpService(SUIT_ID);if (StringUtils.isEmpty(echostr)){try {BufferedReader reader = request.getReader();StringBuffer buffer = new StringBuffer();String line = " ";while (null != (line = reader.readLine())) {buffer.append(line);}String postData = buffer.toString();String decryptMsgs = new WxCpTpCryptUtil(tpService.getWxCpTpConfigStorage()).decrypt(signature, timestamp, nonce, postData);System.out.println("数据回调-解密后的xml数据:" + decryptMsgs);WxCpTpXmlPackage tpXmlPackage = WxCpTpXmlPackage.fromXml(decryptMsgs);System.out.println(JSONUtil.toJsonStr(tpXmlPackage));// 消息回复WxCpXmlOutMessage outMessage = this.route(SUIT_ID, tpXmlPackage);String plainXml = XStreamTransformer.toXml((Class) outMessage.getClass(), outMessage);logger.info("\n组装回复信息:{}", plainXml);WxCpTpCryptUtil pc = new WxCpTpCryptUtil(tpService.getWxCpTpConfigStorage());return pc.encrypt(plainXml);}catch (Exception e){logger.error("校验失败:" + e.getMessage());return "success";}}try {if (tpService.checkSignature(signature, timestamp, nonce, echostr)) {return new WxCpTpCryptUtil(tpService.getWxCpTpConfigStorage()).decrypt(echostr);}} catch (Exception e) {logger.error("校验签名失败:" + e.getMessage());}return "success";}private WxCpXmlOutMessage route(String suitId, WxCpTpXmlPackage message) {// 日志处理WxCpTpConfiguration.getHandler("log").handle(message, null);// 消息处理return WxCpTpConfiguration.getHandler("msg").handle(message, null);}/*** 指令回调url  通讯录,部门变更,  授权变更,  ticket数据* @param signature* @param timestamp* @param nonce* @param echostr* @param request* @param response* @return* @throws IOException*/@RequestMapping(value = "/suite/receive")public String suite(@RequestParam("msg_signature") String signature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestParam(value = "echostr", required = false) String echostr,HttpServletRequest request,HttpServletResponse response) throws Exception {logger.info("指令回调URL-调用我了");logger.info("signature = [{}], timestamp = [{}], nonce = [{}], echostr = [{}]", signature, timestamp, nonce, echostr);tpService = WxCpTpConfiguration.getCpTpService(SUIT_ID);// 不为空为回调配置请求if (null != echostr) {try {if (tpService.checkSignature(signature, timestamp, nonce, echostr)) {return new WxCpTpCryptUtil(tpService.getWxCpTpConfigStorage()).decrypt(echostr);}} catch (Exception e) {e.printStackTrace();}return null;}BufferedReader reader = request.getReader();StringBuffer buffer = new StringBuffer();String line = " ";while (null != (line = reader.readLine())) {buffer.append(line);}String postData = buffer.toString();String decryptMsgs = null;try {decryptMsgs = new WxCpTpCryptUtil(tpService.getWxCpTpConfigStorage()).decrypt(signature, timestamp, nonce, postData);System.out.println("解密后的xml数据:" + decryptMsgs);}catch (Exception e){logger.error("校验失败:" + e.getMessage());return "success";}WxCpTpXmlMessage wxCpTpXmlMessage = WxCpTpXmlMessage.fromXml(decryptMsgs);InfoTypeEnum infoTypeEnum = InfoTypeEnum.getByInstance(wxCpTpXmlMessage.getInfoType());if (infoTypeEnum == null){throw new Exception("不支持该类型操作");}Map<String, Object> jsonObject = wxCpTpXmlMessage.getAllFieldsMap();switch (infoTypeEnum) {//(每十分钟)推送ticket。ticket会实时变更,并用于后续接口的调用。case SUITE_TICKET: {String suitId = wxCpTpXmlMessage.getSuiteId();String suiteTicket = wxCpTpXmlMessage.getSuiteTicket();Integer timeStamp = Convert.toInt(wxCpTpXmlMessage.getTimeStamp());logger.info("推送ticket成功:" + jsonObject.toString());WxCpTpConfigStorage tpConfig = tpService.getWxCpTpConfigStorage();tpConfig.updateSuiteTicket(suiteTicket, 20*60);logger.info("suit ticket缓存更新成功");String suitAccessToken = tpService.getSuiteAccessToken();logger.info("suitAccessToken:" + suitAccessToken);break;}// 企业微信应用市场发起授权时,企业微信后台会推送授权成功通知case CREATE_AUTH:{logger.info("创建授权:" + jsonObject.toString());String suiteId = wxCpTpXmlMessage.getSuiteId();String authCode = wxCpTpXmlMessage.getAuthCode();String timeStamp = wxCpTpXmlMessage.getTimeStamp();WxCpTpCorp permanentCode = tpService.getPermanentCode(authCode);logger.info("永久授权码:" + permanentCode);break;}case CHANGE_AUTH:{logger.info("变更授权");// TODO: 2020/2/14break;}case CHANGE_CONTACT:{logger.info("通讯录变更");String changeType = jsonObject.get(WxCpTpInnerConstant.CHANGE_TYPE).toString();ChangeTypeEnum changeTypeEnum = ChangeTypeEnum.getByInstance(changeType);if (Objects.isNull(changeTypeEnum)){throw new Exception("该类型通讯录变更不存在");}switch (changeTypeEnum){case DELETE_USER:{logger.info("删除用户");break;}case UPDATE_TAG:{logger.info("更新标签");break;}case DELETE_PARTY:{logger.info("删除部门");break;}default:{break;}}break;}case CHANGE_EXTERNAL_CONTACT:{logger.info("外部联系人");String changeType = jsonObject.get(WxCpTpInnerConstant.CHANGE_TYPE).toString();ChangeTypeEnum changeTypeEnum = ChangeTypeEnum.getByInstance(changeType);if (Objects.isNull(changeTypeEnum)){throw new Exception("该类型CHANGE_EXTERNAL_CONTACT不存在");}switch (changeTypeEnum){case ADD_EXTERNAL_CONTACT:{logger.info("添加客户" + jsonObject.toString());String externalUserID = jsonObject.get("ExternalUserID").toString();String welcomeCode = jsonObject.get("WelcomeCode").toString();if (StringUtils.isEmpty(welcomeCode)){logger.warn("welcomeCode没获取到,用户可能已经设置了欢迎语!!!");break;}String userID = jsonObject.get("UserID").toString();String suiteId = jsonObject.get("SuiteId").toString();String authCorpId = jsonObject.get("AuthCorpId").toString();System.out.println("欢迎code:" + welcomeCode);sendWelcome(tpService, welcomeCode);break;}default:{break;}}}default: {break;}}return "success";}/*** 获取操作方式type* @param decryptMsgs* @return*/public static Pair<String, JSONObject> doType(String decryptMsgs){try {JSONObject jsonObject = XML.toJSONObject(decryptMsgs.replace("<xml>", "").replace("</xml>", ""));String infoType = jsonObject.getString(WxCpTpInnerConstant.INFO_TYPE);if (StringUtils.isBlank(infoType)){throw new Exception("infoType为空");}return new Pair<String, JSONObject>(infoType, jsonObject);}catch (Exception e){e.printStackTrace();}return null;}/*** 发送欢迎语* @param tpService* @param welcome_code* @throws Exception*/public void sendWelcome(WxCpTpService tpService, String welcome_code) throws Exception{String auth_corpId = "ww4007de5885";String permCode = authMap.get(auth_corpId);WxAccessToken wxAccessToken = tpService.getCorpToken(auth_corpId, permCode);logger.info("获取企业凭证:" + JSONUtil.toJsonStr(wxAccessToken));String url = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token=%s";// image、link和miniprogram只能有一个String postData_back = "{\n" +"    \"welcome_code\":\""+welcome_code+"\",\n" +"    \"text\": {\n" +"        \"content\":\"欢迎,自定义文本消息内容\"\n" +"    },\n" +"    \"image\": {\n" +"        \"media_id\": \"1Qio5JosY6ylJN9LzyOSrhCYkZQA0IzDYV-PSOXca4bWaX1B-uzTRBXXS1H3bPcg2\"\n" +"    },\n" +"    \"link\": {\n" +"        \"title\": \"消息标题\",\n" +"        \"picurl\": \"https://assets.2dfire.com/frontend/277ec5572aad73d80b3bcaa5b57fc65b.png\",\n" +"        \"desc\": \"消息描述\",\n" +"        \"url\": \"http://2dfire.com\"\n" +"    },\n" +"    \"miniprogram\": {\n" +"        \"title\": \"消息标题找店\",\n" +"        \"pic_media_id\": \"1Qio5JosY6ylJN9LzyOSrhCYkZQA0IzDYV-PSOXca4bWaX1B-uzTRBXXS1H3bPcg2\",\n" +"        \"appid\": \"wx3c2dd923aea\",\n" +"        \"page\": \"/pages/findShop\"\n" +"    }\n" +"}\n";String postData = "{\n" +"    \"welcome_code\":\""+welcome_code+"\",\n" +"    \"text\": {\n" +"        \"content\":\"欢迎,自定义文本消息内容\"\n" +"    },\n" +"    \"miniprogram\": {\n" +"        \"title\": \"消息标题找店\",\n" +"        \"pic_media_id\": \"1xWyPP9Pcf2oOso6fBCTsELmjTyehO-aOOOWZs54NNCQLfNCrW8PXsoYIp1jaTX\",\n" +"        \"appid\": \"wx3c2dd9236aea\",\n" +"        \"page\": \"/pages/findShop\"\n" +"    }\n" +"}\n";String result = tpService.post(String.format(url, wxAccessToken.getAccessToken()), postData);//JsonObject jsonObject = new JsonParser().parse(result).getAsJsonObject();System.out.println("发送欢迎语:"+ result);}}

5.5 WxCpTpOauthController.java
package com.lxh.cptp.controller;import com.alibaba.fastjson.JSONObject;
import com.lxh.cptp.config.WxCpTpConfiguration;
import com.lxh.cptp.utils.JsonUtils;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.util.RandomUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.cp.api.WxCpTpService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;import static com.lxh.cptp.config.WxCpTpConfiguration.SUIT_ID;/*** created by lanxinghua@2dfire.com on 2020/2/23* 授权相关(第三方应用)*/
@RestController
@RequestMapping("/wx/oauth")
public class WxCpTpOauthController extends BaseController{private WxCpTpService tpService;private final Logger logger = LoggerFactory.getLogger(this.getClass());private String oauthUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_privateinfo&state=%s#wechat_redirect";private String loginUrl = "http://chenxingxing.51vip.biz/wx/oauth/login";/*** 拆分链接* @param url*/@GetMapping("/jump")public void jump(String url,HttpServletResponse response) throws IOException {if (StringUtils.isBlank(url)) {throw new IllegalArgumentException("url is empty");}oauthUrl = String.format(oauthUrl, SUIT_ID, loginUrl, url);logger.info("跳转url:" + oauthUrl);response.sendRedirect(oauthUrl);}/*** 授权链接  通过code换取用户信息*/@GetMapping("/login")public void login(String code,String state,HttpServletRequest request,HttpServletResponse response) {if (StringUtils.isBlank(code)) {throw new IllegalArgumentException("code is empty");}try {tpService = WxCpTpConfiguration.getCpTpService(SUIT_ID);JSONObject userInfo3rd = getUserInfo3rd(tpService, code);if (userInfo3rd == null){throw new Exception("用户信息获取失败");}JSONObject userInfoDetail = getUserDetail3rd(tpService, userInfo3rd.get("user_ticket").toString());request.getSession().setAttribute("token", userInfoDetail);response.sendRedirect(state);} catch (Exception e) {this.logger.error(e.getMessage(), e);}}/*** 创建js-sdk签名* @param url* @return* @throws Exception*/@RequestMapping("/create/jsapi_sign")@ResponseBodypublic Object jssdk(@RequestParam  String url,HttpServletRequest request) throws Exception{// 从url里面解析test参数String auth_corpId = "wwd282e1e3";if (url.contains("entityId")){Map<String, Object> parameter = getParameter(url);auth_corpId = testMap.get(parameter.get("entityId").toString());}if (StringUtils.isEmpty(auth_corpId)){throw new Exception("企业授权id为空.");}logger.info("authCorpId:{}  url:{}", auth_corpId, url);tpService = WxCpTpConfiguration.getCpTpService(SUIT_ID);long timestamp = System.currentTimeMillis() / 1000;String nonceStr = RandomUtils.getRandomStr();JSONObject object = getJsApiTicket(auth_corpId, tpService);String jsApiTicket = object.getString("ticket");String signature = SHA1.genWithAmple("jsapi_ticket=" + jsApiTicket,"noncestr=" + nonceStr,"timestamp=" + timestamp,"url=" + url);WxJsapiSignature jsapiSignature = new WxJsapiSignature();jsapiSignature.setTimestamp(timestamp);jsapiSignature.setNonceStr(nonceStr);jsapiSignature.setUrl(url);jsapiSignature.setSignature(signature);jsapiSignature.setAppId(auth_corpId);logger.info("data:" + JsonUtils.toJson(jsapiSignature));return jsapiSignature;}
}

有什么问题,可以下面留言,一起交流…

企业微信脚手架(第三方)相关推荐

  1. 企业微信脚手架(企业内部)

    开发文档地址:https://work.weixin.qq.com/api/doc/90000/90003/90556 企业微信后台:https://work.weixin.qq.com/ 更新日志: ...

  2. 前端企业微信服务商第三方应用开发详情流程2021-4-9

    1.首先要申请2个企业账号,一个账号是服务商也就是你即将开发第三应用的企业. 另一个就是安装你这个应用的企业,也就是测试用户. 2.申请通过备案的开发环境,测试环境,正式环境的各种域名.在应用的可信域 ...

  3. 企业微信开发第三方应用开发视频教程,ToB Dev李月喜全网首发

    csdn程序员学院<企业微信三方应用开发>视频课程,全网企业微信三方应用开发教程首发, https://edu.csdn.net/course/detail/30582  即将完结欢迎试看 ...

  4. 企业微信接入第三方应用并配置身份认证

    这里以百度贴吧为第三方应用为例,url为:https://tieba.baidu.com/index.html 1.创建企业微信第三方应用 第一步,企业微信管理员登录企业微信管理后台添加应用 第二步, ...

  5. 对接企业微信上架第三方应用_JAVA

    一.创建应用 登录服务商平台->应用管理->应用开发->网页应用->创建应用 具体地址https://open.work.weixin.qq.com/wwopen/develo ...

  6. 企业微信---第三方应用开发 笔记

    跳坑记录 要成为第三方供应商,先按开发文档步骤操作.服务商注册应用.也可以申请个企业微信帐号,然后成为服务商(可以做测试,但应用不能上线) 1,注册成功后,配置开发信息:通用开发参数 这里的CorpI ...

  7. 一款基于企业微信的固定资产管理软件

    移动办公的必要性和重要性,在今年显得尤为突出.远程办公的需求日益增加,移动化管理固定资产,给企业带来了极大的便利.移动化管理固定资产,不但可以提升固定资产管理的工作效率,而且实现了无触式办公操作. 企 ...

  8. 为什么用企业微信做运营

    很多企业都在用企业微信做私域用户运营,取得了很不错的成绩,卖珠宝的周大福,家具定制的索菲亚,在线教育的猿辅导,休闲零食的恰恰,餐饮乐凯撒引流30万等 为什么这些企业都会采用企业微信呢?用两组数据,三件 ...

  9. 干货分享--企业微信社群促活的12种方式

    微信今年公布的数据,日活(DAU)超过10亿,每天基于微信生态里面新产生的微信群就高达千万个,腾讯将企业微信与微信互通后,企业微信社群运营目前已经成为社群运营的新方向. 与微信社群一样,一个用户加入一 ...

最新文章

  1. axure按钮切换颜色_如何用Axure画出Web产品的列表组件:基础画法
  2. 【全网最精简写法】ES6获取浏览器url跟参
  3. 极光API推送 (v3 版本)
  4. SSL证书过期替换之踩坑总结
  5. 用友BQ商业智能平台——图表功能
  6. [JSOI2008 Prefix火星人]
  7. Ubuntu 8.10的5个新特色
  8. hdu4418 概率dp+高斯消元(近日吐槽)
  9. Android程序开发初级教程(一)
  10. PDF to Word OCR for Mac(PDF文档转换成word格式)
  11. Spring事务传播机制详解
  12. Echarts 柱状图设置颜色
  13. 淘宝直通车现在每天烧多少钱,500元直通车能开多久?
  14. Vue2项目中使用AntV/X6 分辨率适配
  15. XMind 2021 Mac 去水印教程
  16. 这些强大的数据分析软件你都知道吗?
  17. 网站白名单可行性分析
  18. trainging contest#2(2011成都现场赛)D BY bly
  19. android 软解8k视频,外媒:别被忽悠了!手机目前支持8K视频毫无意义
  20. 百度地图 行政区切换显示

热门文章

  1. PLSQL界面打不开、PLSQL无法启动解决办法
  2. MATLAB实现自编码器(一)——Autoencoder类和训练设置
  3. 爬取17k小说网的小说
  4. 兄弟俩畅游Tomcat城市的SpringMVC科技园区
  5. 软件设计文档国家标准
  6. Lost connection to MySQL server during query (by quqi99)
  7. 2022-2028年中国植物工厂行业发展动态及投资规划分析报告
  8. 【必看】App Store 要求App填写隐私保护问卷?别慌!友小盟手把手教你填~
  9. 坑挺多 | 联邦学习FATE:训练模型(二)
  10. 2022新的Echarts Demo 网址