企业微信-自建应用-配置聊天侧边栏的那些事

偶然的一次机会,看到了企业微信聊天的侧边栏可以集成自建应用,便走走这坑路
一,打开企业微信文档,打开企业微信管理后台并扫码登录
二,找到应用管理滑到底部,并创建应用 如下图
企业微信-自建应用-配置聊天侧边栏的那些事
文章图片

企业微信-自建应用-配置聊天侧边栏的那些事
文章图片

三,打开应用详情页
企业微信-自建应用-配置聊天侧边栏的那些事
文章图片

企业微信-自建应用-配置聊天侧边栏的那些事
文章图片

点击设置可信域名
企业微信-自建应用-配置聊天侧边栏的那些事
文章图片

四,java开发
常量类,定义了微信接口名,参数等
/** * @author shock * @ClassName: WXworkConstants * @Description: 企业微信常量 */ public class WxWorkConstants {/** * 微信token缓存Redis中Key */ public static final String WX_TOKEN_KEY = "qywx_access_token_key"; /** * 微信api_ticket缓存Redis中Key */ public static final String WX_TICKET_KEY = "qywx_jsapi_ticket_key"; public static final String WX_AGENT_TICKET_KEY = "qywx_agent_jsapi_ticket_key"; /** *微信js接口的 access_token,微信js接口的临时票据,有效期 7200秒 */ public static finalintEXPIRETIME = 7200; /** * 获取企业微信token地址及对应参数 */ public static final String QYWX_GET_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"; public static final String QYWX_GET_TOKEN_URL_PARAM_CORPID = "corpid"; public static final String QYWX_GET_TOKEN_URL_PARAM_CORPSECRET = "corpsecret"; /** * 获取企业微信token返回成功对应errcode值 */ public static final String QYWX_GET_TOKEN_RETURN_SUCCESS_CODE = "0"; public static final String QYWX_GET_TOKEN_RETURN_ERRCODE = "errcode"; public static final String QYWX_GET_TOKEN_RETURN_ERRMSG = "errmsg"; public static final String QYWX_GET_TOKEN_RETURN_TOKEN = "access_token"; /** * 获取企业微信引入JS-SDK的ticket地址及对应参数 */ public static final String QYWX_GET_JSAPITICKET_URL = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; public static final String QYWX_GET_JSAPITICKET_URL_PARAM_TOKEN = "access_token"; public static final String QYWX_GET_JSAPITICKET_URL_PARAM_TICKET = "jsapi_ticket"; public static final String QYWX_GET_JSAPITICKET_URL_PARAM_NONCESTR = "noncestr"; public static final String QYWX_GET_JSAPITICKET_URL_PARAM_TIMESTAMP = "timestamp"; public static final String QYWX_GET_JSAPITICKET_URL_PARAM_URL = "url"; public static final String QYWX_GET_JSAPITICKET_RETURN_SIGNATURE = "signature"; /** * 获取用户ticket参数 */ public static final String QYWX_GET_JSAPITICKET_RETURN_SUCCESS_CODE = "0"; public static final String QYWX_GET_JSAPITICKET_RETURN_ERRCODE = "errcode"; public static final String QYWX_GET_JSAPITICKET_RETURN_ERRMSG = "errmsg"; public static final String QYWX_GET_JSAPITICKET_RETURN_TICKET = "ticket"; /** * 获取应用ticket相关agentConfig */ public static final String QYWX_GET_AGENT_JSAPITICKET_URL = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get"; public static final String QYWX_GET_AGENT_JSAPITICKET_TYPE = "agent_config"; public static final String QYWX_GET_AGENT_JSAPITICKET_PARAM = "type"; /** * 通过code获取用户信息url及其对应参数 */ public static final String QYWX_GET_USERINFO_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"; public static final String QYWX_GET_USERINFO_URL_PARAM_TOKEN = "access_token"; public static final String QYWX_GET_USERINFO_URL_PARAM_CODE = "code"; /** * 获取客户详情url及其参数 */ public static final String QYWX_GET_CONTACT_DETAIL_URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get"; public static final String QYWX_GET_CONTACT_DETAIL_PARAM_TOKEN = "access_token"; public static final String QYWX_GET_CONTACT_DETAIL_PARAM_USERID = "external_userid"; public static final String QYWX_GET_CONTACT_DETAIL_RETURN_EXTERNAL_CONTACT = "external_contact"; /** * 授权code 用于获取企业员工信息 */ public static final String AUTH_BASE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?"; /** * 允许的范围 */ public static final String SCOPE = "snsapi_base"; /** * 获取用户信息 */ public static final String INFO_BASE_URL = "https://api.weixin.qq.com/sns/userinfo?"; /** * 回调地址 */ public static final String REDIRECT_URL = "http://azhen.gz.aeert.com/"; /** * 根据code查询企业员工信息 */ public static final String GET_USER_INFO_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/get"; public static final String GET_USER_INFO_PARAM_USERID = "userid"; public static final String GET_USER_INFO_RETURN_USERID = "UserId"; public static final String GET_USER_INFO_BY_CODE_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"; public static final String GET_USER_INFO_BY_CODE_PARAM = "code"; /** * 企业微信的CorpID,在企业微信管理端查看 */ public static final String QYWX_CORPID = "企业ID"; /** * 授权方的网页应用ID,在具体的网页应用中查看 */ public static final String QYWX_AGENTID = "你的应用ID"; /** * 应用的凭证密钥,在具体的网页应用中查看 */ public static final String QYWX_CORPSECRET = "你的秘钥"; /** * 参数连接符 */ public static final String QYWX_AND = "&"; public static final String QYWX_EQUAL = "="; public static final String QYWX_QUERY = "?"; }

请求微信接口工具类
package com.itjcloud.scrm.system.api.wx.util; import cn.hutool.core.lang.Console; import com.alibaba.fastjson.JSONObject; import com.itjcloud.scrm.common.utils.HttpUtils; import com.itjcloud.scrm.system.api.wx.constans.WxWorkConstants; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.UUID; /** * @author shock * @ClassName: WxWorkUtil */ public class WxWorkUtil { private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; public static JSONObject getSignature(String url, String ticket) { JSONObject rul = new JSONObject(); // 生成签名字符串 String nonceStr = UUID.randomUUID().toString(); String timestamp = Long.toString(System.currentTimeMillis() / 1000); String sign = ""; sign += "jsapi_ticket=" + ticket + WxWorkConstants.QYWX_AND + "noncestr=" + nonceStr + WxWorkConstants.QYWX_AND + "timestamp=" + timestamp + WxWorkConstants.QYWX_AND + "url=" + url; String signature = ""; try { // 指定sha1算法 MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update(sign.getBytes()); // 获取字节数组 byte[] messageDigest = digest.digest(); // Create Hex String signature = byteToHexStr(messageDigest); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } rul.put(WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_SIGNATURE, signature); rul.put(WxWorkConstants.QYWX_GET_JSAPITICKET_URL_PARAM_NONCESTR, nonceStr); rul.put(WxWorkConstants.QYWX_GET_JSAPITICKET_URL_PARAM_TIMESTAMP, timestamp); return rul; }/** * 获取企业微信token(应用调用接口凭证) 该值具有以下特性: 1、通用性:该企业微信应用下通用, 2、 * *有效性:该值有效时长为7200秒, 3、微信端不可长期无限制请求,推荐使用缓存保存改值 * * @return */ public static String getTencentToken() { String url = ""; url += WxWorkConstants.QYWX_GET_TOKEN_URL + WxWorkConstants.QYWX_QUERY; url += WxWorkConstants.QYWX_GET_TOKEN_URL_PARAM_CORPID + WxWorkConstants.QYWX_EQUAL + WxWorkConstants.QYWX_CORPID; url += WxWorkConstants.QYWX_AND + WxWorkConstants.QYWX_GET_TOKEN_URL_PARAM_CORPSECRET + WxWorkConstants.QYWX_EQUAL; url += WxWorkConstants.QYWX_CORPSECRET; JSONObject tokenJson = UrlRequestUtil.sendGetRequest(url); String errcode = tokenJson .getString(WxWorkConstants.QYWX_GET_TOKEN_RETURN_ERRCODE); if (WxWorkConstants.QYWX_GET_TOKEN_RETURN_SUCCESS_CODE .equals(errcode)) { return tokenJson .getString(WxWorkConstants.QYWX_GET_TOKEN_RETURN_TOKEN); } else { throw new RuntimeException("获取微信Token失败"); } }/** * 根据token获取票据,为签名提供票据 * * @param token * @return */ public static String getTencentJSSDKTicket(String token) { String params = ""; params += WxWorkConstants.QYWX_GET_JSAPITICKET_URL_PARAM_TOKEN + WxWorkConstants.QYWX_EQUAL + token; String result = HttpUtils.sendGet(WxWorkConstants.QYWX_GET_JSAPITICKET_URL, params); JSONObject ticketJson = JSONObject.parseObject(result); String errcode = ticketJson .getString(WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_ERRCODE); if (WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_SUCCESS_CODE .equals(errcode)) { return ticketJson .getString(WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_TICKET); } else { throw new RuntimeException("获取微信Ticket失败"); } }/** * 获取应用票据 * @param token * @return */ public static String getTencentAgentJSSDKTicket(String token) { String params = ""; params += WxWorkConstants.QYWX_GET_JSAPITICKET_URL_PARAM_TOKEN + WxWorkConstants.QYWX_EQUAL + token; params += WxWorkConstants.QYWX_AND + WxWorkConstants.QYWX_GET_AGENT_JSAPITICKET_PARAM + WxWorkConstants.QYWX_EQUAL + WxWorkConstants.QYWX_GET_AGENT_JSAPITICKET_TYPE; String result = HttpUtils.sendGet(WxWorkConstants.QYWX_GET_AGENT_JSAPITICKET_URL, params); JSONObject ticketJson = JSONObject.parseObject(result); String errcode = ticketJson .getString(WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_ERRCODE); if (WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_SUCCESS_CODE .equals(errcode)) { return ticketJson .getString(WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_TICKET); } else { throw new RuntimeException("获取微信Agent Ticket失败"); } }/** * 获取客户详情 */ public static JSONObject getExternalcontact(String userId, String token){ String params = ""; params += WxWorkConstants.QYWX_GET_CONTACT_DETAIL_PARAM_TOKEN + WxWorkConstants.QYWX_EQUAL + token; params += WxWorkConstants.QYWX_AND; params += WxWorkConstants.QYWX_GET_CONTACT_DETAIL_PARAM_USERID + WxWorkConstants.QYWX_EQUAL + userId; String result = HttpUtils.sendGet(WxWorkConstants.QYWX_GET_CONTACT_DETAIL_URL, params); JSONObject jsonObject = JSONObject.parseObject(result); String errcode = jsonObject .getString(WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_ERRCODE); if (WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_SUCCESS_CODE .equals(errcode)) { return jsonObject; } else { throw new RuntimeException("获取企业微信客户详情失败!"); } }/** 获取员工Id */ public static JSONObject getWxStaffUserId(String token, String code){String params = ""; params += WxWorkConstants.QYWX_GET_CONTACT_DETAIL_PARAM_TOKEN + WxWorkConstants.QYWX_EQUAL + token; params += WxWorkConstants.QYWX_AND; params += WxWorkConstants.GET_USER_INFO_BY_CODE_PARAM + WxWorkConstants.QYWX_EQUAL + code; String result = HttpUtils.sendGet(WxWorkConstants.GET_USER_INFO_BY_CODE_URL, params); JSONObject jsonObject = JSONObject.parseObject(result); String errcode = jsonObject .getString(WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_ERRCODE); if (WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_SUCCESS_CODE .equals(errcode)) { return jsonObject; } else { throw new RuntimeException("获取企业微信员工信息失败!"); } }/** 获取员工信息详情 */ public static JSONObject getWxStaffUserInfo(String token, String userId){ String params = ""; params += WxWorkConstants.QYWX_GET_CONTACT_DETAIL_PARAM_TOKEN + WxWorkConstants.QYWX_EQUAL + token; params += WxWorkConstants.QYWX_AND; params += WxWorkConstants.GET_USER_INFO_PARAM_USERID + WxWorkConstants.QYWX_EQUAL + userId; String result = HttpUtils.sendGet(WxWorkConstants.GET_USER_INFO_URL, params); JSONObject jsonObject = JSONObject.parseObject(result); String errcode = jsonObject .getString(WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_ERRCODE); if (WxWorkConstants.QYWX_GET_JSAPITICKET_RETURN_SUCCESS_CODE .equals(errcode)) { return jsonObject; } else { throw new RuntimeException("获取企业员工详情失败!"); } } /** * 根据指定长度返回随机字符串 * * @param length * @return 对应长度的随机字符串 */ private static String getRandomString(int length) { // 随机字符串的随机字符库 String keyString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; StringBuffer sb = new StringBuffer(); int len = keyString.length(); for (int i = 0; i < length; i++) { sb.append(keyString.charAt((int) Math.round(Math.random() * (len - 1)))); } return sb.toString(); }/** * 将字节转换为十六进制字符串 * * @param mByte * @return */ private static String byteToHexStr(byte[] mByte) { int len = mByte.length; StringBuilder buf = new StringBuilder(len * 2); // 把密文转换成十六进制的字符串形式 for (int j = 0; j < len; j++) { buf.append(HEX_DIGITS[(mByte[j] >> 4) & 0x0f]); buf.append(HEX_DIGITS[mByte[j] & 0x0f]); } return buf.toString(); } }

HTTP请求封装类
package com.itjcloud.scrm.common.utils; import com.itjcloud.scrm.common.constant.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.*; import java.io.*; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; import java.security.cert.X509Certificate; import java.util.Map; /** * 通用http发送方法 * * @author ruoyi */ public class HttpUtils { private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); /** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { return sendGet(url, param, Constants.UTF8); }/** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @param contentType 编码类型 * @return 所代表远程资源的响应结果 */ public static String sendGet(String url, String param, String contentType) { StringBuilder result = new StringBuilder(); BufferedReader in = null; try { String urlNameString = url + "?" + param; log.info("sendGet - {}", urlNameString); URL realUrl = new URL(urlNameString); URLConnection connection = realUrl.openConnection(); connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); connection.connect(); in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); String line; while ((line = in.readLine()) != null) { result.append(line); } log.info("recv - {}", result); } catch (ConnectException e) { log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); } finally { try { if (in != null) { in.close(); } } catch (Exception ex) { log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); } } return result.toString(); }/** * 向指定 URL 发送POST方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param, Map headParamMap) { PrintWriter out = null; BufferedReader in = null; StringBuilder result = new StringBuilder(); try { String urlNameString = url; log.info("sendPost - {}", urlNameString); URL realUrl = new URL(urlNameString); URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("contentType", "utf-8"); conn.setDoOutput(true); conn.setDoInput(true); headParamMap.forEach(conn::setRequestProperty); out = new PrintWriter(conn.getOutputStream()); out.print(param); out.flush(); in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); String line; while ((line = in.readLine()) != null) { result.append(line); } log.info("recv - {}", result); } catch (ConnectException e) { log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); } finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); } } return result.toString(); }public static String sendSSLPost(String url, String param) { StringBuilder result = new StringBuilder(); String urlNameString = url + "?" + param; try { log.info("sendSSLPost - {}", urlNameString); SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); URL console = new URL(urlNameString); HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("contentType", "utf-8"); conn.setDoOutput(true); conn.setDoInput(true); conn.setSSLSocketFactory(sc.getSocketFactory()); conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); conn.connect(); InputStream is = conn.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String ret = ""; while ((ret = br.readLine()) != null) { if (ret != null && !ret.trim().equals("")) { result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8")); } } log.info("recv - {}", result); conn.disconnect(); br.close(); } catch (ConnectException e) { log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); } return result.toString(); }private static class TrustAnyTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { }@Override public void checkServerTrusted(X509Certificate[] chain, String authType) { }@Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } }private static class TrustAnyHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return true; } } }

五,VUE
【企业微信-自建应用-配置聊天侧边栏的那些事】在index.html中引入微信的JS SDK,还要引入一个 jwxwork sdk 。不引第二个,调用一些接口报错没权限。我佛了
- 锐客网html,body{ margin: 0 auto; width: 100%; min-width: 355px; height: 100%; } .el-cascader-panel { height: 120px; } .el-cascader-menu{ min-width: 120px !important; }.el-cascader-node { padding: 0 10px 0 7px !important; } .el-cascader__suggestion-list{ width: 280px !important; } .el-cascader-menu__wrap{ width: 120px !important; }

主要认证企业微信的组件,重点是 wx.config和wx.agentConfig
.view-wrap { width: 100%; height: 100%; }// 微信环境 .view-wx { width: 100%; height: 100%; }// 非微信环境 .view-err { width: 100%; height: 100%; text-align: center; font-size: 18px; margin-top: 30px; color: #333333; }.customer_flex { display: flex; justify-content: space-between; align-items: center; }.customer_center_flex { display: flex; justify-content: center; align-items: center; }.customer_detail { height: 100%; width: 100%; // padding: 0 5px; // box-sizing: border-box; .top { padding: 10px; box-sizing: border-box; border: 1px solid #DEE0E2; .portrait { width: 40px; height: 40px; img { width: 100%; } }.top_right { width: 85%; .customer_name { text-align: left; line-height: 10px; .title { font-size: 15px; font-weight: Regular; color: #333333; }.shop { font-size: 12px; font-weight: Regular; color: #3154EF; } } }}}/deep/ .el-button--danger { background-color: #F6382B; }/deep/ .el-tabs__item { padding: 0; }/deep/ .el-tabs__content { height: calc(100vh - 200px); overflow: auto; }

六,坑
1,因为需要可信域名,而我使用花生壳内网穿透的,免费的网很慢~ 2,企业微信是有调试台的,教程移步:企业微信开启调试模式 3,企业微信获取客户(外部联系人)的userid很容易,不知道为啥获取当前登录员工的userid却那么费劲,只好走企业微信的 构造网页授权链接进行重定向获取code(这个code只能消费一次,有效期为5分钟), 再通过code获取员工userid,再通过userid获取员工手机号等详细信息。 4,vue的 vue.config.js的publicPath: '/', 慎改 5,大坑图示要配置,应用,否则客户相关接口没权调用 企业微信-自建应用-配置聊天侧边栏的那些事
文章图片
, 6,ok

    推荐阅读