再說一次,企業微信開發文檔很坑,特別坑,坑成鬼了。
有時候浪費很長時間改bug,查來查去,都沒有發現到底哪里錯了,查資料,搜資料,詢問大神,都沒有發現bug。改到最后都抑郁了,才發現用瀏覽器訪問根本就不會報出具體的錯誤,pc端的企業微信客戶端也不行,只有用手機上的企業微信客戶端訪問才會有具體的錯誤信息,在這方面浪費了很長時間,感覺自己有點傻,但是內心不承認。
總之,要想一帆風順開發,聽起來像是開玩笑,總會遇到讓自己懷疑人生的bug。
文檔對於很多細節描述不具體,而且也有很多東西沒有demo可以參觀,有demo的還是PHP,怎么說呢,有些東西想在網上查查,都查不到,所有,在開始之前,請讓我先好好吐槽一下,以解我這幾天的心頭之火。
好吧,廢話不多說,總不能轉行吧!所以還是好好學習吧!!
企業微信自建應用審批接口開發:
企業微信自帶有審批接口,最主要是,使用企業微信審批能力,在非審批應用內設置流程、發起審批。還能訂閱通知消息,接收審批狀態變化情況。
現在很多企業都使用企業微信審批接口,使用企業微信審批接口,審批發生變化,會自動發送消息給審批人以及抄送人,比較方便。微信自帶的審批接口適用於小企業,簡單的業務。一般大公司都會開發適用於自己企業的OA系統。
企業微信自建應用審批接口開發:
1.首先在企業微信后端,創建應用,進入審批接口,添加模板
2.自建應用發起審批
通過JS-SDK,可在自建應用中發起審批,需要設置可信域名,花生殼免費域名,我試了,不行!
3.設置工作台應用主頁,在主頁內發起審批
注意:
1.測試時一定要用手機企業微信客戶端,會顯示詳細錯誤信息,用瀏覽器或者pc端, 比較坑爹。
2.善於利用簽名工具,驗證自己各數據是否正確生成
1.首先在企業微信后端,創建應用,進入審批接口,添加模板
2.自建應用發起審批
點擊接入流程說明,進入開發API
開發邏輯為上圖所示
2.1
通過JS-SDK,可在自建應用中發起審批。查看JS-SDK調用詳細說明
具體步驟:
1.通過config接口注入權限驗證配置
2.通過agentConfig注入應用的權限
3.調用審批流程引擎JS-API
1.通過config接口注入權限驗證配置
wx.config({
beta: true,// 必須這么寫,否則wx.invoke調用形式的jsapi會有問題
debug: true, // 開啟調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時才會打印。
appId: '', // 必填,企業微信的corpID
timestamp:'' , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名,見 附錄-JS-SDK使用權限簽名算法
jsApiList: ['agentConfig','thirdPartyOpenPage','selectExternalContact'] // 必填,需要使用的JS接口列表,凡是要調用的接口都需要傳進來
});
wx.ready(function(){
2.通過agentConfig注入應用的權限。
wx.agentConfig({
corpid: '', // 必填,企業微信的corpid,必須與當前登錄的企業一致
agentid: '', // 必填,企業微信的應用id
timestamp: , // 必填,生成簽名的時間戳
nonceStr: '', // 必填,生成簽名的隨機串
signature: '',// 必填,簽名,見附錄1
jsApiList: ['thirdPartyOpenPage','selectExternalContact'], //必填
success: function(res) {
3.調用審批流程引擎JS-API
wx.invoke('thirdPartyOpenPage', {
"oaType": "10001",// String
"templateId": "46af67a118a6ebf000002",// String
"thirdNo": "thirdNo",// String
"extData": {
'fieldList': [{
'title': '采購類型',
'type': 'text',
'value': '市場活動',
},
{
'title': '訂單鏈接',
'type': 'link',
'value': 'https://work.weixin.qq.com',
}],
}
},
function(res) {
// 輸出接口的回調信息
console.log(res);
});
},
fail: function(res) {
if(res.errMsg.indexOf('function not exist') > -1){
alert('版本過低請升級')
}
}
});
});
各個接口都調用成功的話,那么離成功就不遠了;
2.1.jsp頁面主要代碼
<script type="text/javascript">
function approval() {
//動態獲取當前頁面url
var link = window.location.href;
$.ajax({
type:"GET",
data:{"url":link},
url:"<%=request.getContextPath()%>/approval/send_approval.do",
dataType:"json",
success:function (data) {
console.log(data);
wx.config({
beta: true,
debug: true,
appId: data.appId,
timestamp: data.config_timestamp,
nonceStr: data.config_nonceStr,
signature: data.config_signature,
jsApiList: ['agentConfig','openUserProfile','thirdPartyOpenPage','selectExternalContact']
});
wx.ready(function () {
alert("config");
wx.agentConfig({
corpid: data.appId,
agentid: data.agentid,
timestamp: data.agent_timestamp,
nonceStr: data.agent_nonceStr,
signature: data.agent_signature,
jsApiList: ['thirdPartyOpenPage','selectExternalContact'],
success: function(res) {
//審批流程js調用
alert("agentConfig");
wx.invoke('thirdPartyOpenPage', {
"oaType": data.oaType,
"templateId": data.templateId,
"thirdNo": data.thirdNo,
"extData": {
'fieldList': [{
'title': '采購類型',
'type': 'text',
'value': '市場活動',
},
{
'title': '訂單鏈接',
'type': 'link',
'value': 'https://work.weixin.qq.com',
}],
}
},
function(res) {
// 輸出接口的回調信息
alert("thirdPartyOpenPage");
alert(res);
}
);
},
fail: function(res) {
alert("approval提交不通過");
alert("agentConfig:"+res.errMsg);
if(res.errMsg.indexOf('function not exist') > -1){
alert('版本過低請升級')
}
}
});
});
wx.error(function(res){
});
}
})
}
</script>
2.2后端主要代碼
@RequestMapping(value = "send_approval.do")
public void sendApproval(HttpServletRequest request, HttpServletResponse response,String url)throws Exception{
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
JSONObject jsonObject = iApprovalService.approvalCreate(url).getData();
//返回數據到jsp頁面
response.getWriter().print(jsonObject);
}
public JSONObject approvalCreate(String url){
JSONObject object = new JSONObject();
//agentConfig接口下主要數據
String approval_ticket = JS_Util.getApprovalTicket();
JSONObject appJSONObject = JS_Util.getSignature(url,approval_ticket);
object.put("agent_nonceStr",appJSONObject.get("nonceStr").toString());
object.put("agent_timestamp",appJSONObject.get("timestamp").toString());
object.put("agent_signature",appJSONObject.get("signature").toString());
System.out.println("===============================");
System.out.println(appJSONObject.toString());
//config接口下主要數據
String ticket = JS_Util.getJsApiTicket();
JSONObject jsonObject = JS_Util.getSignature(url,ticket);
object.put("config_nonceStr",jsonObject.get("nonceStr").toString());
object.put("config_timestamp",jsonObject.get("timestamp").toString());
object.put("config_signature",jsonObject.get("signature").toString());
System.out.println("===========================");
System.out.println(jsonObject.toString());
//invoke接口下主要數據
object.put("thirdNo",String.valueOf(generateOrderNo()));
object.put("appId",PropertiesUtil.getProperty("corpid"));
object.put("agentid",PropertiesUtil.getProperty("mycreateapp_agentid"));
object.put("templateId",PropertiesUtil.getProperty("approval_id"));
object.put("oaType","10001");
return object;
}
signatrue的生成
可以使用簽名工具,查看生成的signatrue是否正確,可以快速的排除自己代碼的錯誤
signature生成
簽名算法
簽名生成規則如下:
參與簽名的參數有四個: noncestr(隨機字符串), jsapi_ticket, timestamp(時間戳), url(當前網頁的URL, 不包含#及其后面部分)
將這些參數使用URL鍵值對的格式 (即 key1=value1&key2=value2…)拼接成字符串string1。
有兩個注意點:1. 字段值采用原始值,不要進行URL轉義;2. 必須嚴格按照如下格式拼接,不可變動字段順序。
jsapi_ticket=JSAPITICKET&noncestr=NONCESTR×tamp=TIMESTAMP&url=URL
然后對string1作sha1加密即可。
注意事項
簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。
簽名用的url必須是調用JS接口頁面的完整URL。
出於安全考慮,開發者必須在服務器端實現簽名的邏輯。
public class JS_Util {
private static Logger logger = LoggerFactory.getLogger(JS_Util.class);
//獲取企業的jsapi_ticket
private final static String GET_WX_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN";
//應用jsapi_tictet
private final static String GET_APP_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=ACCESS_TOKEN&type=agent_config";
//獲取企業的ticket
public static String getJsApiTicket(){
String access_token = WeiXinUtil.getAccessToken(PropertiesUtil.getProperty("corpid"),PropertiesUtil.getProperty("mycreateapp_secret")).getAccess_token();
String url = GET_WX_TICKET.replace("ACCESS_TOKEN",access_token);
JSONObject jsonObject = WeiXinUtil.httpRequest(url,"GET",null);
String ticket = jsonObject.get("ticket").toString();
return ticket;
}
//獲取審批流程ticket
public static String getApprovalTicket(){
String access_token = WeiXinUtil.getAccessToken(PropertiesUtil.getProperty("corpid"),PropertiesUtil.getProperty("mycreateapp_secret")).getAccess_token();
String url = GET_APP_TICKET.replace("ACCESS_TOKEN",access_token);
JSONObject jsonObject = WeiXinUtil.httpRequest(url,"GET",null);
String ticket = jsonObject.get("ticket").toString();
return ticket;
}
/**
* 參與簽名的參數有四個: noncestr(隨機字符串), jsapi_ticket, timestamp(時間戳), url(當前網頁的URL, 不包含#及其后面部分)
* 將這些參數使用URL鍵值對的格式 (即 key1=value1&key2=value2…)拼接成字符串string1。
* 然后對string1作sha1加密即可。
* @return
*/
public static JSONObject getSignature(String url,String ticket){
JSONObject jsonObject = new JSONObject();
String noncestr = getRandomString(16);
String timestamp = Long.toString(System.currentTimeMillis()).substring(0,10);
// jsapi_ticket=JSAPITICKET&noncestr=NONCESTR×tamp=TIMESTAMP&url=URL 順序不能更改
String str = "jsapi_ticket="+ticket+
"&noncestr="+noncestr+
"×tamp="+timestamp+
"&url="+url;
String signature = SHA1(str);
jsonObject.put("nonceStr",noncestr);
jsonObject.put("timestamp",timestamp);
jsonObject.put("signature",signature);
jsonObject.put("ticket",ticket);
return jsonObject;
}
private static String SHA1(String decript){
try{
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(decript.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
String shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
}catch (Exception e){
logger.error("sha1加密錯誤",e);
}
return "";
}
private static String getRandomString(int length){
String keyString = "ergrfewfwdgggcvv;uihefujsncjdvngrjegeuirgverggvbergbvuigverug";
int len = keyString.length();
StringBuffer str = new StringBuffer();
for(int i=0;i<length;i++){
str.append(keyString.charAt((int) Math.round(Math.random() * (len - 1))));
}
return str.toString();
}
}
獲取企業的ticket和應用的ticket,請求的url不同,但是使用的accessToken是相同的
public class WeiXinUtil {
private static Logger log = LoggerFactory.getLogger(WeiXinUtil.class);
//微信的請求url
//獲取access_token的接口地址(GET)
public final static String access_token_url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpId}&corpsecret={corpsecret}";
/**
* 1.發起https請求並獲取結果
*/
public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = null;
StringBuffer buffer = new StringBuffer();
try {
// 創建SSLContext對象,並使用我們指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 從上述SSLContext對象中得到SSLSocketFactory對象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 設置請求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
if ("GET".equalsIgnoreCase(requestMethod))
httpUrlConn.connect();
// 當有數據需要提交時
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意編碼格式,防止中文亂碼
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 將返回的輸入流轉換成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 釋放資源
inputStream.close();
inputStream = null;
httpUrlConn.disconnect();
jsonObject = JSONObject.fromObject(buffer.toString());
} catch (ConnectException ce) {
log.error("Weixin server connection timed out.");
} catch (Exception e) {
log.error("https request error:{}", e);
}
return jsonObject;
}
/**
* 獲取應用的access_token
*/
public static AccessToken getAccessToken(String appid, String appsecret) {
AccessToken accessToken = null;
String requestUrl = access_token_url.replace("{corpId}", appid).replace("{corpsecret}", appsecret);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
// 如果請求成功
if (null != jsonObject) {
try {
accessToken = new AccessToken();
accessToken.setAccess_token(jsonObject.getString("access_token"));
accessToken.setExpires_in(jsonObject.getInt("expires_in"));
} catch (JSONException e) {
accessToken = null;
// 獲取token失敗
log.error("獲取token失敗 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
return accessToken;
}
}
主要的參數都被我統一放在了properties文件里,寫了個工具類PropertiesUtil進行取出,方便管理。
3.設置工作台應用主頁,在主頁內發起審批,域名為設置的可信域名,剛開始用的花生殼,沒有測試通過,域名所有權驗證不通過
測試時一定要用手機企業微信客戶端,會顯示詳細錯誤信息,用瀏覽器或者pc端, 比較坑爹。