文章目录

  • 三、OA-审批
  • 1.概述
  • 1.1场景描述
  • 1.2与“审批流程引擎”的区别
  • 2.获取审批模板详情
  • 2.1线上调试
  • 2.2代码实战
  • 2.2.1获取access_token
  • 2.2.2试错
  • 2.2.3获取模版详情
  • 2.2.4试错
  • 3.提交审批申请
  • 3.1代码实战
  • 3.2试错
  • 源码
  • 赞赏


三、OA-审批

1.概述

目前,企业微信审批应用对企业内部提供了以下接口和能力:

1.1场景描述

(1)企业可通过审批应用或自建应用secret换取access_token,用于企业微信审批应用相关接口调用。

(2)首先,可通过“获取审批模板详情”接口,了解模板内的控件构成及控件id。
(3)然后,可通过“提交审批申请”,利用模板id和控件id,代员工发起和填写审批申请,自定义审批流程。
(4)审批前后,可通过“审批申请状态变化回调通知”,订阅审批单据流转的变化,进行各项拓展动作。
(5)此外,还可通过“批量获取审批编号”、“获取审批申请详情”接口,随时获取审批申请的内容详情和流程状态。

python 企业微信审批 企业微信审批制度_python 企业微信审批

1.2与“审批流程引擎”的区别

(1)企业微信审批应用相关接口,是围绕“审批应用”的开放,数据的写入、读取对象都为企业微信“审批应用”。

(2)“审批流程引擎”相关接口,是在“自建应用”或“第三方应用”中增加流程相关功能,使用和作用对象都为“自建应用”或“第三方应用”,不会影响企业微信“审批应用”。

(3)注:如下图所示指向的"审批"和"自建审批应用"都属于是企业内部开发,其获取AccessToken的方式参考“获取access_token”。而第三方应用获取AccessToken的方式参考参考“获取企业凭证”。这里有个概念就行,后面主要讲企业内部开发的两种方式。

python 企业微信审批 企业微信审批制度_模版_02

2.获取审批模板详情

企业可通过审批应用或自建应用Secret调用本接口,获取企业微信“审批应用”内指定审批模板的详情。官方文档链接

企业微信官方线上调试:调试工具

