企业微信自建应用审批接口开发

再说一次,企业微信开发文档很坑,特别坑,坑成鬼了。

有时候浪费很长时间改bug,查来查去,都没有发现到底哪里错了,查资料,搜资料,询问大神,都没有发现bug。改到最后都抑郁了,才发现用浏览器访问根本就不会报出具体的错误,pc端的企业微信客户端也不行,只有用手机上的企业微信客户端访问才会有具体的错误信息,在这方面浪费了很长时间,感觉自己有点傻,但是内心不承认。

总之,要想一帆风顺开发,听起来像是开玩笑,总会遇到让自己怀疑人生的bug。

文档对于很多细节描述不具体,而且也有很多东西没有demo可以参观,有demo的还是PHP,怎么说呢,有些东西想在网上查查,都查不到,所有,在开始之前,请让我先好好吐槽一下,以解我这几天的心头之火。

好吧,废话不多说,总不能转行吧!所以还是好好学习吧!!

企业微信自建应用审批接口开发:

企业微信自带有审批接口,最主要是,使用企业微信审批能力,在非审批应用内设置流程、发起审批。还能订阅通知消息,接收审批状态变化情况。

现在很多企业都使用企业微信审批接口,使用企业微信审批接口,审批发生变化,会自动发送消息给审批人以及抄送人,比较方便。微信自带的审批接口适用于小企业,简单的业务。一般大公司都会开发适用于自己企业的OA系统。

 

企业微信自建应用审批接口开发:

1.首先在企业微信后端,创建应用,进入审批接口,添加模板

2.自建应用发起审批

      通过JS-SDK,可在自建应用中发起审批,需要设置可信域名,花生壳免费域名,我试了,不行!

3.设置工作台应用主页,在主页内发起审批

注意:

      1.测试时一定要用手机企业微信客户端,会显示详细错误信息,用浏览器或者pc端, 比较坑爹。

              2.善于利用签名工具,验证自己各数据是否正确生成

 


1.首先在企业微信后端,创建应用,进入审批接口,添加模板

2.自建应用发起审批

点击接入流程说明,进入开发API

 开发逻辑为上图所示

2.1

     通过JS-SDK,可在自建应用中发起审批。查看JS-SDK调用详细说明
                具体步骤:
                   1.通过config接口注入权限验证配置
                   2.通过agentConfig注入应用的权限
                   3.调用审批流程引擎JS-API

1.通过config接口注入权限验证配置

 wx.config({
        beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题
        debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: '', // 必填,企业微信的corpID
        timestamp:'' , // 必填,生成签名的时间戳
        nonceStr: '', // 必填,生成签名的随机串
        signature: '',// 必填,签名,见 附录-JS-SDK使用权限签名算法
        jsApiList: ['agentConfig','thirdPartyOpenPage','selectExternalContact'] // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
    });
    wx.ready(function(){
    
        2.通过agentConfig注入应用的权限。
        wx.agentConfig({
            corpid: '', // 必填,企业微信的corpid,必须与当前登录的企业一致
            agentid: '', // 必填,企业微信的应用id
            timestamp: , // 必填,生成签名的时间戳
            nonceStr: '', // 必填,生成签名的随机串
            signature: '',// 必填,签名,见附录1
            jsApiList: ['thirdPartyOpenPage','selectExternalContact'], //必填
            success: function(res) {
            
                3.调用审批流程引擎JS-API
                wx.invoke('thirdPartyOpenPage', {
                    "oaType": "10001",// String
                    "templateId": "46af67a118a6ebf000002",// String
                    "thirdNo": "thirdNo",// String
                    "extData": {
                        'fieldList': [{
                            'title': '采购类型',
                            'type': 'text',
                            'value': '市场活动',
                        },
                        {
                            'title': '订单链接',
                            'type': 'link',
                            'value': 'https://work.weixin.qq.com',
                        }],
                    }
                },
                function(res) {
                    // 输出接口的回调信息
                    console.log(res);
                });
                
            },
            fail: function(res) {
                if(res.errMsg.indexOf('function not exist') > -1){
                    alert('版本过低请升级')
                }
            }
        });
    });

各个接口都调用成功的话,那么离成功就不远了;

2.1.jsp页面主要代码


