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

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

有时候浪费很长时间改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加密即可。

注意事项

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

 
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端, 比较坑爹。

猜你喜欢

转载自www.cnblogs.com/jikeyun/p/12068962.html