企业微信开发实战:自建审批流引擎

2023-05-16

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测试,必须让项目处于公网可信域名下。笔者是通过内网穿透到自己买的域名上,具体可以自己学习研究。
同时在工作台配置
在这里插入图片描述
在企业微信上打开前端页面
在这里插入图片描述
在这里插入图片描述
点击提交

后台回调到审批信息回传信息
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

企业微信开发实战:自建审批流引擎 的相关文章

  • 三维重建了解

    一 三维重建方法 1 1 传统方法 RGBD D来源结构光或者TOF xff1a 缺点 xff0c 重建范围受限 xff0c 一般不能重建大模型 xff1b 比如 xff0c kinectFusion xff0c DynamicFusion
  • docker容器常用命令

    一 常用命令 显示本地镜像 xff1a docker images 显示已经启动的容器 xff1a docker ps a 从docker hub拉取镜像 reed98 airsim v0是镜像名 xff1a docker pull ree
  • ARM学习随笔(12)定时器查询方式和中断方式

    定时器详细讲解 百度文库 点击打开链接 xff08 一 xff09 查询方式和中断方式的区别在于 xff1a 查询方式不断查询标志位然后进行处理 xff0c 而中断要编写中断服务子程序来处理中断事件 xff08 二 xff09 内部中断是指
  • vgg16网络裁剪并加载模型参数

    主要是测试下模型裁剪后转onnx的问题 删除vgg16网络全连接层 xff0c 加载预训练模型并重新保存模型参数 xff0c 将该参数用于转onnx模型格式 usr bin env python coding utf 8 64 Time 2
  • pth转onnx的三种情况

    usr bin env python coding utf 8 64 Time 2022 8 3 16 19 64 Author weiz 64 ProjectName cbir 64 File pth2onnx py 64 Descrip
  • 以vgg为backbone的简易图像检索系统

    图像检索 xff08 Content based Image Retrieval xff0c 简称CBIR xff09 即以图搜图 xff0c 基于图片语义信息 xff0c 诸如颜色 纹理 布局 CNN based高层语义等特征检索技术 该
  • img2pose: Face Alignment and Detection via 6DoF, Face Pose Estimation代码理解

    import argparse import os import sys import time import numpy as np from PIL import Image ImageOps from torchvision impo
  • 解决普通用户使用sudo找不到命令

    sudo bazel build c opt define MEDIAPIPE DISABLE GPU 61 1 mediapipe examples desktop face mesh face mesh cpu 出现 xff1a sud
  • sfm算法之三角化(三角测量)

    sfm算法流程一般是特征点提取 特征点匹配 计算本质矩阵 基础矩阵 xff0c 最后三角化 但是利用机械臂去观察周围 xff0c 前后帧姿态变化参数是具有的 xff0c 所以不需要通过基础矩阵获取 即利用机械臂的信息直接进行深度估计 已知
  • bazel构建项目案例(第三方库,编译成库,运行案例)

    使用bazel构建项目 xff0c 包含如何引入外部库 xff08 项目中引入了opencv和编译的tensorflow lite库 xff09 xff0c 如何编译成动态库和静态库 xff0c 以及如何调用编译好的库 项目根目录的所有文件
  • 各种小功能集二

    各种小功能集一 十一 C C 43 43 路径解析 头文件 std string UtilsGetPath const char pszFilename std string UtilsGetDirname const char pszFi
  • windows10配置paddleOCR的CPU版本总结

    paddleOCR的CPU版本依赖的库还是比较少的 如下 1 opencv库 本人配置的版本是opencv4 5 0 2 paddle inference 推理库 该库解压后有version txt文件 xff0c 版本信息如下 xff1a
  • 传统图像技术的边缘提取

    usr bin env python coding utf 8 import cv2 import os import numpy as np def laplacian img ksize 61 3 laplacian 61 cv2 La
  • TCP-UDP网络编程调试助手下载

    下载地址 xff1a 可能需要谷歌 xff1a 软件干净 xff0c 挺好用的 xff0c 如果有更好的 xff0c 欢迎留言 xff01 https www waveshare com wiki File TCP UDP Debug 7z
  • Data Matrix码的使用

    一 引言 Data Matrix原名Data code xff0c 由美国国际资料公司 International Data Matrix 简称ID Matrix 于1989年发明 Data Matrix又可分为ECC000 140与ECC
  • 小样本学习(Few-Shot Learning)训练参数意义

    一 常规参数 1 1 epoch 是指所有的训练数据都要跑一遍 假设有6400个样本 xff0c 在训练过程中 xff0c 这6400个样本都跑完了才算一个epoch 一般实验需要训练很多个epoch xff0c 直到LOSS稳定后才停止
  • 不同相机之间图片像素对应关系求解(单应性矩阵求解)

    一 场景 相机1和相机2相对位置不变 xff0c 相机拍摄图片有重叠 xff0c 求他们交叠部分的一一对应关系 数学语言描述为已知相机1图片中P点像素 u1 v1 xff0c 相机1中P点在相机2图片中像素值为 u2 v2 xff0c 它们
  • python协程学习

    一 什么是协程及实现方式 1 1 协程 又称微线程 xff0c 纤程 也称为用户级线程 xff0c 在不开辟线程的基础上完成多任务 xff0c 也就是在单线程的情况下完成多任务 xff0c 多个任务按照一定顺序交替执行 1 2 实现方式 g
  • 无法打开文件libboost_random-vc141-mt-s-x64-1_81.lib

    踩坑过程 xff1a 需要使用websocketpp工具 xff0c 得先安装boost 安装boost及websocketpp过程简单且顺利 xff0c 并且能正常能正常运行官方例程 把官方例程简单封装测试 xff0c 一切皆好 等把we

随机推荐