<script type="text/javascript">

    function approval() {
        //动态获取当前页面url
        var link = window.location.href;

        $.ajax({
            type:"GET",
            data:{"url":link},
            url:"<%=request.getContextPath()%>/approval/send_approval.do",
            dataType:"json",
            success:function (data) {

                console.log(data);
                wx.config({
                    beta: true,
                    debug: true,
                    appId: data.appId,
                    timestamp: data.config_timestamp,
                    nonceStr: data.config_nonceStr,
                    signature: data.config_signature,
                    jsApiList: ['agentConfig','openUserProfile','thirdPartyOpenPage','selectExternalContact']
                });

                wx.ready(function () {
                    alert("config");
                    wx.agentConfig({
                        corpid: data.appId,
                        agentid: data.agentid,
                        timestamp: data.agent_timestamp,
                        nonceStr: data.agent_nonceStr,
                        signature: data.agent_signature,
                        jsApiList: ['thirdPartyOpenPage','selectExternalContact'],
                        success: function(res) {
                            //审批流程js调用
                            alert("agentConfig");
                            wx.invoke('thirdPartyOpenPage', {
                                    "oaType": data.oaType,
                                    "templateId": data.templateId,
                                    "thirdNo": data.thirdNo,
                                    "extData": {
                                        'fieldList': [{
                                            'title': '采购类型',
                                            'type': 'text',
                                            'value': '市场活动',
                                        },
                                            {
                                                'title': '订单链接',
                                                'type': 'link',
                                                'value': 'https://work.weixin.qq.com',
                                            }],
                                    }
                                },
                                function(res) {
                                    // 输出接口的回调信息
                                    alert("thirdPartyOpenPage");
                                    alert(res);
                                }
                            );
                        },
                        fail: function(res) {
                            alert("approval提交不通过");
                            alert("agentConfig:"+res.errMsg);
                            if(res.errMsg.indexOf('function not exist') > -1){
                                alert('版本过低请升级')
                            }
                        }
                    });

                });

                wx.error(function(res){

                });
            }
        })
    }

</script>

2.2后端主要代码

  @RequestMapping(value = "send_approval.do")
  public void sendApproval(HttpServletRequest request, HttpServletResponse response,String url)throws Exception{
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
     
        JSONObject jsonObject = iApprovalService.approvalCreate(url).getData();
        //返回数据到jsp页面
        response.getWriter().print(jsonObject);
    }
 public JSONObject approvalCreate(String url){

        JSONObject object = new JSONObject();
        
        //agentConfig接口下主要数据
        String approval_ticket = JS_Util.getApprovalTicket();
        JSONObject appJSONObject = JS_Util.getSignature(url,approval_ticket);
        object.put("agent_nonceStr",appJSONObject.get("nonceStr").toString());
        object.put("agent_timestamp",appJSONObject.get("timestamp").toString());
        object.put("agent_signature",appJSONObject.get("signature").toString());
        System.out.println("===============================");
        System.out.println(appJSONObject.toString());

        //config接口下主要数据
        String ticket = JS_Util.getJsApiTicket();
        JSONObject jsonObject = JS_Util.getSignature(url,ticket);
        object.put("config_nonceStr",jsonObject.get("nonceStr").toString());
        object.put("config_timestamp",jsonObject.get("timestamp").toString());
        object.put("config_signature",jsonObject.get("signature").toString());
        System.out.println("===========================");
        System.out.println(jsonObject.toString());

        //invoke接口下主要数据
        object.put("thirdNo",String.valueOf(generateOrderNo()));
        object.put("appId",PropertiesUtil.getProperty("corpid"));
        object.put("agentid",PropertiesUtil.getProperty("mycreateapp_agentid"));
        object.put("templateId",PropertiesUtil.getProperty("approval_id"));
        object.put("oaType","10001");

        return object;
    }

 

 

signatrue的生成

 

 

可以使用签名工具,查看生成的signatrue是否正确,可以快速的排除自己代码的错误

signature生成

    

签名算法

