1.概述

å¨è¿éæå¥å¾çæè¿°

企业微信上是这样介绍的。不过经本人的研究测试,该工作流引擎的功能是比较有限的。

首先只有移动端才能发起,流程的定义是必须在企业微信控制台中定义,而且不支持条件分支,适用于比较简单的应用场景,请假之类的。而且审批界面数据展示自定义程度很低。

2.企业微信开发基础

文档链接:
https://work.weixin.qq.com/api/doc#90000/90135/90665
corpid
每个企业都拥有唯一的corpid,获取此信息可在管理后台“我的企业”-“企业信息”下查看“企业ID”(需要有管理员权限)
agentid
每个应用都有唯一的agentid。在管理后台->“应用与小程序”->“应用”,点进某个应用,即可看到agentid。
secret
secret是企业应用里面用于保障数据安全的“钥匙”,每一个应用都有一个独立的访问密钥,为了保证数据的安全,secret务必不能泄漏。
access_token
access_token是企业后台去企业微信的后台获取信息时的重要票据,由corpid和secret产生。所有接口在通信时都需要携带此信息用于验证接口的访问权限

3.审批流引擎开发

文档链接
https://work.weixin.qq.com/api/doc#90000/90135/90269

1.创建自建应用审批模板

å¨è¿éæå¥å¾çæè¿°

2.前端调用页面

1.通过config接口注入权限验证配置。查看
2.通过agentConfig注入应用的权限。查看
3.调用审批流程引擎JS-API(如下文请求示例)。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>菜鸟教程(runoob.com)</title>
    <script src="js/jquery.min.js">
    </script>

    <script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js">
    </script>

    <script>
        $(document).ready(function(){

            var link = window.location.href;

            $("button").click(function(){

                $.ajax({
                    //请求方式
                    type : "GET",
                    //请求的媒体类型
                    contentType: "application/json;charset=UTF-8",
                    //请求地址
                    url : "http://zdwtest.nat300.top/weichat-config/getSignature",
                    //数据,json字符串
                    data : {
                        "url":link
                    },
                    //请求成功
                    success : function(res) {
                        // console.log(data);
                        wx.config({
                            beta: true,
                            debug: true,
                            appId: res.data.corpId,
                            timestamp: res.data.timestamp,
                            nonceStr: res.data.noncestr,
                            signature: res.data.signature,
                            jsApiList: ['agentConfig','openUserProfile','thirdPartyOpenPage','selectExternalContact']
                        });

                        wx.ready(function(){

                            wx.agentConfig({
                                corpid: res.data.corpId, // 必填,企业微信的corpid,必须与当前登录的企业一致
                                agentid: res.data.agentId, // 必填,企业微信的应用id
                                timestamp: res.data.timestamp, // 必填,生成签名的时间戳
                                nonceStr: res.data.noncestr, // 必填,生成签名的随机串
                                signature: res.data.agentSignature,// 必填,签名,见附录1
                                jsApiList: ['agentConfig','openUserProfile','thirdPartyOpenPage','selectExternalContact'], //必填
                                success: function(res) {
                                    // 发起审批流程
                                    wx.invoke('thirdPartyOpenPage', {
                                            "oaType": "10001",// String
                                            "templateId": "e38e4df283a991b00e3394dc71fbef79_1837057289",// String
                                            "thirdNo": "t01",// 审批单号,开发者自己控制,不可重复
                                            "extData": {
                                                'fieldList': [{
                                                    'title': '采购类型',
                                                    'type': 'text',
                                                    'value': '市场活动',
                                                },{
                                                    'title': '采购类型',
                                                    'type': 'text',
                                                    'value': '市场活动',
                                                },{
                                                    'title': '采购类型',
                                                    'type': 'text',
                                                    'value': '市场活动',
                                                },{
                                                    'title': '采购类型',
                                                    'type': 'text',
                                                    'value': '市场活动',
                                                },{
                                                    'title': '采购类型',
                                                    'type': 'text',
                                                    'value': '市场活动',
                                                }],
                                            }
                                        },
                                        function(res) {
                                            // 输出接口的回调信息
                                            console.log(res);
                                        });
                                },
                                fail: function(res) {
                                    if(res.errMsg.indexOf('function not exist') > -1){
                                        alert('版本过低请升级')
                                    }
                                }
                            });
                        })
                    }
                });

            });
        });
    </script>
</head>

<body>
<button>点我</button>
</body>
</html>

注意:调试前端页面必须用企业微信客户端,pc版均不支持。