**请求方式:**POST(HTTPS
**请求地址:**https://qyapi.weixin.qq.com/cgi-bin/oa/gettemplatedetail?access_token=ACCESS_TOKEN

请求示例:

{   "template_id" : "ZLqk8pcsAoXZ1eYa6vpAgfX28MPdYU3ayMaSPHaaa"}//请求体(body)中的内容

较早时间创建的模板,id为类似“1910324946027731_1688852032423522_1808577376_15111111111”的数字串。

参数说明:

参数

必须

说明

access_token


调用接口凭证。必须使用审批应用或企业内自建应用的secret获取,获取方式参考:文档-获取access_token

template_id


模板的唯一标识id。可在“获取审批单据详情”、“审批状态变化回调通知”中获得,也可在审批模板的模板编辑页面浏览器Url链接中获得。

1.审批应用的Secret可获取企业自建模板及第三方服务商添加的模板详情;自建应用的Secret可获取企业自建模板的模板详情。
2.接口调用频率限制为600次/分钟。

python 企业微信审批 企业微信审批制度_微信_03

2.1线上调试

1、找到"应用管理"->“审批”->“API”->“查看”->“发送”,此时企业微信将会收到该应用的密钥,自己保存好即可。

python 企业微信审批 企业微信审批制度_模版_04

2、点击调试工具,进入官方提供的调试界面,其中corpid指的是企业id(在后台"我的企业"可看到),corpsecret指的是上一步中在企业微信客户端收到的secret,填写完后,点击"获取access_token"按钮,即可看到输出的token,如下图:

python 企业微信审批 企业微信审批制度_python 企业微信审批_05


python 企业微信审批 企业微信审批制度_NETCore_06

3、找到"应用管理"->“审批”->“请假"模版,最后一个”/“中的字符串为该模版的id,复制即可。

python 企业微信审批 企业微信审批制度_python 企业微信审批_07


4、将第"3’'步中的id替换掉下图的"template_id”,点击"调用接口"按钮即可。

python 企业微信审批 企业微信审批制度_python 企业微信审批_08

5、将滚动条拖到底部,可看到如下图所示输出该模版的详情信息。

python 企业微信审批 企业微信审批制度_微信_09

2.2代码实战

2.2.1获取access_token

1、创建一个ASP.NET Core Web(Razor)项目。

2、具体代码如下:

[Route("api/[controller]")]
public class SampleDataController : Controller {
//控制器中注入缓存中间件
private MemoryCacheHelper _cahce;  
public SampleDataController(IMemoryCache cahce)
        {
            _cahce = cahce;
        }

#region 1、获取accessToken
        /// <summary>
        /// 请求企业微信accessToken
        /// </summary>
        /// <returns></returns>
        private async Task<AccessTokenModel> RequireAccessToken()
        {
            var result = new AccessTokenModel();
            try
            {
                string corpid = "你的企业微信id";
                string secret = "你的审批应用密钥";
                string url = $"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpi}&corpsecret={secret}";
                using (HttpClient client = new HttpClient())
                {
                    result = JsonConvert.DeserializeObject<AccessTokenModel>(await client.GetStringAsync(url));//将请求到的json序列化为模型类
                }
            }
            catch (Exception ex)
            {
                throw;
            }
            return result;
        }
        /// <summary>
        /// 获取内存中的accessToken
        /// </summary>
        /// <returns></returns>
        [Route("CacheTryGetToken")]
        public IActionResult CacheTryGetToken()
        {
            return Json(GetAccessToken());
        }
        /// <summary>
        /// 获取缓存中的accesstoken
        /// </summary>
        /// <returns></returns>
        private AccessTokenModel GetAccessToken()
        {
            var accessToken = _cahce.Get<AccessTokenModel>(PublicParam.accessTokenKey);
            if (accessToken == null)
            {
				
                accessToken = RequireAccessToken().Result;
                _cahce.Set(PublicParam.accessTokenKey, accessToken, TimeSpan.FromSeconds(PublicParam.sleepTime));
            }
            return accessToken;
        }
        #endregion
            
            
 /// <summary>
    /// AccessToken模型类
    /// </summary>
    public class AccessTokenModel
    {
       /// <summary>
        /// 错误码
        /// </summary>
        public string errcode { get; set; }
        /// <summary>
        /// 错误信息
        /// </summary>
        public string errmsg { get; set; }
        /// <summary>
        /// access_token
        /// </summary>
        public string access_token { get; set; }
        /// <summary>
        /// 超时时间
        /// </summary>
        public long expires_in { get; set; }
    }

    public class JsonContent : StringContent
    {
        public JsonContent(object obj) :
        base(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json")
        { }
    }     
}

3、运行项目,并访问https://localhost:5001/api/SampleData/CacheTryGetToken ,可看到如下的token数据:

python 企业微信审批 企业微信审批制度_NETCore_10

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获取模版详情

1、请求"请假"模版详情数据的代码如下:

#region 2、获取模版详情
        /// <summary>
        /// 获取模版详情
        /// </summary>
        /// <returns></returns>
        [Route("TemplateDetails")]
        public async Task<IActionResult> TemplateDetails()
        {
            //获取token
            var model = GetAccessToken();
            string accessToken = "";
            if (model != null)
                accessToken = model.access_token;
            string url = "https://qyapi.weixin.qq.com/cgi-bin/oa/gettemplatedetail?access_token=" + accessToken;//构建请求url
            using (HttpClient client = new HttpClient())
            {
                //template_id(请看“线上调试”篇章)
                var response = await client.PostAsync(url, new JsonContent(new { template_id = "你的模版id" }));
                //获取请求到数据,并转化为字符串
                var res = response.Content.ReadAsStringAsync().Result;
                return Content(res);
            }

        }
#endregion

2、运行项目,并访问https://localhost:5001/api/SampleData/TemplateDetails,可看到如下的"请假"模版数据:

python 企业微信审批 企业微信审批制度_python 企业微信审批_11

3、模版参数参考官方文档:https://open.work.weixin.qq.com/api/doc/90000/90135/91982

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
请求地址: 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": [],
            "departments": [],
            "files": [],
            "children": [],
            "stat_field": [],
            "vacation": {
              "selector": {
                "type": "single",
                "options": [
                  {
                    "key": "1",
                    "value": [
                      {
                        "text": "年假",
                        "lang": "zh_CN"
                      }
                    ]
                  }
                ]
              },
              "attendance": {
                "date_range": {
                  "type": "halfday",
                  "new_begin": 1618848000,
                  "new_end": 1618891200,
                  "new_duration": 86400
                },
                "type": 1,
                "slice_info": {
                  "day_items": [
                    {
                      "daytime": 1618848000,
                      "time_sections": [],
                      "duration": 28800
                    }
                  ],
                  "duration": 28800
                }
              }
            },
            "sum_field": [],
            "related_approval": [],
            "students": [],
            "classes": []
          }
        },
        {
          "control": "Textarea",
          "id": "item-1497581399901",
          "title": [
            {
              "text": "请假事由",
              "lang": "zh_CN"
            },
            {
              "text": "Leave Reason",
              "lang": "en"
            }
          ],
          "value": {
            "text": "你猜猜看",
            "tips": [],
            "members": [],
            "departments": [],
            "files": [],
            "children": [],
            "stat_field": [],
            "sum_field": [],
            "related_approval": [],
            "students": [],
            "classes": []
          }
        },
        {
          "control": "File",
          "id": "item-1497581426169",
          "title": [
            {
              "text": "说明附件",
              "lang": "zh_CN"
            },
            {
              "text": "Attachment",
              "lang": "en"
            }
          ],
          "value": {
            "tips": [],
            "members": [],
            "departments": [],
            "files": [],
            "children": [],
            "stat_field": [],
            "sum_field": [],
            "related_approval": [],
            "students": [],
            "classes": []
          }
        }
      ]
    },
    "summary_list": [
      {
        "summary_info": [
          {
            "text": "请假类型:年假",
            "lang": "zh_CN"
          }
        ]
      },
      {
        "summary_info": [
          {
            "text": "开始时间:2021/4/21 上午",
            "lang": "zh_CN"
          }
        ]
      },
      {
        "summary_info": [
          {
            "text": "结束时间:2021/4/21 下午",
            "lang": "zh_CN"
          }
        ]
      }
    ]
  }

