一、背景
最近接到一个项目,项目原团队(产品、技术)已经解散,交接情况不明,也无人接手。刚好我负责的业务跟这些项目有半毛钱关系,其他小组找到我,我也是一脸懵逼,一问三不知,也没有源码。于是找部门经理帮忙(gitlab权限,找整个公司的项目),找到项目源码后,fuck ,后台是PHP,无奈跟相关人说需要重构。自己研究相关的页面、PHP源码,弄清楚涉及什么业务,沉思三两天,终于知道所以然。。。
二、开发思路
1、配置企业微信应用,前往企业微信后台管理配置,如下截图:
2、页面地址,假如部署后的域名访问为:https://www.aaa.com/wp_wework/workphoto/home ,需要配置这个可信赖域名,找到自定义的应用,配置如下:
3、页面授权,用户使用企业微信,即可操作企业微信的应用页面,页面即可获取这个用户信息。那么它们之间的授权关系是怎么样的呢,这个是重点。
1)、获取企业的jsapi_ticket,相关操作如下:
A、涉及的参数:dto的字段
{
private String corpId; // 企业微信ID
private String agentId; // 应用ID
private String url; // 可信赖域名
}
public String getCorpTicket(String corpId, String agentId , QyAgentCommondUrlDTO dto) {
// 获取 redis 的缓存数据
String res = repository.get(QYWEIXIN_JSAPI_TICKET_KEY+dto.getCorpId()+dto.getAgentId());
String jsapi_ticket = "";
if(StringUtil.isEmpty(res)){
// 调用 企业微信的接口:获取企业的jsapi_ticket
String result = OAuthApi.getJsapiTicket().getJson();
JSONObject resultJson = JSONObject.parseObject(result);
if(resultJson.get("ticket") != null){
jsapi_ticket = resultJson.getString("ticket");
repository.setExpire(QYWEIXIN_JSAPI_TICKET_KEY+dto.getCorpId()+dto.getAgentId(),result,7200);
}
}else{
JSONObject resJson = JSONObject.parseObject(res);
jsapi_ticket = resJson.getString("ticket");
}
//生成的随机字符串
String nonce_str = StringUtilsX.getRandomStringByLength(32);
//时间戳
Long timeStamp = System.currentTimeMillis() / 1000;
String stringSignTemp = "jsapi_ticket="+jsapi_ticket+"&noncestr=" + nonce_str+"×tamp=" + timeStamp
+"&url="+dto.getUrl();
String signature = StringUtilsX.getSha1(stringSignTemp);
net.sf.json.JSONObject tickJson = new net.sf.json.JSONObject();
tickJson.put("appId",dto.getCorpId());
tickJson.put("timestamp",timeStamp);
tickJson.put("nonceStr",nonce_str);
tickJson.put("signature",signature);
tickJson.put("isSuccess",Boolean.TRUE);
tickJson.put("responseCode",0);
tickJson.put("responseMsg","请求成功");
logger.info("---getJsticket--results:"+tickJson);
return tickJson.toString();
}
// 加解密类
public class StringUtilsX {
/**
* StringUtils工具类方法 获取一定长度的随机字符串,范围0-9,a-z
*
* @return 一定长度的随机字符串
*/
public static String getRandomStringByLength(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* sha1 加密
* @param str
* @return
*/
public static String getSha1(String str){
if(str==null||str.length()==0){
return null;
}
char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9',
'a','b','c','d','e','f'};
try {
MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
mdTemp.update(str.getBytes("UTF-8"));
byte[] md = mdTemp.digest();
int j = md.length;
char buf[] = new char[j*2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
buf[k++] = hexDigits[byte0 & 0xf];
}
return new String(buf);
} catch (Exception e) {
// TODO: handle exception
return null;
}
}
}
前端界面js处理:这里主要是获取企业的jsapi_ticket 和 获取应用的jsapi_ticket,给界面授权。注意:wxCorpConfig 、 wxAgentConfig,目前用到wxCorpConfig,wxAgentConfig暂时没用上。
async function wxShareRequest({ url, title, desc, link, imgUrl, phone } = {}) {
try {
// 开发环境
let domain = process.env.NODE_ENV == 'development' ? 'https://www.aaa.com/wp_wework/' : 'https://www.bbbb.com/wp_wework/';
let wxCorpConfig = await getWxplatformCorpConfig()
let wxAgentConfig = await getWxplatformAgentConfig()
if (wxCorpConfig === false || wxAgentConfig === false) return
wx.config({
beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: wxCorpConfig.appId, // 必填,企业微信的corpID
timestamp: wxCorpConfig.timestamp, // 必填,生成签名的时间戳
nonceStr: wxCorpConfig.nonceStr, // 必填,生成签名的随机串
signature: wxCorpConfig.signature, // 必填,签名,见 附录-JS-SDK使用权限签名算法
jsApiList: ['openUserProfile','agentConfig', 'onMenuShareAppMessage', 'onMenuShareWechat', 'onMenuShareTimeline'] // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
});
wx.ready(function() {
wx.onMenuShareAppMessage({
title: '工作形象照', // 分享标题
desc: '蓝月亮的小伙伴,快来制作你的专属企业微信头像吧!', //'快速制作企业微信头像 ', // 分享描述
link: domain, // 分享链接;在微信上分享时,该链接的域名必须与企业某个应用的可信域名一致
imgUrl: domain + (imgUrl || '/static/img/share.png'), // 分享图标
success: function() {
// 用户确认分享后执行的回调函数
},
cancel: function() {
// 用户取消分享后执行的回调函数
}
});
wx.onMenuShareWechat({
title: '工作形象照', // 分享标题
desc: '蓝月亮的小伙伴,快来制作你的专属企业微信头像吧!', // 分享描述
link: domain, // 分享链接
imgUrl: domain + (imgUrl || '/static/img/share.png'), // 分享图标
success: function() {
// 用户确认分享后执行的回调函数
},
cancel: function() {
// 用户取消分享后执行的回调函数
}
});
wx.onMenuShareTimeline({
title: '工作形象照', // 分享标题
link: domain, // 分享链接
imgUrl: domain + (imgUrl || '/static/img/share.png'), // 分享图标
success: function() {
// 用户确认分享后执行的回调函数
},
cancel: function() {
// 用户取消分享后执行的回调函数
}
});
});
} catch (error) {
}
}
2)、网页授权方式,网页授权登录,这个主要获取用户的信息,就如我们登录后,有个token,如下图所示:
这里分两步:
第一步:点击应用,页面跳转。在页面初始过程需要配置跳转权限,这里侧重在路由跳转的设置,如下:
重点:获取code ,既是:window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + global.AppId + '&redirect_uri=' + encodeURIComponent(global.domain + to.fullPath) + '&response_type=code&scope=snsapi_base&agentid='+global.agentId+'&state=STATE#wechat_redirect'
// 白名单
const whiteList = ['/login', '/register'] // no redirect whitelist
const agent = window.navigator.userAgent.toLowerCase();
// 全局路由卫士
router.beforeEach((to, from, next) => {
// 设置页面title
document.title = getPageTitle(to.meta.title);
// 确定用户是否已登录(cookie中是否有token) getToken();
const hasToken = getToken();
if (hasToken) {
next()
} else {
console.log(to)
/* has no token*/
// 在免登录白名单,直接进入
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
// 没有访问权限的其他页面被重定向到登录页面。
// redirect 后携带登录后要访问的页面 to.fullPath 可将query中的参数一并带入
// next(`/login?redirect=${encodeURIComponent(to.fullPath)}`);
/**
* 微信授权
* @description 微信环境则走微信授权获取个人信息及支持微信支付
*/
// 判断是否为微信环境 utils.isWechat
if (agent.match(/MicroMessenger/i) == "micromessenger") {
// 判断是否微信授权过
// 判断是否有微信授权回调 code
if (!to.query.code) {
window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + global.AppId + '&redirect_uri=' + encodeURIComponent(global.domain + to.fullPath) + '&response_type=code&scope=snsapi_base&agentid='+global.agentId+'&state=STATE#wechat_redirect'
} else {
let corpId = global.AppId; // 企业微信ID
let agentId = global.agentId; // 应用ID
let param ={
corpId: corpId,
agentId: agentId,
code: to.query.code.trim()
}
// 通过code获取用户信息
authAction(param).then(response => {
if (response.data.success) {
let obj = response.data.data;
if (!obj){
console.log("后台返回为空的用户对象");
return ;
}
let item = {
name: obj.name,
employeeNo:obj.userid,
gender:obj.gender,
corpId: corpId,
agentId: agentId
}
let token = obj.userid;
setToken(token);
setUserInfo(item);
}
next('/');
}).catch(error => {
console.error(error)
})
}
} else {
Toast({
message: '请在企业微信客户端打开',
forbidClick: true, // 禁用背景点击
duration: 2000
});
}
}
}
})
第二步:获取用户信息,方法为:authAction ,这里的后台调用两个方法:根据code获取成员信息 和 读取成员 ,前者获取企业微信员工编号,后者根据员工编号获取用户信息。至此,整个页面授权成功。前端做好用户信息的缓存。
三、总结
1、企业微信点击应用后授权问题(获取企业的jsapi_ticket、网页授权登录),侧重在页面跳转既是路由跳转过程授权。
2、企业微信接口调用的权限处理,需要知道企业微信的应用的配置信息。
3、前端页面对用户授权后的用户信息缓存问题,推荐使用cookie。
4、文章比较粗糙,旨在简述企业微信自定义应用的页面授权思路。简单来说是对哪些页面授权,现有提供授权的接口有哪些,传哪些参数处理,注意什么。