11月份收到上级的一个项目,开发一套以会诊和随访为主要功能的医院外包项目,起初都是账号密码登录,省时省力,由于是第一次独立完成公司的项目,考虑不周,想把微信授权登录放在后面再来完善,就这样子开发了一个月,直到开发到随访有一个功能是实时的微信定时通知,就这样子不得不开写微信授权登录的前后台逻辑。对于一个小前端兼写点后端的前端来说,有点难为我,但是秉着迎难而上的势头,见招拆招,开写! 首先,其实微信授权的逻辑并不困难,但是后续的登录鉴权,登录分BC端,功能的完善,接口的梳理,ui的绘制,前端store的控制,页面的跳转,感觉还是有必要记录一下。 接下来开写 1.前端调用uni.login拿到这个用户的codeRes,这个codeRes是为了去后台换用户的openId,openId是用户的唯一标识,之后调用我们的微信登录接口,接下来贴代码 前端部分:
uni.login({
provider: 'weixin',
success(codeRes) {
wxCLogin({
code: codeRes.code,
appId: uni.getAccountInfoSync().miniProgram.appId
}).then(res => {
if (res.code===901) {
loginPopupShow.value=true
openId.value = res.msg
} else if (res.code === 302) {
openId.value = res.msg.split("!")[1]
} else {
Promise.all([
store.dispatch('SetToken', res.data),
store.dispatch('GetClientUserInfo'),
store.dispatch('GetUserTemplateConfig'),
store.dispatch('GetDictTypeTreeData'),
]).then(() => {
proxy.$tab.reLaunch('/pages/index/index')
})
}
})
}})
可以看到拿到codeRes后,将项目的appId丢给了后端,接下来贴后端代码
public String wxLogin(AuthWechatCodeParam codeParam){
String appid = "******************";
String secret = "*****************************";
Map<String, Object> params = new HashMap<>();
params.put("appid", appid);
params.put("secret", secret);
params.put("js_code", codeParam.getCode());
params.put("grant_type", "authorization_code");
log.warn("*** 微信请求参数, {}", com.alibaba.fastjson.JSONObject.toJSONString(params));
String url = "https://api.weixin.qq.com/sns/jscode2session";
String response = HttpUtil.post(url, params);
log.warn("*** 微信返回结果, {}", response);
//response拿到的是oppenid和session-key,要把openid存进我们的三方表(存放的是我们的用户信息)
com.alibaba.fastjson.JSONObject responseLogin = JSON.parseObject(response);
String thirdId = responseLogin.get("openid").toString();
// 根据第三方用户id和用户来源获取用户信息
List<AuthThirdUser> authThirdList = this.authThirdService.list(new LambdaQueryWrapper<AuthThirdUser>().eq(AuthThirdUser::getThirdId, thirdId));
// .eq(AuthThirdUser::getCategory, AuthDeviceTypeEnum.MINI.getValue()));
if(authThirdList.isEmpty()){//如果是空的,要进行处理
throw new CommonException(901, thirdId);
}
AuthThirdUser authThirdUser = authThirdList.get(0);
return this.doLoginById(authThirdUser.getUserId(), AuthDeviceTypeEnum.MINI.getValue(), SaClientTypeEnum.C.getValue());
}
接下来拿着我们的appid和appsecret携带着前端传进来的codeRes发到微信的api.weixin.qq.com/sns/jscode2… 去换来我们要的,也是最主要的openId。在这之前,我需要阐述一下我们需要什么表,患者需要一张表client_user,同时需要一张三方表auth_third_user,其实三方表的作用就是把openId存起来,对于这个问题,我请教了一下同事杰哥,为什么不把openId直接存放在client_user呢?这样不也是同样可以实现三方表的功能,少建一张表。其实这样子也是可以的,但是你得保证后续没有其他的第三方登录,不然可能需要多存其他的id。显得有点混乱,三方表其实就是一张中间表。我们拿着用户的openId去我们的三方表查,如果查得到,执行登录,查不到抛出901,交由前端,去触发用户授权手机号的动作,其实就是注册。 接下来贴一下,用户授权手机号之后的后端代码,其实就是注册。下面是用户点击授权后的前端代码与后端代码。 前端代码:
const timer = ref(null)
const getphonenumber = async (e) => {
if (timer.value !== null) {
clearTimeout(timer.value)
}
console.log('1111111111111111111111111111111,32s')
timer.value = setTimeout(() => {
getPhoneNumber({
code: e.detail.code,
appId: uni.getAccountInfoSync().miniProgram.appId,
openId: openId.value,
empNo: loginForm.empNo
}).then(res => {
console.log('res', res)
if (res.code == 301) {
uni.showToast({
title: res.msg,
icon: 'none'
})
popupEmpNo.value.open()
} else {
console.log(res.data)
Promise.all([
store.dispatch('SetToken', res.data),
// store.dispatch('GetUserLoginMenu'),
store.dispatch('GetUserInfo'),
store.dispatch('GetUserTemplateConfig'),
store.dispatch('GetDictTypeTreeData')
]).then(() => {
proxy.$tab.reLaunch('/pages/home/index')
})
}
})
}, 1000)
}
public String getPhoneNumber(AuthWechatCodeParam codeParam) {
String appid = "*******************";
String secret = "***********************";
String accessToken = getAccessToken(appid,secret);
com.alibaba.fastjson.JSONObject params = new com.alibaba.fastjson.JSONObject();
params.put("code", codeParam.getCode());
log.warn("*** appid {}", appid);
log.warn("*** secret {}", secret);
log.warn("*** 微信请求参数, {}", com.alibaba.fastjson.JSONObject.toJSONString(codeParam));
String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
String response = HttpUtil.post(url, params.toJSONString());
com.alibaba.fastjson.JSONObject resPhoneInfo = JSON.parseObject(response);
log.warn("*** 微信返回结果, {}", response);
String userPhone = resPhoneInfo.getJSONObject("phone_info").getString("purePhoneNumber");
// 创建一个新的用户对象
// 保存新用户信息到用户表中
ClientUser newClient = new ClientUser();
newClient.setAccount(userPhone);
newClient.setIdentity("0");
newClient.setAvatar("https://file-luhuo.luhuomao.com/hpmp-mobile/icon/%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%83%281%29.png");
newClient.setOpenId(codeParam.getOpenId());
newClient.setUserStatus(ClientUserStatusEnum.ENABLE.getValue());
newClient.setPassword(CommonCryptogramUtil.doHashValue(devConfigApi.getValueByKey("123456")));
clientUserService.save(newClient);
SaBaseClientLoginUser loginUser = clientLoginUserApi.getClientUserByAccount(userPhone);
AuthThirdUser newUser = new AuthThirdUser();
String currentTime = String.valueOf(System.currentTimeMillis());
newUser.setName("newUser" + currentTime);
newUser.setUserId(loginUser.getId());
newUser.setNickname("newUser" + currentTime);
newUser.setOpenId(codeParam.getOpenId());
newUser.setThirdId(codeParam.getOpenId());
// 判断保存操作是否成功,如果成功则执行登录操作
if (loginUser.getId() != null) {
authThirdService.save(newUser);
return this.doLoginById(loginUser.getId(), AuthDeviceTypeEnum.MINI.getValue(), SaClientTypeEnum.C.getValue());
} else {
// 如果保存失败,可以返回相应的错误信息或者抛出异常
throw new CommonException(992,"注册失败");
}
}
private String getAccessToken(String appId, String appSecret) {
Object tokenObj = commonCacheOperator.get(appId);
if (ObjectUtil.isNotNull(tokenObj)) {
return tokenObj.toString();
}
com.alibaba.fastjson.JSONObject params = new com.alibaba.fastjson.JSONObject();
params.put("grant_type", "client_credential");
params.put("appid", appId);
params.put("secret", appSecret);
params.put("force_refresh", false);
HttpResponse accessTokenResponse = HttpRequest.post("https://api.weixin.qq.com/cgi-bin/stable_token" ).body(params.toJSONString()).execute();
com.alibaba.fastjson.JSONObject tokenJson = JSON.parseObject(accessTokenResponse.body());
if(!tokenJson.containsKey("access_token")){
log.error("******* 微信获取accessToken的新接口错误返回结果 {}", accessTokenResponse.body());
throw new CommonException("微信官方修改了获取access_token令牌新接口!");
}
String token = tokenJson.get("access_token").toString();
int expiresIn = Integer.parseInt(tokenJson.get("expires_in").toString());
commonCacheOperator.put("WX-TOKEN-" + appId, token, expiresIn);
return token;
}
里面有两个函数,一个是用appSecret和appid去拿到token令牌,还需要用户的codeRes,因为微信对用户手机号是加密的,需要我们自己在服务器请求手机号回来,可以看到userPhone就这样返回回来了。有了手机号我们其实就达成最终目的了,然后把他的相关信息set进我们的client表,形成注册,为了保证用户set进去了,做了一层判断再把用户set进三方表,完成登录。 写到这里,我发现自己把store的功能忘记得差不多,接下来顺带复习一下,我们可以注意到里面登录很多地方用到store,特别是存储信息的时候,例如
GetUserInfo({commit,state}) {
return new Promise((resolve, reject) => {
getLoginUser().then(res => {
// 缓存用户信息
commit('SET_userInfo', res.data)
resolve(res.data)
}).catch(error => {
reject(error)
})
})
},
把commit 和 state 作为参数传递给组件的方法,使得组件能够更方便地访问 Vuex store 中的数据和触发表志。我记得dispach底层其实就是commit,只是人家是异步的,接下来就是返回的一个promise,将数据存储,失败就抛出。commit和dispatch的主要区别在于它们的行为方式。commit用于同步地触发action,而dispatch用于异步地触发action。
在Vuex中,每个action都有一个type,当使用commit或dispatch触发action时,需要传递相应的type作为参数。commit返回的是触发action时的状态,而dispatch返回的是异步操作的结果。
因此,将commit和state作为参数传递给组件的方法,可以使组件更方便地访问Vuex store中的数据和异步触发action。而使用dispatch可以处理需要异步操作的情况,例如从远程服务器获取数据并更新状态。