签名生成规则如下:
参与签名的参数有四个: noncestr(随机字符串), jsapi_ticket, timestamp(时间戳), url(当前网页的URL, 不包含#及其后面部分

将这些参数使用URL键值对的格式 (即 key1=value1&key2=value2…)拼接成字符串string1。
有两个注意点:1. 字段值采用原始值,不要进行URL转义;2. 必须严格按照如下格式拼接,不可变动字段顺序。



 

   jsapi_ticket=JSAPITICKET&noncestr=NONCESTR&timestamp=TIMESTAMP&url=URL

然后对string1作sha1加密即可。

 

注意事项

  1. 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
  2. 签名用的url必须是调用JS接口页面的完整URL。
  3. 出于安全考虑,开发者必须在服务器端实现签名的逻辑。

 

 
public class JS_Util {

    private static Logger logger = LoggerFactory.getLogger(JS_Util.class);

    //获取企业的jsapi_ticket
    private final static String GET_WX_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN";

    //应用jsapi_tictet
    private final static String GET_APP_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=ACCESS_TOKEN&type=agent_config";


    //获取企业的ticket
    public static String getJsApiTicket(){
        String access_token = WeiXinUtil.getAccessToken(PropertiesUtil.getProperty("corpid"),PropertiesUtil.getProperty("mycreateapp_secret")).getAccess_token();
        String url = GET_WX_TICKET.replace("ACCESS_TOKEN",access_token);
        JSONObject jsonObject = WeiXinUtil.httpRequest(url,"GET",null);
        String ticket = jsonObject.get("ticket").toString();
        return ticket;
    }


    //获取审批流程ticket
    public static String getApprovalTicket(){
        String access_token = WeiXinUtil.getAccessToken(PropertiesUtil.getProperty("corpid"),PropertiesUtil.getProperty("mycreateapp_secret")).getAccess_token();
        String url = GET_APP_TICKET.replace("ACCESS_TOKEN",access_token);
        JSONObject jsonObject = WeiXinUtil.httpRequest(url,"GET",null);
        String ticket = jsonObject.get("ticket").toString();
        return ticket;
    }

    /**
     * 参与签名的参数有四个: noncestr(随机字符串), jsapi_ticket, timestamp(时间戳), url(当前网页的URL, 不包含#及其后面部分)
     * 将这些参数使用URL键值对的格式 (即 key1=value1&key2=value2…)拼接成字符串string1。
     * 然后对string1作sha1加密即可。
     * @return
     */
    public static JSONObject getSignature(String url,String ticket){

        JSONObject jsonObject = new JSONObject();

        String noncestr = getRandomString(16);
        String timestamp = Long.toString(System.currentTimeMillis()).substring(0,10);

        // jsapi_ticket=JSAPITICKET&noncestr=NONCESTR&timestamp=TIMESTAMP&url=URL  顺序不能更改
        String str = "jsapi_ticket="+ticket+
                      "&noncestr="+noncestr+
                      "&timestamp="+timestamp+
                      "&url="+url;

        String signature = SHA1(str);
        jsonObject.put("nonceStr",noncestr);
        jsonObject.put("timestamp",timestamp);
        jsonObject.put("signature",signature);
        jsonObject.put("ticket",ticket);
        return jsonObject;
    }

    private static String SHA1(String decript){
        try{
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(decript.getBytes());
            byte[] digest = md.digest();

            StringBuffer hexstr = new StringBuffer();
            for (int i = 0; i < digest.length; i++) {
                String shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        }catch (Exception e){
            logger.error("sha1加密错误",e);
        }
        return "";
    }

    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();
    }

}

获取企业的ticket和应用的ticket,请求的url不同,但是使用的accessToken是相同的  

​

public class WeiXinUtil {
    private static Logger log = LoggerFactory.getLogger(WeiXinUtil.class);
    //微信的请求url
    //获取access_token的接口地址(GET)
    public final static String access_token_url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpId}&corpsecret={corpsecret}";



    /**
     * 1.发起https请求并获取结果
     */
    public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
        JSONObject jsonObject = null;
        StringBuffer buffer = new StringBuffer();
        try {
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
            httpUrlConn.setSSLSocketFactory(ssf);

            httpUrlConn.setDoOutput(true);
            httpUrlConn.setDoInput(true);
            httpUrlConn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            httpUrlConn.setRequestMethod(requestMethod);

            if ("GET".equalsIgnoreCase(requestMethod))
                httpUrlConn.connect();

            // 当有数据需要提交时
            if (null != outputStr) {
                OutputStream outputStream = httpUrlConn.getOutputStream();
                // 注意编码格式,防止中文乱码
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }

            // 将返回的输入流转换成字符串
            InputStream inputStream = httpUrlConn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            String str = null;
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            bufferedReader.close();
            inputStreamReader.close();
            // 释放资源
            inputStream.close();
            inputStream = null;
            httpUrlConn.disconnect();
            jsonObject = JSONObject.fromObject(buffer.toString());
        } catch (ConnectException ce) {
            log.error("Weixin server connection timed out.");
        } catch (Exception e) {
            log.error("https request error:{}", e);
        }
        return jsonObject;
    }


    /**
     * 获取应用的access_token
     */
    public static AccessToken getAccessToken(String appid, String appsecret) {
        AccessToken accessToken = null;

        String requestUrl = access_token_url.replace("{corpId}", appid).replace("{corpsecret}", appsecret);
        JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
        // 如果请求成功
        if (null != jsonObject) {
            try {
                accessToken = new AccessToken();
                accessToken.setAccess_token(jsonObject.getString("access_token"));
                accessToken.setExpires_in(jsonObject.getInt("expires_in"));
            } catch (JSONException e) {
                accessToken = null;
                // 获取token失败
                log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
            }
        }
        return accessToken;
    }
}

 

 主要的参数都被我统一放在了properties文件里,写了个工具类PropertiesUtil进行取出,方便管理。

3.设置工作台应用主页,在主页内发起审批,域名为设置的可信域名,刚开始用的花生壳,没有测试通过,域名所有权验证不通过

测试时一定要用手机企业微信客户端,会显示详细错误信息,用浏览器或者pc端, 比较坑爹。

 

    审批状态回调及审批查询请移步:https://blog.youkuaiyun.com/yzhbyssgdbd123/article/details/98171964

 

 

 

 

 

 

评论 23
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值