3.1代码实战

1、用户id对应的是"通讯录"->“xxx成员”->“账号”,如下图所示:

python 企业微信审批 企业微信审批制度_NETCore_12


2、执行上述json之前,请先手动在企业微信客户端进行"请假"申请的发起,了解具体有哪些控件,会做哪些操作。

3、大致请求代码如下(其他帮助类存放在代码的ApplyEventModel中,具体看git仓库),请思路是A(发起人)发起一个请假申请,B(审批人)对该请假申请进行审批:

#region 3、提交审批申请
        /// <summary>
        /// 提交审批申请
        /// </summary>
        /// <param name="approver">审批人id,如果不填写,就默认推送给的另一个微信号</param>
        /// <returns></returns>
        [Route("ApplyEvent")]
        public async Task<IActionResult> ApplyEvent(string approver= "审批人id")
        {
            //get token
            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/applyevent?access_token=" + accessToken;//构建请求url

            //request 
            using (HttpClient client = new HttpClient())
            {
                var response = await client.PostAsync(url, new JsonContent(BuildApplyEventModel(approver)));
                //获取请求到数据,并转化为字符串
                var res = response.Content.ReadAsStringAsync().Result;
                return Content(res);
            }

        }

        /// <summary>
        /// 构建ApplyEventModel
        /// </summary>
		 /// <param name="approver">审批人id</param>
        /// <returns></returns>
        private ApplyEventModel BuildApplyEventModel(string approver)
        {
            ApplyEventModel model = new ApplyEventModel();
            model.creator_userid = "创建人id";
            model.template_id = "请假模版id";
            model.use_template_approver = 0;
            model.approver = new List<ApproverModel>()
            {
                //审批人id数据,可以有多个审批人
                  new ApproverModel(){ userid=new List<string>(){ approver },attr=1 }
            };
            model.notify_type = 1;
            model.apply_data = BuildApplyData();
            model.summary_list = BuildSummaryList();
            var jsonModel = JsonConvert.SerializeObject(model);
            return model;
        }
        /// <summary>
        /// 构建apply_data
        /// </summary>
        /// <returns></returns>
        private ApplyData BuildApplyData()
        {
            var contentsList = new List<Contents>()
            {
                new Contents(){
                    control="Vacation",
                    id="vacation-1563793073898",
                    title = new List<Title>()
                    {
                        new Title(){ text="请假类型",lang="zh_CN"},
                        new Title(){ text="Leave Type",lang="en"}
                    },
                    value=new Value
                    {
                        tips = new List<string>(),
                        members = new List<string>(),
                        departments = new List<string>(),
                        files = new List<string>(),
                        children = new List<string>(),
                        stat_field = new List<string>(),
                        vacation = new Vacation()
                        {
                            selector = new Selector()
                            {
                                type = "single",
                                options = new List<Options>()
                                {
                                    new Options(){
                                        key="1",
                                        value = new List<Title>()
                                        {
                                            new Title(){text="年假",lang="zh_CN"}
                                        }
                                    }
                                }
                            },
                            attendance = new Attendance()
                            {
                                date_range = new Date_range()
                                {
                                    type = "halfday",
                                    new_begin= 1618848000,
                                    new_end = 1618891200,
                                    new_duration=86400
                                },
                                type = 1,
                                slice_info = new Slice_info()
                                {
                                    day_items = new List<Day_items>()
                                    {
                                        new Day_items()
                                        {
                                            daytime = 1618848000,
                                            time_sections = new List<string>(),
                                            duration = 28800
                                        }
                                    },
                                    duration = 28800
                                },
                                
                            },
                        },
                        sum_field = new List<string>(),
                        related_approval = new List<string>(),
                        students = new List<string>(),
                        classes = new List<string>()
                    }
                },
                new Contents()
                {
                    control = "Textarea",
                    id = "item-1497581399901",
                    title = new List<Title>()
                    {
                        new Title()
                        {
                            text = "请假事由",
                            lang = "zh_CN"
                        },
                        new Title()
                        {
                            text="Leave Reason",
                            lang = "en"
                        }
                    },
                    value = new Value()
                    {
                        text = "你猜猜看"
                    }
                },
                 new Contents()
                {
                    control = "File",
                    id = "item-1497581426169",
                    title = new List<Title>()
                    {
                        new Title()
                        {
                            text = "说明附件",
                            lang = "zh_CN"
                        },
                        new Title()
                        {
                            text="Attachment",
                            lang = "en"
                        }
                    },
                    value = new Value()
                }
            };
            ApplyData model = new ApplyData();
            model.contents = contentsList;
            return model;
        }

        /// <summary>
        /// 构建Summary_list(客户端收到的推送显示的内容)
        /// </summary>
        /// <returns></returns>
        private List<Summary_list> BuildSummaryList()
        {
            List<Summary_list> list = new List<Summary_list>()
            {
                new Summary_list()
                {
                    summary_info = new List<Summary_info>()
                    {
                        new Summary_info() {
                            text = "请假类型:年假",
                            lang = "zh_CN"
                        }
                    },
                },
                new Summary_list() {
                    summary_info = new List<Summary_info>()
                    {
                        new Summary_info() {
                            text = "开始时间:2021/4/21 上午",
                            lang = "zh_CN"
                        }
                    },
                },
                new Summary_list()
                {
                    summary_info = new List<Summary_info>()
                    {
                        new Summary_info() {
                            text = "结束时间:2021/4/21 下午",
                            lang = "zh_CN"
                        }
                    },
                },
            };
            return list;
        }
        #endregion

4、请求结果如下,errmsg为ok则表示成功,其中sp_no是提交成功后返回的表单编号:

python 企业微信审批 企业微信审批制度_微信_13

3.2试错

1、此时,将用户id改为当前企业微信不存在的用户id,则会出现如下错误,提示"审批参数有误,无效的用户id"

{
"errcode": 301025,
"errmsg": "get approval param error:invalid userid:"
}

2、在上述BuildApplyEventModel方法中的templateid后随意加一个字符(让templateid不存在),会出现如下错误,提示"提交审批单请求参数错误,操作的模版id",故此时应排查templateid是否有误。

{
"errcode": 301025,
"errmsg": "get approval param error:invalid template_id"
}