企业微信开发实战:自建审批流引擎
1.概述企业微信上是这样介绍的。不过经本人的研究测试,该工作流引擎的功能是比较有限的。首先只有移动端才能发起,流程的定义是必须在企业微信控制台中定义,而且不支持条件分支,适用于比较简单的应用场景,请假之类的。而且审批界面数据展示自定义程度很低。2.企业微信开发基础文档链接:https://work.weixin.qq.com/api/doc#90000/90135/90665...
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测试,必须让项目处于公网可信域名下。笔者是通过内网穿透到自己买的域名上,具体可以自己学习研究。
同时在工作台配置
在企业微信上打开前端页面
点击提交
后台回调到审批信息回传信息
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)