文章目录
- 4.审批申请状态变化回调通知
- 4.1设置接收事件服务器
- 4.1.1 设置入口
- 4.2 配置说明
- 5.批量获取审批单号
- 5.1概述
- 5.2代码实战
- 5.3试错
- 6.获取审批申请详情
- 6.1概述
- 6.2代码实战
- 6.3试错
- 源码
- 赞赏
4.审批申请状态变化回调通知
订阅后,当企业内指定类型的企业微信“审批应用”单据流程发生变化时,会将审批单最新的流程状态回调给开发者。
4.1设置接收事件服务器
4.1.1 设置入口
目前,支持将审批状态变化通知回调给审批应用、指定的自建应用。
1、审批应用回调地址设置
- 企业可以在【管理后台->应用和小程序->审批->API->接收事件服务器】中,设置回调地址,并勾选需要进行状态变化回调通知的审批模板类型。
- 设置后,回调地址可接收审批应用所有允许进行回调的审批申请相关状态变化通知。
2、自建应用回调地址设置
- 企业可在【管理后台->应用管理->审批->API->可调用应用】中,设置可调用审批相关接口的自建应用。
- 设置后,回调地址可接收自建应用可见范围内成员提交的、审批应用允许进行回调的审批申请相关状态变化通知。
3、注意:只有在【审批->API->可调用应用】中勾选了的自建应用才可以接收到在【审批->API->接收事件服务器】勾选模板相关的审批申请回调通知。
4.2 配置说明
1、参数设置
进入配置页面,要求填写URL、Token、EncodingAESKey三个参数。
- URL是企业后台接收企业微信推送请求的访问协议和地址,支持http或https协议(建议使用https)。
- Token可由企业任意填写,用于生成签名。
- EncodingAESKey用于消息体的加密,是AES密钥的Base64编码。
2、其他配置
- 在审批应用“接收事件服务器”,可设置允许进行回调通知的模板类型,此配置将同样作用于自建应用与第三方应用的回调通知。
- 在自建应用中,可通过在【应用详情-接收消息-设置API接收】中开关“审批状态通知事件”,来控制是否接收相关回调。
(1)按下图所示步骤打开"配置回调地址"界面:
(2)打开utools工具(安装utools工具),搜索"内网穿透",如下图所示将本机的5000端口通过"你的自定义四级域名.cn.utools.club"暴露到外网,此时其他人就可以通过你配置的域名访问到你本机跑的netcore项目。
(3)接收消息服务器配置如下,其中url指的是你代码中Callback地址,记得要将localhost改为内网穿透后的地址;token和EncodingAESKey都可自定义,也可通过"随机获取"按钮随机生成,后面代码中需要用到。
(4)具体代码如下:
- 官方提供的加解密类
- 官方回调解析
- 官方在线回调调试
-
Callback
接口是事件回调接收接口。 -
Verify
方法是校验信息的有消息,在页面上配置接口,点击"保存"时,企业微信会调用该接口,校验接口是否能正常解析sEchoStr
并返回。 -
DecryptMsg
方法是对用户回复的消息解密,会在控制台中输出企业微信推送过来的xml信息。
#region 4、审批申请状态变化回调通知
[Route("Callback")]
public Task<string> Callback(CallbackModel model)
{
//1、校验回调是否成功
return Verify(model);
//2、解析回调的数据
//return DecryptMsg(model);
}
/// <summary>
/// 1、校验信息有效性
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
private string Verify(CallbackModel model)
{
//调用官方解析方法
Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt("你的回调token", "你的EncodingAESKey", "你的企业id");
// string sVerifyMsgSig = HttpUtils.ParseUrl("msg_signature");
//获取url中的msg_signature参数值
string sVerifyMsgSig = model.msg_signature;
// string sVerifyTimeStamp = HttpUtils.ParseUrl("timestamp");
//获取url中的timestamp参数值
string sVerifyTimeStamp = model.timestamp;
// string sVerifyNonce = HttpUtils.ParseUrl("nonce");
//获取url中的nonce参数值
string sVerifyNonce = model.nonce;
// string sVerifyEchoStr = HttpUtils.ParseUrl("echostr");
//获取url中的echostr参数值
string sVerifyEchoStr = model.echostr;
int ret = 0;
string sEchoStr = "";
//校验数据的有消息,如果有效则返回0
ret = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, sVerifyEchoStr, ref sEchoStr);
if (ret != 0)
{
System.Console.WriteLine("ERR: VerifyURL fail, ret: " + ret);
}
return sEchoStr;
}
/// <summary>
/// 2、对用户回复的消息解密(解析并输出xml)
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
private async Task<string> DecryptMsg(CallbackModel model)
{
//获取请求体
var context = HttpContext.Request.Body;
int ret = 0;
Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt("你的回调token", "你的EncodingAESKey", "你的企业id");
// string sReqMsgSig = HttpUtils.ParseUrl("msg_signature");
//获取url中的msg_signature参数值
string sReqMsgSig = model.msg_signature;
// string sReqTimeStamp = HttpUtils.ParseUrl("timestamp");
//获取url中的timestamp参数值
string sReqTimeStamp = model.timestamp;
// string sReqNonce = HttpUtils.ParseUrl("nonce");
//获取url中的nonce参数值
string sReqNonce = model.nonce;
// Post请求的密文数据
// string sReqData = HttpUtils.PostData();
string sReqData = "";
//将请求体中的内容通过流的方式输出到字符串sReqData中
using (var reader = new StreamReader(Request.Body))
{
sReqData = await reader.ReadToEndAsync();
}
//string sReqData = "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt><AgentID><![CDATA[218]]></AgentID></xml>";
string sMsg = ""; // 解析之后的明文
ret = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData, ref sMsg);
if (ret != 0)
{
System.Console.WriteLine("ERR: Decrypt Fail, ret: " + ret);
return "";
}
// ret==0表示解密成功,sMsg表示解密之后的明文xml串
// TODO: 对明文的处理
// For example:
XmlDocument doc = new XmlDocument();
doc.LoadXml(sMsg);
//XmlNode root = doc.FirstChild;
//string content = root["Content"].InnerText;
//System.Console.WriteLine(content);
//XmlNode root = doc.FirstChild;
//string content = root.InnerText;
//控制台打印请求体中获取到的内容
System.Console.WriteLine(sMsg);
return sMsg;
}
#endregion
(5)在项目未运行时或者设置不存在的回调接口时点击"保存",会看到提示"openapi回调地址请求不通过"的错误信息。
(6)注释掉"return DecryptMsg(model)",取消"return Verify(model)"的注释,运行该netcore项目,并开启内网穿透工具。
(7)Verify
方法返回的sEchoStr
参数是官方调用我们的回调接口时存放在加密数据中的信息,只有解析成功,并将该sEchoStr
参数返回,界面上才能看到"保存成功"的提示(在线回调调试),如下图:
(8)注释掉"return DecryptMsg(model)",取消"return Verify(model)"的注释,运行该netcore项目,并开启内网穿透工具,并在客户端中发起一条申请,如下图所示:
(9)此时命令行中会输出上一步中发起的"请假"申请,如下所示(xml文档属性解析):
<?xml version="1.0" encoding="utf-8"?>
<xml>
<ToUserName><![CDATA[ww94e74526d29a98c4]]></ToUserName>
<FromUserName><![CDATA[sys]]></FromUserName>
<CreateTime>1619146833</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[sys_approval_change]]></Event>
<AgentID>3010040</AgentID>
<ApprovalInfo>
<SpNo>202104230001</SpNo>
<SpName><![CDATA[请假]]></SpName>
<SpStatus>1</SpStatus>
<TemplateId><![CDATA[3TmmUh7YqD7QMLPZWKJFP8bvanYgmndV6eqbq84n]]></TemplateId>
<ApplyTime>1619146832</ApplyTime>
<Applyer>
<UserId><![CDATA[XieGuoYong]]></UserId>
<Party><![CDATA[1]]></Party>
</Applyer>
<SpRecord>
<SpStatus>1</SpStatus>
<ApproverAttr>1</ApproverAttr>
<Details>
<Approver>
<UserId><![CDATA[a7a885f027068925d1609a726fb945a6]]></UserId>
</Approver>
<Speech><![CDATA[]]></Speech>
<SpStatus>1</SpStatus>
<SpTime>0</SpTime>
</Details>
</SpRecord>
<StatuChangeEvent>1</StatuChangeEvent>
</ApprovalInfo>
</xml>
(10)到这一步就完成了"审批申请状态变化回调通知"的配置与代码实现了,后续需要入库操作,请自行解析xml,并进行持久化操作(注:后面有空的话也可能会分享我入库的版本出来,如果看的人多且我有空的话!)。
5.批量获取审批单号
5.1概述
1、调试工具地址:调试工具
2、审批应用及有权限的自建应用,可通过Secret调用本接口,以获取企业一段时间内企业微信“审批应用”单据的审批编号,支持按模板类型、申请人、部门、申请单审批状态等条件筛选。
自建应用调用此接口,需在“管理后台-应用管理-审批-API-审批数据权限”中,授权应用允许提交审批单据。
3、一次拉取调用最多拉取100个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。
推荐使用此接口获取审批数据,旧接口后续将不再维护。
**请求方式:**POST(HTTPS)
**请求地址:**https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovalinfo?access_token=ACCESS_TOKEN
请求示例:
{
"starttime" : "1569546000",
"endtime" : "1569718800",
"cursor" : 0 ,
"size" : 100 ,
"filters" : [
{
"key": "template_id",
"value": "ZLqk8pcsAoaXZ1eY56vpAgfX28MPdYU3ayMaSPHaaa"
},
{
"key" : "creator",
"value" : "WuJunJie"
},
{
"key" : "department",
"value" : "1"
},
{
"key" : "sp_status",
"value" : "1"
}
]
}
参数说明:
参数 | 必须 | 说明 |
access_token | 是 | 调用接口凭证。必须使用审批应用或企业内自建应用的secret获取,获取方式参考:文档-获取access_token |
starttime | 是 | 审批单提交的时间范围,开始时间,UNix时间戳 |
endtime | 是 | 审批单提交的时间范围,结束时间,Unix时间戳 |
cursor | 是 | 分页查询游标,默认为0,后续使用返回的next_cursor进行分页拉取 |
size | 是 | 一次请求拉取审批单数量,默认值为100,上限值为100 |
filters | 否 | 筛选条件,可对批量拉取的审批申请设置约束条件,支持设置多个条件 |
└ key | 否 | 筛选类型,包括: template_id - 模板类型/模板id; creator - 申请人; department - 审批单提单者所在部门; sp_status - 审批状态。 注意: 仅“部门”支持同时配置多个筛选条件。 不同类型的筛选条件之间为“与”的关系,同类型筛选条件之间为“或”的关系 |
└ value | 否 | 筛选值,对应为:template_id-模板id;creator-申请人userid ;department-所在部门id;sp_status-审批单状态(1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付) |
1 接口频率限制 600次/分钟
2 请求的参数endtime需要大于startime, 起始时间跨度不能超过31天;
返回结果 :
{
"errcode": 0,
"errmsg": "ok",
"sp_no_list": [
"201909270001",
"201909270002",
"201909270003"
]
}
参数说明:
参数 | 说明 |
sp_no_list | 审批单号列表,包含满足条件的审批申请 |
next_cursor | 后续请求查询的游标,当返回结果没有该字段时表示审批单已经拉取完 |
错误说明:
错误码 | 说明 |
301055 | 无审批应用数据拉取权限 |
301025 | 请求参数错误 |
301026 | 批量拉取审批单内部接口失败 |
5.2代码实战
1、代码如下:
#region 5、批量获取审批单号
/// <summary>
/// 批量获取审批单号
/// </summary>
/// <returns></returns>
[Route("ApprovalInfo")]
public async Task<IActionResult> ApprovalInfo()
{
//request accessToken
var model = GetAccessToken();
string accessToken = "";
if (model != null)
accessToken = model.access_token;
//build request url
string url = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovalinfo?access_token=" + accessToken;
//build filter model
//很多在线的unix时间戳转换工具,将开始与结束时间戳设置为你发起过申请的时间段内即可
//实际开发应该是在页面上可以选择,然后后端才动态去请求,这里只做演示,故写死
ApprovalInfoFilterModel filter = new ApprovalInfoFilterModel()
{
starttime = "1618880400",//开始时间戳
endtime = "1618968648"//结束时间戳
};
//request
using (HttpClient client = new HttpClient())
{
var response = await client.PostAsync(url, new JsonContent(filter));
//get result
/*
{
"errcode": 0,
"errmsg": "ok",
"sp_no_list": [
"201909270001",
"201909270002",
"201909270003"
]
}
*/
//sp_no_list 审批单号列表,包含满足条件的审批申请
//next_cursor 后续请求查询的游标,当返回结果没有该字段时表示审批单已经拉取完
var res = response.Content.ReadAsStringAsync().Result;
return Content(res);
}
}
/*
* 错误码 说明
301055 无审批应用数据拉取权限
301025 请求参数错误
301026 批量拉取审批单内部接口失败
*/
#endregion
2、访问ApprovalInfo
接口,可看到返回的"审批单号"列表(注:这个单号是你当前的审批应用发起过的申请的单号,如果你没发起过,返回的列表就是空的)
5.3试错
此时将请求的开始时间改为两个月前的时间,并重新运行项目,会出现如下参数错误的json,原因时企业微信那边规定了起始时间与结束时间的跨度不能超过31天。
{
"errcode": 301025,
"errmsg": "get approval param error, hint: [1619148837_171_c18ae242ea46634c3d1c0e81d30579a4], from ip: 61.144.144.239, more info at https://open.work.weixin.qq.com/devtool/query?e=301025",
"sp_no_list": []
}
6.获取审批申请详情
6.1概述
1、调试工具地址:调试工具
2、企业可通过审批应用或自建应用Secret调用本接口,根据审批单号查询企业微信“审批应用”的审批申请详情。
**请求方式:**POST(HTTPS)
请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token=ACCESS_TOKEN
请求示例:
{ "sp_no" : "201909270001"}
参数说明:
参数 | 必须 | 说明 |
access_token | 是 | 调用接口凭证。必须使用审批应用或企业内自建应用的secret获取,获取方式参考:文档-获取access_token |
sp_no | 是 | 审批单编号。 |
接口频率限制 600次/分钟
返回结果参数说明 :官网文档链接
6.2代码实战
1、代码如下:
#region 6、获取审批申请详情
/// <summary>
/// 获取审批申请详情
/// </summary>
/// <param name="sp_no">审批单号</param>
/// <returns></returns>
[Route("ApprovalDetail")]
public async Task<IActionResult> ApprovalDetail(string sp_no)
{
//request accessToken
var model = GetAccessToken();
string accessToken = "";
if (model != null)
accessToken = model.access_token;
//build request url
string url = "https://qyapi.weixin.qq.com/cgi-bin/oa/getapprovaldetail?access_token=" + accessToken;
//request
using (HttpClient client = new HttpClient())
{
var response = await client.PostAsync(url, new JsonContent(new { sp_no = sp_no }));
//get result
var res = response.Content.ReadAsStringAsync().Result;
return Content(res);
}
}
#endregion
2、访问ApprovalDetail
接口,并在url后面加上?sp_no=你的审批单号
,可看到返回了指定的审批详情数据(字段解析传送门)。
6.3试错
1、填写一个错误的审批单号,会出现"获取审批数据错误",如下是返回的json:
{
"errcode": 301026,
"errmsg": "get approval data error, hint: [1619150281_164_57932dd54c6c8511da178b6acb01d061], from ip: 61.144.145.51, more info at https://open.work.weixin.qq.com/devtool/query?e=301026"
}
2、将代码中的sp_no = sp_no
更改为sp_no1 = sp_no
,会出现如下错误,提示参数错误,原因是接口需要的是"sp_no"而不是"sp_no1"。
{
"errcode": 301025,
"errmsg": "get approval param error, hint: [1619150454_164_e80e6d553772daf415132280984e5858], from ip: 61.144.144.239, more info at https://open.work.weixin.qq.com/devtool/query?e=301025"
}
错误说明
错误码 | 说明 |
301055 | 无审批应用数据拉取权限 |
301025 | 请求参数错误 |
301026 | 拉取审批申请详情内部接口失败 |