3.获取签名的后台接口

@Api(value="企业微信相关信息获取",description = "企业微信相关信息获取")
@RequestMapping("/weichat-config")
@Controller
public class WeiChatConfigController {
    @Autowired
    ApprovalPrecessService approvalPrecessService;

    @ApiOperation(value = "获取前端登录时需要的签名信息",notes = "获取前端登录时需要的签名信息",httpMethod = "GET")
    @RequestMapping(value = "/getSignature",method = RequestMethod.GET)
    @ResponseBody
    public RestResult<WeixinConfigDTO> getTWeixinConfig(String url){
        WeixinConfigDTO tWeixinConfig = approvalPrecessService.getTweixinConfig(url);
        return new RestResult(tWeixinConfig);
    }
}
public class ApprovalProcessServiceImpl implements ApprovalPrecessService {
//    private static log log = log.getlog(ApprovalProcessServiceImpl.class);
//    private static PropertiesFileUtil propertiesFileUtil = new  PropertiesFileUtil();
//	@Autowired
//	TWeixinConfigMapper tWeixinConfigMapper;

	@Autowired
	private WeichatService weichatService;

	@Override
	public String getSignature(String jsTickt,String noncestr,String timestamp,String url) throws Exception {
				String str = "jsapi_ticket="+jsTickt+"&"+"noncestr="+noncestr+"&"+"timestamp="+timestamp+"&"+"url="+url;
		        return DigestUtils.sha1Hex(str);
	}

	@Override
	public WeixinConfigDTO getTweixinConfig(String url){
		WeixinConfigDTO weixinConfigDTO = new WeixinConfigDTO();
		try {
			String agentId = WeiChatConfigConstants.QIYEWEICHAT_AGENTID;
			String corpId = WeiChatConfigConstants.QIYEWEICHAT_CORPID;
			String secret = WeiChatConfigConstants.QIYEWEICHAT_CORPSECRET;
			String accessToken = weichatService.getWeiChatAccessTokern();
			//企业的jsTickt
			String jsTickt  = CallWxUtil.getJsTicket(accessToken);
			//应用的jsTickt
			String agentJsTickt = CallWxUtil.getAgentJsTicket(accessToken);
			String timestamp=String.valueOf(System.currentTimeMillis());
			String noncestr =getRandomString(16);
			String userUrl=url;
			String signature  = getSignature(jsTickt, noncestr, timestamp, userUrl);
			String agentSignature = getSignature(agentJsTickt, noncestr, timestamp, userUrl);
			weixinConfigDTO.setCorpId(corpId);
			weixinConfigDTO.setAgentId(agentId);
			weixinConfigDTO.setCorpSecret(secret);
			weixinConfigDTO.setSignature(signature);
			weixinConfigDTO.setAgentSignature(agentSignature);
			weixinConfigDTO.setNoncestr(noncestr);
			weixinConfigDTO.setJsapiTicket(jsTickt);
			weixinConfigDTO.setTimestamp(Long.valueOf(timestamp));
			weixinConfigDTO.setAccessToken(accessToken);
		}catch (Exception e){
			log.info(e.getMessage());
			log.info("获取签名异常!!");
		}
		return  weixinConfigDTO;
	}
	
	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();
	}
}

获取jsticket 参考文档
https://work.weixin.qq.com/api/doc#10029/获取应用的jsapi_ticket

其中jsticket是签名的重要参数,和access_token一样具有时效性,建议缓存处理。

4.接受审批回调信息的接口
文档参考:https://work.weixin.qq.com/api/doc#90000/90135/90930
首先在控制台配置url为介绍审批回调信息的接口,然后随机生成token,encodingAESkey,建议生成后固定。
å¨è¿éæå¥å¾çæè¿°

企业微信首先会对回调接口进行校验,成功之后便会推送回调信息。
这里GET 为检验接口,
1.对收到的请求,解析上述的各个参数值(参数值需要做Urldecode处理)
2.根据已有的token,结合第1步获取的参数timestamp, nonce, echostr重新计算签名,然后与参数msg_signature检查是否一致,确认调用者的合法性。计算方法参考:消息体签名检验
3.解密echostr参数得到消息内容(即msg字段)
4.在1秒内响应GET请求,响应内容为上一步得到的明文消息内容(不能加引号,不能带bom头,不能带换行符)
POST为推送接口
1.对msg_signature进行校验
2.解密Encrypt,得到明文的消息结构体(消息结构体后面章节会详说)
3.如果需要被动回复消息,构造被动响应包
4.正确响应本次请求
· 企业微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
· 当接收成功后,http头部返回200表示接收ok,其他错误码企业微信后台会一律当做失败并发起重试

