【尚医通】微信扫码登录和手机号登录冲突问题解决思路
问题描述
最近做尚医通遇到一个问题,微信扫码登录和手机号登录在 特殊情况
下会发生冲突,导致无法登录的问题。下面就描述一下几种情况。
正常情况:用户第一次一上来就使用微信扫码登录,后端在调用回调函数的时候就完成了用户注册,由于是第一次登录,需要绑定手机号,于是又调用了一次手机号登录的接口,手机号登录成功之后就完成了绑定。之后就可以随便使用扫码登录或手机号登录了。
特殊情况:用户第一次一上来就使用手机号登录,后端会根据手机号去数据库搜索“唯一”的一条数据,如果结果为空则说明是第一次登录,直接注册;如果不为空,说明已经注册过,就直接登录。用户第二次登录时使用的是微信扫码登录,后端在调用回调函数的时候再次完成用户注册,然后又绑定手机号。那么现在数据库就有两条手机号相同的数据了。第三次登陆时若使用手机号登录,那么“唯一”查询就不满足了,就会报错,最后登录不了。
回顾流程
首先回顾一下扫码登录的流程。
更多详情请见:资源中心 - 微信开放平台 (qq.com)
解决思路
上回调函数代码!
//微信扫描后回调的方法
@GetMapping("/callback")
public String callback(@RequestParam("code") String code, @RequestParam("state") String state) {
//1.获取临时票据 code
log.info("code:{}", code);
//2.拿着code和微信id和密钥,请求微信固定地址,得到两个值
//2.1使用code和appid以及appscrect换取access_token
// %s表示一个占位符
StringBuffer baseAccessTokenUrl = new StringBuffer()
.append("https://api.weixin.qq.com/sns/oauth2/access_token")
.append("?appid=%s")
.append("&secret=%s")
.append("&code=%s")
.append("&grant_type=authorization_code");
String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
ConstantWxPropertiesUtil.WX_OPEN_APP_ID,
ConstantWxPropertiesUtil.WX_OPEN_APP_SECRET,
code);
//3.使用httpclient请求这个地址
try {
String accesstokenInfo = HttpClientUtils.get(accessTokenUrl);
log.info("accesstokenInfo:{}", accesstokenInfo);
//4.从返回字符串中获取两个值 openid 和access_token
JSONObject jsonObject = JSON.parseObject(accesstokenInfo);
String access_token = jsonObject.getString("access_token");
//openid为每个微信用户唯一
String openid = jsonObject.getString("openid");
log.info("access_token:{}", access_token);
log.info("openid:{}", openid);
//5.根据access_token获取微信用户的基本信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
String resultInfo = HttpClientUtils.get(userInfoUrl);
log.info("resultInfo:{}", resultInfo);
JSONObject resultUserInfoJson = JSON.parseObject(resultInfo);
//6.解析用户信息
//6.1用户名称
String nickname = resultUserInfoJson.getString("nickname");
//6.2用户头像
String headimgurl = resultUserInfoJson.getString("headimgurl");
//7.如果第一次微信登录添加到数据库
UserInfo userInfo = userInfoService.saveUser(nickname, openid);
//8.返回name和token字符串
String name = userInfo.getName();
if (StringUtils.isEmpty(name)) {
name = userInfo.getNickName();
}
if (StringUtils.isEmpty(name)) {
name = userInfo.getPhone();
}
//判断userInfo是否有手机号,如果手机号为空,返回openid
//如果手机号不为空,返回openid值是空字符串
//前端根据“openid”判断:如果openid为空,已绑定手机号(不需要再绑定手机号),否则需要绑定手机号
if (StringUtils.isEmpty(userInfo.getPhone())) {
openid = userInfo.getOpenid();
} else {
openid = "";
}
//使用jwt生成token字符串
String token = JwtHelper.createToken(userInfo.getId(), name);
//跳转到前端页面
return "redirect:" + ConstantWxPropertiesUtil.YYGH_BASE_URL
+ "/weixin/callback?token=" + token + "&openid="
+ openid + "&name=" + URLEncoder.encode(name, "utf-8");
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
问题主要出现在第七步。
解决思路如下:
- 如果数据库中已经存在了手机号登录时的数据,那么微信登录时就不应该再向数据库中添加数据。
- 但是在回调函数中我们无法通过手机号查询数据库。所以我们无法在回调函数中实现是否注册的判断,那不如索性把第七步给删了,改成只搜索用户信息,不保存用户信息。
回调函数中无法一口气解决所有的问题,所以我们只得把重心调转到登录方法上。
上登录方法代码!!!:
//手机号登录接口
@Override
public Map<String, Object> loginUser(LoginVo loginVo) {
//1.从loginVo获取输入的手机号和验证码
String phone = loginVo.getPhone();
String code = loginVo.getCode();
//2.判断手机号和验证码是否为空
if (StringUtils.isBlank(phone) || StringUtils.isBlank(code)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
//3.判断手机验证码和输入的验证码是否一致
String redisCode = redisTemplate.opsForValue().get(phone);
if (!code.equals(redisCode)) {
throw new YyghException(ResultCodeEnum.CODE_ERROR);
}
//4.绑定手机号
UserInfo userInfo = null;
if (!StringUtils.isBlank(loginVo.getOpenid())) {
userInfo = this.selectWxInfoByOpenId(loginVo.getOpenid());
if (null != userInfo) {
userInfo.setPhone(loginVo.getPhone());
updateById(userInfo);
} else {
throw new YyghException(ResultCodeEnum.DATA_ERROR);
}
}
//5.如果userinfo为空,进行正常的手机登录
if (userInfo == null) {
//判断是否第一次登录:根据手机号查询数据库,如果不存在相同手机号就是第一次登录
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserInfo::getPhone, phone);
userInfo = baseMapper.selectOne(queryWrapper);
if (userInfo == null) {
//第一次登录,添加信息到数据库
userInfo = new UserInfo();
userInfo.setName("");
userInfo.setPhone(phone);
userInfo.setStatus(1);
baseMapper.insert(userInfo);
}
}
//6.校验是否禁用
if (userInfo.getStatus() == 0) {
throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
}
//7.不是第一次,直接登录
//返回登录信息
//返回登录用户名
//返回token信息
Map<String, Object> map = new HashMap<>();
String name = userInfo.getName();
if (StringUtils.isBlank(name)) {
name = userInfo.getNickName();
}
if (StringUtils.isBlank(name)) {
name = userInfo.getPhone();
}
map.put("name", name);
// token生成
String token = JwtHelper.createToken(userInfo.getId(), name);
map.put("token", token);
return map;
}
我们重写第4步代码。
解决思路如下:
在第4步绑定手机号的时候,我们先根据手机号把用户给查询出来。
- 如果用户存在且还没设置openid,那么我们就将前端传过来的openid设置给该用户,完成绑定;
- 如果用户存在且也已经设置了openid,那么我们直接抛一个异常,提示前端该手机已经绑定了微信号。
- 如果用户不存在,那么我们利用openid,phone直接注册一个新用户并登录,实现注册,绑定,登录一条龙。