关于一个前端写微信授权登录的过程

77 阅读3分钟

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,将数据存储,失败就抛出。commitdispatch的主要区别在于它们的行为方式。commit用于同步地触发action,而dispatch用于异步地触发action。 在Vuex中,每个action都有一个type,当使用commitdispatch触发action时,需要传递相应的type作为参数。commit返回的是触发action时的状态,而dispatch返回的是异步操作的结果。 因此,将commitstate作为参数传递给组件的方法,可以使组件更方便地访问Vuex store中的数据和异步触发action。而使用dispatch可以处理需要异步操作的情况,例如从远程服务器获取数据并更新状态。