一、構建spring boot專案
1、新建專案
新建一個模塊(module):enterprise-wechat
新建一個子模塊(module):wechat
目錄結構如下:

結構描述:
common
-> WeChatConstants:存放企業微信一些常量,公用引數
-> WeChatUtils:存放企業微信第三方應用api
controller
-> SystemController:控制層,接收請求
entity
-> aes:目錄下檔案企業微信加解密包
service
-> IConfigService:呼叫企業微信服務層
pom.xml
-> 匯入所需要的jar包
pom.xml中需要匯入commons.codec包
<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
package com.wechat.common;
/**
* 企業微信
*/
public class WeChatConstants {
// 企業微信授權碼獲取時間
public static final Long EXPIRES_IN = 24 * 60 * 60 * 1000L;
//24 * 60 * 60 * 1000L 7200L * 1000
/**
* 服務商CorpID
*/
public static final String CORP_ID = "ww14438c6c07a317f2";
/**
* 服務商身份的呼叫憑證
*/
public static final String PROVIDER_SECRET = "RH7PehRJX3LIcw4axad_H2T9HSUG1finOBEpnLTVIioBrP-zgZrGsqJ9pHVw5vVj";
/**
* 應用的唯一身份標識
*/
public static final String SUITE_ID = "ww4f66fa544a32f920";
/**
* 應用的呼叫身份密鑰
*/
public static final String SUITE_SECRET = "vVv8JzaBlEVCTQkHKqmr57EAMs65AILWiI_4ANc25T4";
// 回呼相關
/**
* 回呼/通用開發引數Token, 兩者解密演算法一樣,所以為方便設為一樣
*/
public static final String TOKENS = "E0sOXx4LqeE5BmDvMTAz3x";
/**
* 回呼/通用開發引數EncodingAESKey, 兩者解密演算法一樣,所以為方便設為一樣
*/
public static final String ENCODING_AES_KEY = "IESLPSyW4vyBB90jkzfwfYRtcMky6LIOevr4SVefz7I";
}
2)企業微信api:WeChatUtils
package com.wechat.common;
/**
* 企業微信工具類
*/
public class WeChatUtils {
/**
* 第三方應用api start
*/
// 獲取第三方應用憑證
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";
// 第三方 獲取登錄用戶資訊 POST
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";
// 第三方 獲取訪問用戶身份 GET
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";
// 第三方 獲取訪問用戶敏感資訊 post
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";
// 第三方 讀取成員 GET
public final static String THIRD_BUS_WECHAT_GET_USER = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USERID";
// 服務商的token
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";
// 獲取應用的jsapi_ticket
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";
// 獲取企業的jsapi_ticket
public final static String THIRD_BUS_GET_JSAPI_TICKET_BUS = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN";
/**
* 第三方應用api end
*/
}
3)controller層:SystemController
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 = "https://www.cnblogs.com/why0703/p/system")
public class SystemController {
@Autowired
private IConfigService configService;
/**
* 驗證通用開發引數及應用回呼
* @param: request
* @param: response
* @returns: void
*/
@ApiOperation(value = "https://www.cnblogs.com/why0703/p/驗證通用開發引數及應用回呼")
@GetMapping(value = "https://www.cnblogs.com/why0703/p/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");
// 隨機字串
// 如果是重繪,需回傳原echostr
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);
// 驗證URL成功,將sEchoStr回傳
out = response.getWriter();
out.print(sEchoStr);
} catch (Exception e) {
//驗證URL失敗,錯誤原因請查看例外
e.printStackTrace();
}
}
/**
* 重繪ticket,AuthCode
*/
@ApiOperation(value = "https://www.cnblogs.com/why0703/p/重繪ticket,AuthCode")
@PostMapping(value = "https://www.cnblogs.com/why0703/p/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");
// 企業id
String corpId = request.getParameter("corpid");
ServletInputStream in = request.getInputStream();
// 重繪ticket,AuthCode
String success = configService.doPostCallback(msgSignature, timestamp, nonce, type, corpId, in);
return success;
}
}
4)Service層:IConfigService
package com.wechat.service;
import javax.servlet.ServletInputStream;
/**
* 企業微信第三方服務service
*/
public interface IConfigService {
/**
* 驗證通用開發引數及應用回呼
* @returns: java.lang.String
*/
String doGetCallback(String msgSignature, String timestamp, String nonce, String echoStr);
/**
* 獲取SuiteTicket,AuthCode
*/
String doPostCallback(String msgSignature, String timestamp, String nonce, String type, String corpId, ServletInputStream in);
}
5)service實作類:ConfigServiceImpl
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.entity.aes.AesException;
import com.wechat.entity.aes.WXBizMsgCrypt;
import com.wechat.service.IConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.servlet.ServletInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;
/**
* TODO 類描述
*/
@Slf4j
@Service
public class ConfigServiceImpl implements IConfigService {
/**
* 驗證通用開發引數及應用回呼
* @returns: java.lang.String
*/
@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;
}
/**
* 獲取SuiteTicket,AuthCode
* @param: msgSignature 微信加密簽名
* @param: timestamp 時間戳
* @param: nonce 亂數
* @param: type 型別
* @param: corpId 企業id
* @param: in
* @returns: java.lang.String
*/
@Override
public String doPostCallback(String msgSignature, String timestamp, String nonce, String type, String corpId, ServletInputStream in) {
String id = "";
// 訪問應用和企業回呼傳不同的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=""; // 密文,對應POST請求的資料
//1.獲取加密的請求訊息:使用輸入流獲得加密請求訊息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+"====postDatahttps://www.cnblogs.com/why0703/p/==="+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");
log.debug("====SuiteTicket=====" + suiteTicket);
} else if(suiteMap.get("AuthCode") != null){
String authCode = (String) suiteMap.get("AuthCode");
log.debug("doPostValid->AuthCode:" + authCode);
}
} catch (Exception e) {
e.printStackTrace();
}
return "success";
}
}
4、驗證
以上代碼撰寫完成后,就可以打包到環境上面進行測驗驗證:
①:echostr驗證


回傳結果:回傳 echostr,并顯示已驗證
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(見企業微信地方應用(二)https://www.cnblogs.com/why0703/p/15983925.html)
provider_access_token:服務商的token
suite_access_token:獲取第三方應用憑證
access_token:授權方(企業)access_token
通過上面的代碼及配置,我們獲取到了suiteTicket和auth_code,
接下來我們要通過這些值獲取到上面token,通過springboot開發實作“企業微信第三方應用(二)api使用測驗”
代碼后面同步到github
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/451226.html
標籤:Java