@Controller
@RequestMapping("/weichat-callBack")
public class WeiChatCallbackController {

    @Autowired
    private WeiChatCallbackService weiChatCallbackService;


    //第一次get请求验证
    @GetMapping(value = "/callBack")
    @ResponseBody
    public String verifyURL(String msg_signature, String timestamp, String nonce, String echostr) {
        return weiChatCallbackService.verifyURL(msg_signature,timestamp,nonce,echostr);
    }

    //接受post请求消息推送
    @PostMapping("/callBack")
    @ResponseBody
    public void callBack(HttpServletRequest request, String msg_signature, String timestamp, String nonce)  {
        weiChatCallbackService.doCallBack(request, msg_signature, timestamp, nonce);
    }
    
}
public class WeiChatCallbackServiceImpl implements WeiChatCallbackService {

    private WXBizMsgCrypt getWXBizMsgCrypt() throws AesException {
        String sToken = WeiChatConfigConstants.QIYEWEICHAT_MSGTOKEN;
        String sCorpID = WeiChatConfigConstants.QIYEWEICHAT_CORPID;
        String sEncodingAESKey = WeiChatConfigConstants.QIYEWEICHAT_ENCODINGAESKEY;
        return new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
    }


    @Override
    public String verifyURL(String msg_signature, String timestamp, String nonce, String echostr) {
        try {
            WXBizMsgCrypt wxcpt = getWXBizMsgCrypt();
            String sEchoStr; //需要返回的明文
            sEchoStr = wxcpt.VerifyURL(msg_signature, timestamp,
                    nonce, echostr);
            return sEchoStr;
        } catch (Exception e) {
            //验证URL失败,错误原因请查看异常
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return "无";
    }

    @Override
    public void doCallBack(HttpServletRequest request, String msg_signature, String timestamp, String nonce) {

        // post请求的密文数据
        try {
            WXBizMsgCrypt wxcpt = getWXBizMsgCrypt();

            InputStream inputStream = request.getInputStream();
            String sPostData = IOUtils.toString(inputStream,"UTF-8");
            String sMsg = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, sPostData);
            System.out.println("after decrypt msg: " + sMsg);
            // TODO: 解析出明文xml标签的内容进行处理
            // For example:
            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();

            //获取消息类型
            String msgType = getContentByTagName(root,"MsgType");
            System.out.println("MsgType:" + msgType);
            //处理文本信息
            if (WeiChatMsgTypeEnum.TEXT.getValueInFact().equals(msgType)){
                dealWeiChatTextMsg(root);
                //处理事件信息
            }else if (WeiChatMsgTypeEnum.EVENT.getValueInFact().equals(msgType)){
                dealWeiChatEventMsg(root);
            }
        } catch (Exception e) {
            // TODO
            // 解密失败,失败原因请查看异常
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }

    private void dealWeiChatTextMsg(Element root) {
            String content = getContentByTagName(root,"Content");
            System.out.println("Content:" + content);

            content = getContentByTagName(root,"FromUserName");
            System.out.println("FromUserName:" + content);
    }

    private void dealWeiChatEventMsg(Element root) {
        String content = getContentByTagName(root,"OpenSpName");
        System.out.println("审批模板名称:" + content);

        content = getContentByTagName(root,"ApplyUserName");
        System.out.println("提交人姓名:" + content);

        content = getContentByTagName(root,"NodeStatus");
        System.out.println("节点审批状态:" + content);

    }

    private String getContentByTagName(Element root, String tagName){
        NodeList nodelist = root.getElementsByTagName(tagName);
        String Content = nodelist.item(0).getTextContent();
        return Content;
    }

}

解密,签名的工具类可参考官方的demo
https://work.weixin.qq.com/api/doc#90000/90138/90307
下载java 版即可

4.效果测试
这里使用移动端app测试,必须让项目处于公网可信域名下。笔者是通过内网穿透到自己买的域名上,具体可以自己学习研究。
同时在工作台配置
å¨è¿éæå¥å¾çæè¿°

在企业微信上打开前端页面

å¨è¿éæå¥å¾çæè¿°         å¨è¿éæå¥å¾çæè¿°

点击提交

后台回调到审批信息回传信息

å¨è¿éæå¥å¾çæè¿°

 

 

 

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