1、简介
企业微信向开发者提供审批流程引擎,此特性可将审批流程相关功能嵌入到自建应用中。 开发者可在自建应用中直接调用接口发起审批申请,系统根据审批流程自动通知相关人员进行审批操作。 提交申请后审批流程的每次状态变化,都会通知开发者,可按需进行拓展开发。

2、创建自建应用审批模板
在自己的自建应用中选择审批接口

3、自建应用发起审批
通过JS-SDK,可在自建应用中发起审批
具体步骤:
- 1.通过config接口注入权限验证配置。
- 2.通过agentConfig注入应用的权限。
- 3.调用审批流程引擎JS-API
请求示例:
写的比较简陋,可自行封装
var location = (window.location+'').split('/');
var basePath = location[0]+'//'+location[2]+'/'+location[3];
function approve(orderno) {
if (MessageBox.confirm('询问', '提交审核?', function () {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
config = weixinInit();
wx.config({
beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: config.appID, // 必填,企业号的唯一标识,此处填写企业号corpid
timestamp: config.conf_timestamp, // 必填,生成签名的时间戳
nonceStr: config.conf_nonceStr, // 必填,生成签名的随机串
signature: config.conf_signature,// 必填,签名,见附录1
jsApiList: ['agentConfig', 'thirdPartyOpenPage', 'selectExternalContact'] // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
});
wx.ready(function () {
wx.agentConfig({
corpid: config.appID, // 必填,企业微信的corpid,必须与当前登录的企业一致
agentid: config.agentID, // 必填,企业微信的应用id
timestamp: config.agent_timestamp, // 必填,生成签名的时间戳
nonceStr: config.agent_nonceStr, // 必填,生成签名的随机串
signature: config.agent_signature,// 必填,签名,见附录1
jsApiList: ['thirdPartyOpenPage', 'selectExternalContact'], //必填
success: function (res) {
wx.invoke('thirdPartyOpenPage', {
"oaType": config.oaType,// String
"templateId": config.templateId,// String
"thirdNo": orderno,// String 这里使用订单号当做审批单号,以此关联业务数据,后面的回调函数可以获取到审批单号
"extData": {
'fieldList': [{
'title': '订单编号',
'type': 'text',
'value': orderno,
}],
}
},
function (res) {
// 输出接口的回调信息
console.log(res);
});
},
fail: function (res) {
if (res.errMsg.indexOf('function not exist') > -1) {
alert('版本过低请升级')
}
}
});
})
})) ;
}
function weixinInit() {
var wxData = null;
$.ajax({
url: basePath + "/woWork/getWeixinConfig?url="
+ encodeURIComponent(location.href.split('#')[0]),
dataType: 'json',
type: 'POST',
async: false,
success: function (data) {
wxData = data;
},
});
return wxData;
}
后端代码:
//获取微信配置
@RequestMapping(value = "/getWeixinConfig")
@ResponseBody
public String getWeixinConfig(Model model, HttpSession session,
HttpServletRequest request, HttpServletResponse response,String inputcontent) {
String url = request.getParameter("url");
if(StringUtils.isNotBlank(inputcontent)){
url += "&inputcontent="+inputcontent;
}
String pk_workerAdjust = request.getParameter("pk_workerAdjust") ;
if(!StringUtils.isEmpty(pk_workerAdjust)){
url += "&pk_workerAdjust="+pk_workerAdjust ;
}
//获取access_token
String accessToken = WeiXinUtil.getAccessTokenCache(WeiXinParamesUtil.corpId, WeiXinParamesUtil.agentSecret);
// 获取企业jsapi_ticket
String conf_ticket = WeiXinUtil.getConfJsapiTicket(accessToken);
Map<String, String> confSign = sign(conf_ticket, url, WeiXinParamesUtil.corpId);
// 获取应用jsapi_ticket
String agent_ticket = WeiXinUtil.getAgentJsapiTicket(accessToken);
Map<String, String> agentSign = sign(agent_ticket, url, WeiXinParamesUtil.corpId);
Map<String, String> ret = new HashMap<String, String>();
ret.put("appID", WeiXinParamesUtil.corpId);
ret.put("conf_jsapi_ticket", conf_ticket);
ret.put("agent_jsapi_ticket", agent_ticket);
ret.put("conf_nonceStr", confSign.get("nonceStr").toString());
ret.put("agent_nonceStr", agentSign.get("nonceStr").toString());
ret.put("conf_timestamp", confSign.get("timestamp").toString());
ret.put("agent_timestamp", agentSign.get("timestamp").toString());
ret.put("conf_signature", confSign.get("signature").toString());
ret.put("agent_signature", agentSign.get("signature").toString());
ret.put("agentID", String.valueOf(WeiXinParamesUtil.agentId));
ret.put("oaType", "10001");
ret.put("templateId", "0e45df152be465149ef19119902dbd9a_1546772386");
String result = JSONObject.fromObject(ret).toString();
return result;
}
获取access_token:
public final static String access_token_url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpId}&corpsecret={corpsecret}";
public final static String jsapi_ticket_url_conf = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESSTOKEN";
public final static String jsapi_ticket_url_agent = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=ACCESSTOKEN&type=agent_config";
/**
* 3.获取access_token
*
* @param appid 凭证
* @param appsecret 密钥
* @return
*/
public static String getAccessToken(String appid, String appsecret) {
String access_token = "";
String requestUrl = access_token_url.replace("{corpId}", appid).replace("{corpsecret}", appsecret);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
// 如果请求成功
if (null != jsonObject) {
try {
access_token = jsonObject.getString("access_token");
} catch (JSONException e) {
// 获取token失败
log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
return access_token;
}
获取接口JsapiTicket:
public static String getConfJsapiTicket(String accessToken){
String requestUrl = jsapi_ticket_url_conf.replace("ACCESSTOKEN", accessToken);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
String jsapi_ticket="";
// 如果请求成功
if (null != jsonObject) {
try {
jsapi_ticket=jsonObject.getString("ticket");
} catch (JSONException e) {
// 获取token失败
log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
return jsapi_ticket;
}
获取应用JsapiTicket:
public static String getAgentJsapiTicket(String accessToken){
String requestUrl = jsapi_ticket_url_agent.replace("ACCESSTOKEN", accessToken);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
String jsapi_ticket="";
// 如果请求成功
if (null != jsonObject) {
try {
jsapi_ticket=jsonObject.getString("ticket");
} catch (JSONException e) {
// 获取token失败
log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
}
}
return jsapi_ticket;
}
获取签名:
public static Map<String, String> sign(String jsapi_ticket, String url , String corpId) {
Map<String, String> ret = new HashMap<String, String>();
String nonce_str = create_nonce_str();
String timestamp = create_timestamp();
String string1;
String signature = "";
// 注意这里参数名必须全部小写,且必须有序
string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str
+ "×tamp=" + timestamp + "&url=" + url;
System.out.println(string1);
try {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(string1.getBytes("UTF-8"));
signature = byteToHex(crypt.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
ret.put("appID", corpId);
ret.put("url", url);
ret.put("jsapi_ticket", jsapi_ticket);
ret.put("nonceStr", nonce_str);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
return ret;
}
private static String create_nonce_str() {
return UUID.randomUUID().toString();
}
private static String create_timestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
4、自建应用审批状态变化通知回调
在管理后台-自建应用-设置API接收中,设置并开启审批状态通知事件


这里需要对URL有效性进行验证
当点击“保存”提交以上信息时,企业微信会发送一条验证消息到填写的URL,发送方法为GET。
首次保存只是对接口有效性就行验证,后续的消息通知也通过本接口进行推送,发送方法为POST。
下载官方提供的解密工具类:
https://work.weixin.qq.com/api/doc/90000/90138/90307
验证方法:
//审核接口验证
@RequestMapping(value="/check2",method = RequestMethod.GET)
public static void check2(HttpServletRequest request, HttpServletResponse response) throws Exception{
//当你提交以上信息时,企业号将发送GET请求到填写的URL上,GET请求携带四个参数,企业在获取时需要做urldecode处理,否则会验证不成功
// 微信加密签名
String msg_signature = request.getParameter("msg_signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echoStr = request.getParameter("echostr");
//回调key值
String sEchoStr = null;
String contacts_token = "approvetest";
String contacts_encodingaeskey = "vzNsG28E3YBY0b4ot4rOE3oOH37eJRkgcphCEYprynR";
String corpId = "ww2c03eb2321d8d24c";
try {
PrintWriter out = response.getWriter();
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(contacts_token,contacts_encodingaeskey,corpId);
sEchoStr = wxcpt.VerifyURL(msg_signature,timestamp,nonce,echoStr);
if(StringUtils.isBlank(sEchoStr)){
System.err.print("URL验证失败");
}
out.write(sEchoStr);
out.flush();
}catch (Exception e){
System.err.print("企业微信回调url验证错误"+e);
}
}
真正接收审批消息推送的方法:
//审核接口回调
@RequestMapping(value="/check2",method = RequestMethod.POST)
public void callback(HttpServletRequest request, HttpServletResponse response) throws Exception{
//当你提交以上信息时,企业号将发送GET请求到填写的URL上,GET请求携带四个参数,企业在获取时需要做urldecode处理,否则会验证不成功
// 微信加密签名
String msg_signature = request.getParameter("msg_signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echoStr = request.getParameter("echostr");
//回调key值
String sMsg = null;
String contacts_token = "approvetest";
String contacts_encodingaeskey = "vzNsG28E3YBY0b4ot4rOE3oOH37eJRkgcphCEYprynR";
String corpId = "ww2c03eb2321d8d24c";
try{
InputStream inputStream = request.getInputStream();
String sPostData = IOUtils.toString(inputStream,"UTF-8");
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(contacts_token,contacts_encodingaeskey,corpId);
sMsg = wxcpt.DecryptMsg(msg_signature,timestamp,nonce,sPostData);
//将post数据转换为map
Map<String,Object> dataMap = createMapByXml(sMsg);
JSONObject jsonObject = JSONObject.fromObject(dataMap);
String msgType = JSONObject.fromObject(jsonObject.get("xml")).get("MsgType").toString();
if(msgType.equals("event")){
String eventType = JSONObject.fromObject(jsonObject.get("xml")).get("Event").toString();
//审批状态回调通知
if(eventType.equals("open_approval_change")){
String openSpStatus = JSONObject.fromObject(JSONObject.fromObject(jsonObject.get("xml")).get("ApprovalInfo")).get("OpenSpStatus").toString();
if("2".equals(openSpStatus)){
//审批单号
String thirdNo = JSONObject.fromObject(JSONObject.fromObject(jsonObject.get("xml")).get("ApprovalInfo")).get("ThirdNo").toString();
//提交人
String ApplyUserId = JSONObject.fromObject(JSONObject.fromObject(jsonObject.get("xml")).get("ApprovalInfo")).get("ApplyUserId").toString();
//可以修改业务数据状态
workorderService.updateApprove(thirdNo);
}
}
}
}catch (Exception e){
e.printStackTrace();
System.err.print("企业微信消息交互错误"+e);
}
}
XML 解析:
使用dom4j解析,网上找的方法
public static Map<String, Object> createMapByXml(String xml) {
Document doc = null;
try {
doc = DocumentHelper.parseText(xml);
} catch (DocumentException e) {
e.printStackTrace();
}
Map<String, Object> map = new HashMap<String, Object>();
if (doc == null)
return map;
Element rootElement = doc.getRootElement();
elementTomap(rootElement, map);
return map;
}
/***
* XmlToMap核心方法,里面有递归调用
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> elementTomap(Element outele,
Map<String, Object> outmap) {
List<Element> list = outele.elements();
int size = list.size();
if (size == 0) {
outmap.put(outele.getName(), outele.getTextTrim());
} else {
Map<String, Object> innermap = new HashMap<String, Object>();
for (Element ele1 : list) {
String eleName = ele1.getName();
Object obj = innermap.get(eleName);
if (obj == null) {
elementTomap(ele1, innermap);
} else {
if (obj instanceof java.util.Map) {
List<Map<String, Object>> list1 = new ArrayList<Map<String, Object>>();
list1.add((Map<String, Object>) innermap
.remove(eleName));
elementTomap(ele1, innermap);
list1.add((Map<String, Object>) innermap
.remove(eleName));
innermap.put(eleName, list1);
} else {
elementTomap(ele1, innermap);
((List<Map<String, Object>>) obj).add(innermap);
}
}
}
outmap.put(outele.getName(), innermap);
}
return outmap;
}
这样就可以了,自己测试了下没有问题。