開發文檔地址: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: info
com.lxh.cp: debug
cn.binarywang.wx.cp: debug
server:
port: 80
servlet:
context-path: /
wx:
cptp:
corpId: ww4007bc86bexxxx
suiteId: ww141f5c1b0bfexxxx
secret: NpR7i2nC_lAdr-nhm4Chxxxxxxxxxxxxx
aesKey: 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 [email protected] on 2020/2/25
* 企業微信第三方,緩存支持
* 1.suiteAccessToken
*/
public class WxCpTpRedisConfigImpl extends WxCpTpDefaultConfigImpl {
// suite_ticket與suite_secret配套使用,用於獲取suite_access_token 10min
private 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;
@Override
public void setSuiteId(String corpId) {
SUITE_ACCESS_TOKEN_KEY += corpId;
super.setSuiteId(corpId);
}
public WxCpTpRedisConfigImpl(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**-------------------------- suite ticket -------------------------**/
@Override
public 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);
}
}
@Override
public boolean isSuiteTicketExpired() {
try (Jedis jedis = this.jedisPool.getResource()) {
return jedis.ttl(SUITE_TICKET_KEY) < 2;
}
}
@Override
public 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);
}
}
@Override
public void expireSuiteTicket() {
try (Jedis jedis = this.jedisPool.getResource()) {
jedis.expire(SUITE_TICKET_KEY, 0);
}
}
/**-------------------------- suite access token -------------------------**/
@Override
public String getSuiteAccessToken() {
if (isSuiteAccessTokenExpired()){
return null;
}
try (Jedis jedis = this.jedisPool.getResource()) {
return jedis.get(SUITE_ACCESS_TOKEN_KEY);
}
}
@Override
public boolean isSuiteAccessTokenExpired() {
try (Jedis jedis = this.jedisPool.getResource()) {
return jedis.ttl(SUITE_ACCESS_TOKEN_KEY) < 2;
}
}
@Override
public synchronized void updateSuiteAccessToken(WxAccessToken suiteAccessToken) {
this.updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn());
}
@Override
public synchronized void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
try (Jedis jedis = this.jedisPool.getResource()) {
jedis.setex(SUITE_ACCESS_TOKEN_KEY, expiresInSeconds - 200, suiteAccessToken);
}
}
@Override
public 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 使用第三方企業對應的id
public 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 [email protected] 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();
@Autowired
private WxCpTpProperties properties;
// 更新2020.2.25 加入redis
@Autowired
private 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);
}
/**
* 初始化服務
*/
@PostConstruct
public 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 [email protected] 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/14
break;
}
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 [email protected] 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")
@ResponseBody
public 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;
}
}
有什麼問題,可以下面留言,一起交流…