接入企業(yè)微信審批開發(fā)記錄
背景
客戶提了關(guān)于對接企業(yè)微信【審批】功能的需求,具體需求包括:
1、當(dāng)企業(yè)微信審批流程到達(dá)某個節(jié)點(diǎn)后,能將審批信息推送到我們系統(tǒng),或者我們系統(tǒng)能夠拉取某個流程的信息;
2、能在審批流程中嵌套一個評價節(jié)點(diǎn),跳轉(zhuǎn)我們系統(tǒng)的評價頁面。
1 調(diào)研企業(yè)微信是否支持
根據(jù)這篇博文,可知企業(yè)微信支持 審批回調(diào) ,這個在官方文檔中也可以找到。所以1中 當(dāng)企業(yè)微信審批流程到達(dá)某個節(jié)點(diǎn)后,能將審批信息推送到我們系統(tǒng)是可以做到的。
對于2中的需求,經(jīng)調(diào)研,發(fā)現(xiàn)企業(yè)微信支持定制審批流程的 模版 ,可以在 模版 中加上一個 說明文字 的控件,在控件中插入鏈接,然后靠用戶的自覺性,在某個節(jié)點(diǎn)點(diǎn)擊鏈接去評價,這個實(shí)現(xiàn)客戶也是認(rèn)可的。
2 流程梳理
審批回調(diào) 自然需要配置回調(diào)接口,企業(yè)微信的邏輯是先在企業(yè)微信中 自建 一個應(yīng)用,然后給這個自建應(yīng)用進(jìn)行 接收消息服務(wù)器配置 ,這個 接收消息服務(wù)器配置 就是回調(diào)的url,然后在 審批 中配置 開啟回調(diào)通知的模版 和 可調(diào)用接口的應(yīng)用 ,這樣開啟了企業(yè)微信審批申請狀態(tài)變化回調(diào)。
2.1 自建應(yīng)用
在這里先講一下如何創(chuàng)建一個企業(yè)用于自己開發(fā)測試,移動端比較簡單,左側(cè)點(diǎn)擊圖標(biāo)新建即可。
首先在PC端的企業(yè)微信上進(jìn)入工作臺

接著隨便點(diǎn)擊一個應(yīng)用,比如審批,點(diǎn)擊右上角的圖標(biāo)前往管理后臺

然后就會打開瀏覽器,點(diǎn)擊頂部的 應(yīng)用管理 菜單,可以看大下面有 創(chuàng)建應(yīng)用 的選項(xiàng)

點(diǎn)擊創(chuàng)建,填入必填項(xiàng),創(chuàng)建成功

接著就是配置應(yīng)用中的一些信息,上面的這個 AgentId 是應(yīng)用的ID,那個 Secret 是應(yīng)用的憑證密鑰,這個在獲取企業(yè)微信access_token的時候需要

接下來往下拖,點(diǎn)擊那個 API接收消息,我這里配置過了,顯示的是已啟用

可以看到這里有3個參數(shù),下面2個是點(diǎn)擊右邊的 隨機(jī)獲取 按鈕隨機(jī)獲取的,第1個參數(shù)URL,可以參考邊上的獲取幫助鏈接,這個是企業(yè)微信的驗(yàn)證URL,這里的實(shí)現(xiàn)放在2.2中

還需要配置 企業(yè)可信IP , 企業(yè)可信IP 是配置哪些IP可以通過API獲取企業(yè)數(shù)據(jù),比如審批流程信息

