企业微信开发OA审批
Posted 赵广陆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了企业微信开发OA审批相关的知识,希望对你有一定的参考价值。
目录
1 OA-审批
1.1 场景描述
(1)企业可通过审批应用或自建应用secret换取access_token,用于企业微信审批应用相关接口调用。
(2)首先,可通过“获取审批模板详情”接口,了解模板内的控件构成及控件id。
(3)然后,可通过“提交审批申请”,利用模板id和控件id,代员工发起和填写审批申请,自定义审批流程。
(4)审批前后,可通过“审批申请状态变化回调通知”,订阅审批单据流转的变化,进行各项拓展动作。
(5)此外,还可通过“批量获取审批编号”、“获取审批申请详情”接口,随时获取审批申请的内容详情和流程状态。
1.2 与审批流程引擎的区别
(1)企业微信审批应用相关接口,是围绕“审批应用”的开放,数据的写入、读取对象都为企业微信“审批应用”。
(2)“审批流程引擎”相关接口,是在“自建应用”或“第三方应用”中增加流程相关功能,使用和作用对象都为“自建应用”或“第三方应用”,不会影响企业微信“审批应用”。
(3)注:如下图所示指向的"审批"和"自建审批应用"都属于是企业内部开发,其获取AccessToken的方式参考“获取access_token”。而第三方应用获取AccessToken的方式参考参考“获取企业凭证”。这里有个概念就行,后面主要讲企业内部开发的两种方式。
2 获取审批模板详情
企业可通过审批应用或自建应用Secret调用本接口,获取企业微信“审批应用”内指定审批模板的详情。官方文档链接
企业微信官方线上调试:调试工具
请求方式:POST
请求地址:https://qyapi.weixin.qq.com/cgi-bin/oa/gettemplatedetail?access_token=ACCESS_TOKEN
请求示例:
{
"template_id" : "3TmmcVxSXNgmZJM8ZpbrZVuucxadbweg7pdtEmaQ"
}
较早时间创建的模板,id为类似“1910324946027731_1688852032423522_1808577376_15111111111”的数字串。
参数说明:
参数 | 必须 | 说明 |
---|---|---|
access_token | 是 | 调用接口凭证。必须使用审批应用或企业内自建应用的secret获取,获取方式参考:文档-获取access_token |
template_id | 是 | 模板的唯一标识id。可在“获取审批单据详情”、“审批状态变化回调通知”中获得,也可在审批模板的模板编辑页面浏览器Url链接中获得。 |
1.审批应用的Secret可获取企业自建模板及第三方服务商添加的模板详情;自建应用的Secret可获取企业自建模板的模板详情。
2.接口调用频率限制为600次/分钟。
2.1 线上调试
1、找到"应用管理"->“审批”->“API”->“查看”->“发送”,此时企业微信将会收到该应用的密钥,自己保存好即可。
2、点击调试工具,进入官方提供的调试界面,其中corpid
指的是企业id(在后台"我的企业"可看到),corpsecret
指的是上一步中在企业微信客户端收到的secret
,填写完后,点击"获取access_token"按钮,即可看到输出的token,如下图:
3、找到"应用管理"->“审批”->“请假"模版,最后一个”/“中的字符串为该模版的id,复制即可。
4、将第"3’'步中的id替换掉下图的"template_id”,点击"调用接口"按钮即可。
5、将滚动条拖到底部,可看到如下图所示输出该模版的详情信息。
{
"errcode": 0,
"errmsg": "ok",
"template_names": [
{
"text": "请假",
"lang": "zh_CN"
},
{
"text": "Leave",
"lang": "en"
}
],
"template_content": {
"controls": [
{
"property": {
"control": "Vacation",
"id": "vacation-1563793073898",
"title": [
{
"text": "请假类型",
"lang": "zh_CN"
},
{
"text": "Leave Type",
"lang": "en"
}
],
"placeholder": [
{
"text": "",
"lang": "zh_CN"
}
],
"require": 1,
"un_print": 0
}
},
{
"property": {
"control": "Textarea",
"id": "item-1497581399901",
"title": [
{
"text": "请假事由",
"lang": "zh_CN"
},
{
"text": "Leave Reason",
"lang": "en"
}
],
"placeholder": [
{
"text": "请输入请假事由",
"lang": "zh_CN"
},
{
"text": "Enter a reason",
"lang": "en"
}
],
"require": 0,
"un_print": 0
}
},
{
"property": {
"control": "File",
"id": "item-1497581426169",
"title": [
{
"text": "说明附件",
"lang": "zh_CN"
},
{
"text": "Attachment",
"lang": "en"
}
],
"placeholder": [
{
"text": "",
"lang": "zh_CN"
}
],
"require": 0,
"un_print": 1
}
}
]
},
"vacation_list": {
"item": [
{
"id": 1,
"name": [
{
"text": "年假",
"lang": "zh_CN"
}
]
},
{
"id": 2,
"name": [
{
"text": "事假",
"lang": "zh_CN"
}
]
},
{
"id": 3,
"name": [
{
"text": "病假",
"lang": "zh_CN"
}
]
},
{
"id": 4,
"name": [
{
"text": "调休假",
"lang": "zh_CN"
}
]
},
{
"id": 5,
"name": [
{
"text": "婚假",
"lang": "zh_CN"
}
]
},
{
"id": 6,
"name": [
{
"text": "产假",
"lang": "zh_CN"
}
]
},
{
"id": 7,
"name": [
{
"text": "陪产假",
"lang": "zh_CN"
}
]
},
{
"id": 8,
"name": [
{
"text": "其他",
"lang": "zh_CN"
}
]
}
]
}
}
2.2 代码实战
2.2.1获取access_token工具类
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.tzwy.lcls.entity.vo.AccessTokenVO;
import com.tzwy.lcls.exception.ApiException;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 企业微信工具
*
* @author oldlu
* @version 1.0
*/
@Order(3)
public class WeComUtil {
// 获取accessToken地址
private static String GET_TOKEN_URL;
// 获取企业微信ID
private static String CORPID;
// 获取应用密匙
private static String CORPSECRET;
// 获取应用ID
private static String AGENTID;
// 根据code获取openId
private static String GET_OPEN_ID;
// access_token的失效时间
private static long expiresTime;
// 缓存的access_token
private static String accessToken;
// 静态块,保障一次加载获得数据
static {
GET_TOKEN_URL = YamlConfigurerUtil.getStrYmlVal("wecom.getTokenUrl");
CORPID = YamlConfigurerUtil.getStrYmlVal("wecom.corpid");
CORPSECRET = YamlConfigurerUtil.getStrYmlVal("wecom.corpsecret");
AGENTID = YamlConfigurerUtil.getStrYmlVal("wecom.AgentId");
}
/**
* 获取accessToken
*
* @return accessToken
*/
public static AccessTokenVO getAccessToken() throws ApiException {
String errcode;
AccessTokenVO accesstokenVO = new AccessTokenVO();
// 判断accessToken是否已经过期,如果过期需要重新获取
if (accessToken == null || expiresTime < new Date().getTime()) {
// 发起请求获取accessToken
Map<String, Object> map = new HashMap<>();
map.put("corpid", CORPID);
map.put("corpsecret", CORPSECRET);
String ret = HttpUtil.get(GET_TOKEN_URL,map);
JSONObject result = JSONObject.parseObject(ret);
System.out.println(result.toJSONString());
accesstokenVO = JSONObject.parseObject(ret, AccessTokenVO.class);
if (StrUtil.isBlank(result.getString("errcode"))||result.getInteger("errcode")==0) {
// 设置accessToken的失效时间
long expires_in = result.getLong("expires_in");
// 失效时间 = 当前时间 + 有效期(提前一分钟)
expiresTime = new Date().getTime() + (expires_in - 60) * 1000;
} else {
throw new ApiException("获取accessToken失败:" + accesstokenVO.getErrmsg(), HttpStatus.BAD_REQUEST);
}
}
return accesstokenVO;
}
/**
* 根据code获取微信用户openId
* @param code code
* @return 微信用户openId
* @throws ApiException 异常信息
*/
public static String getOpenId(String appId,String secret,String code) throws ApiException{
String result;
try {
RestTemplate restTemplate = new RestTemplate();
String params = "appid=" + appId +
"&secret=" + secret +
"&js_code=" + code +
"&grant_type=" + "authorization_code";
ResponseEntity<String> responseEntity = restTemplate.getForEntity(GET_OPEN_ID + params, String.class);
result= responseEntity.getBody();
} catch (RestClientException e) {
e.printStackTrace();
throw new ApiException("code获取微信用户openId失败!:", HttpStatus.BAD_REQUEST);
}
return result;
}
//发送应用消息
/**
*
* @return
* @throws ApiException
*/
public static void appMessage(String messageUrl, String accessToken, String touser, String textcard,String agentid) throws ApiException {
Map<String, Object> map = new HashMap<>();
Map<String, Object> textcardMap = new HashMap<>();
textcardMap.put("title","领奖通知");
textcardMap.put("description","<div class=\\\\\\"gray\\\\\\">2016年9月26日</div> <div class=\\\\\\"normal\\\\\\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\\\\\\"highlight\\\\\\">请于2016年10月10日前联系行政同事领取</div>");
textcardMap.put("url","url");
textcardMap.put("btntxt","更多");
map.put("touser", touser);
map.put("msgtype", "textcard");
map.put("agentid", agentid);
map.put("textcard", textcardMap);
/*
*
{
"touser" : "UserID1|UserID2|UserID3",
"msgtype" : "textcard",
"agentid" : 1,
"textcard" : {
"title" : "领奖通知",
"description" : "<div class=\\"gray\\">2016年9月26日</div> <div class=\\"normal\\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\\"highlight\\">请于2016年10月10日前联系行政同事领取</div>",
"url" : "URL",
"btntxt":"更多"
},
}
* */
JSONObject jsonObject = EWeChatUtil.postJson(messageUrl, map, accessToken);
}
public static void main(String[] args) {
JSONObject jsonObject=new JSONObject();
WeComUtil.appMessage("https://qyapi.weixin.qq.com/cgi-bin/message/send","M851qF0u3EjSDGMrTeBJW3L7LaCbbM0Khpr3T9Pcq11htS3lopmfl5-Scqo9MTkVTOmCdntRJD5sC6wHe4R93H27cK5qjyYtPTSnHYllmUwSl-Ztu7ShtdfNnLHcmo07q2Sxhig-fNkEgE4OkfkVh5MgOnbBFDUDfr8-oSKHq1AeHHNHVe41jMPrzfXM0gRlo30z8dEHmmgzYxmqa4Xj5g","zgl",
jsonObject.toJSONString(),"1000003");
}
}
2.2.2 错误分析
1、此时给请求access_token的corpi(企业id)
后面随意加个字符,会出现如下所示错误,意思是企业id有误,故需排查一下密钥是否填写错误。
{
"access_token": null,
"expires_in": 0,
"errcode": "40013",
"errmsg": "invalid corpid, hint: [1619103786_194_2ee3fb91c5238be28588ceed7a444d23], from ip: 119.129.123.127, more info at https://open.work.weixin.qq.com/devtool/query?e=40013"
}
2、在请求access_token的corpsecret(应用密钥)
后面随意加个字符,会出现如下所示错误,提示错误的凭据,原因是密钥有误,故需排查一下密钥是否填写错误。
{
"access_token": null,
"expires_in": 0,
"errcode": "40001",
"errmsg": "invalid credential, hint: [1619103952_195_a01894928bf0bce72d1467131c09363f], from ip: 119.129.123.127, more info at https://open.work.weixin.qq.com/devtool/query?e=40001"
}
2.2.3 获取模版详情
2.2.4 错误分析
1、在上述TemplateDetails
接口的url
后随意加一个字符(让token与缓存中的不一致),会出现如下错误,提示无效的access_token。
{
"errcode": 40014,
"errmsg": "invalid access_token",
"template_names": []
}
2、在上述TemplateDetails
接口的templateid
后随意加一个字符(让templateid不存在),会出现如下错误,提示"提交审批单请求参数错误",因为请求体(body)只有一个参数,故此时应排查templateid是否有误。
{
"errcode": 301025,
"errmsg": "get approval param error, hint: [1619104495_170_e21873150fdc0119da67c4fe98dafe69], from ip: 119.129.123.127, more info at https://open.work.weixin.qq.com/devtool/query?e=301025",
"template_names": []
}
3 提交审批申请
企业可通过审批应用或自建应用Secret调用本接口,代应用可见范围内员工在企业微信“审批应用”内提交指定类型的审批申请。
企业微信官方线上调试:调试工具
请求方式:POST
请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent?access_token=ACCESS_TOKEN
请求示例(请假模版):(参数说明请看官方文档)
注:json数据是根据请求过的"请假审批申请"构建的,后面会介绍到如何请求审批申请详情数据。
{
"creator_userid": "发起申请的用户id",
"template_id": "请假模版id",
"use_template_approver": 0,
"approver": [
{
"attr": 1,
"userid": [ "审批人id" ]
}
],
"notify_type": 1,
"apply_data": {
"contents": [
{
"control": "Vacation",
"id": "vacation-1563793073898",
"title": [
{
"text": "请假类型",
"lang": "zh_CN"
},
{
"text": "Leave Type",
"lang": "en"
}
],
"value": {
"tips": [],
"members": [以上是关于企业微信开发OA审批的主要内容,如果未能解决你的问题,请参考以下文章