企业微信自建应用审批流程开发

4,541 阅读6分钟

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
			+ "&timestamp=" + 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;
}

这样就可以了,自己测试了下没有问题。