2.2 回調(diào)接口實(shí)現(xiàn)
這一節(jié)說明的是企業(yè)微信應(yīng)用驗(yàn)證URL和回調(diào)接口的實(shí)現(xiàn)。
首先,需要下載企業(yè)微信的加解密庫,選擇XML版本,下面的代碼依賴于這些。
點(diǎn)擊查看代碼
package com.springboot.demo.scheduled;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.springboot.demo.aes.WXBizMsgCrypt;
import lombok.extern.slf4j.Slf4j;
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 org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringReader;
/**
* 企業(yè)微信審批狀態(tài)變化回調(diào)
*
* @author Lip
* @since 2024-12-31
*/
@Slf4j
@RestController
@RequestMapping("qy")
public class QywxCallbackController {
@GetMapping(value = "/Callback")
public void connect(HttpServletRequest request, HttpServletResponse response) {
// 企業(yè)號將發(fā)送GET請求到填寫的URL上,GET請求攜帶四個參數(shù),企業(yè)在獲取時需要做url_decode處理,否則會驗(yàn)證不成功
// 微信加密簽名
String msgSignature = request.getParameter("msg_signature");
// 時間戳
String timestamp = request.getParameter("timestamp");
// 隨機(jī)數(shù)
String nonce = request.getParameter("nonce");
// 隨機(jī)字符串
String echoStr = request.getParameter("echostr");
// 自建應(yīng)用中生成的
String contactsToken = "5AsIzFkiXMaa";
String contactsEncodingAesKey = "GwJH5XOLPSmlTGJ2qKEcIy1k3wGg4rsFO51Df7woNsi";
// 企業(yè)ID
String corpId = "ww1f0eebf7e9d7c54d";
// 回調(diào)key值
String sEchoStr;
try {
PrintWriter out = response.getWriter();
WXBizMsgCrypt wxCrypt = new WXBizMsgCrypt(contactsToken, contactsEncodingAesKey, corpId);
sEchoStr = wxCrypt.VerifyURL(msgSignature, timestamp, nonce, echoStr);
if (StringUtils.isBlank(sEchoStr)) {
log.error("URL驗(yàn)證失敗");
}
out.write(sEchoStr);
out.flush();
} catch (Exception e) {
log.error("企業(yè)微信回調(diào)url驗(yàn)證錯誤", e);
}
}
@PostMapping(value = "/Callback")
public void acceptMessage(HttpServletRequest request) {
System.out.println("————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————");
log.info("企業(yè)微信信息交互");
// 微信加密簽名
String sMsgSignature = request.getParameter("msg_signature");
// 時間戳
String sTimestamp = request.getParameter("timestamp");
// 隨機(jī)數(shù)
String sNonce = request.getParameter("nonce");
System.out.println("acceptMessage方法sMsgSignature: " + sMsgSignature);
System.out.println("acceptMessage方法sTimestamp: " + sTimestamp);
System.out.println("acceptMessage方法sNonce: " + sNonce);
try {
// 獲取請求的輸入流
ServletInputStream inputStream = request.getInputStream();
// 創(chuàng)建一個 StringBuilder 對象來存儲請求內(nèi)容
StringBuilder xmlContent = new StringBuilder();
// 使用 BufferedReader 讀取輸入流內(nèi)容
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
xmlContent.append(line);
}
// 關(guān)閉輸入流和讀取器
inputStream.close();
reader.close();
// 輸出請求內(nèi)容
System.out.println("請求 XML 內(nèi)容:" + xmlContent);
String sReqData = xmlContent.toString();
// String sReqData = "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt><AgentID><![CDATA[218]]></AgentID></xml>";
String contactsToken = "5AsIzFkiXMaa";
String contactsEncodingAesKey = "GwJH5XOLPSmlTGJ2qKEcIy1k3wGg4rsFO51Df7woNsi";
String corpId = "ww1f0eebf7e9d7c54d";
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(contactsToken, contactsEncodingAesKey, corpId);
String sMsg = wxcpt.DecryptMsg(sMsgSignature, sTimestamp, sNonce, sReqData);
System.out.println("after decrypt msg: " + sMsg);
// TODO: 解析出明文xml標(biāo)簽的內(nèi)容進(jìn)行處理
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(sMsg);
InputSource is = new InputSource(sr);
Document document = db.parse(is);
Element root = document.getDocumentElement();
NodeList nodelist1 = root.getElementsByTagName("SpNo");
// 獲取審批編號
String SpNo = nodelist1.item(0).getTextContent();
System.out.println("審批編號為:" + SpNo);
NodeList SpStatus_Node = root.getElementsByTagName("SpStatus");
// 申請單狀態(tài):1-審批中;2-已通過;3-已駁回;4-已撤銷;6-通過后撤銷;7-已刪除;10-已支付
String SpStatus = SpStatus_Node.item(0).getTextContent();
NodeList SpRecord_Node = root.getElementsByTagName("SpRecord");
for (int i = 0; i < SpRecord_Node.getLength(); i++) {
System.out.println("審批記錄第" + (i + 1) + "個節(jié)點(diǎn)審批狀態(tài)為:" + SpRecord_Node.item(i).getFirstChild().getTextContent());
}
} catch (Exception e) {
log.error("企業(yè)微信消息交互錯誤", e);
}
}
}
這兩個接口的路徑是一樣的,GET方法是驗(yàn)證接口,POST方法是回調(diào)接口。
2.3 審批流程模板添加鏈接



看圖,3步搞定

浙公網(wǎng)安備 33010602011771號