1.注册
前端页面点击注册按钮,跳转到注册页面,reg.html。
页面中输入账号密码,手机号,点击发送验证码。后台生产验证码并发送
后台接收:
@ResponseBody
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone) {
//发送验证码接口, 逻辑是reg.html前端点击发送验证码后,会发送请求到这里,
//1、接口防刷,先获取到redis中的存的验证码 存储格式,验证码+时间(毫秒)976812_1638924445145
String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
//如果验证码不为空
if (!StringUtils.isEmpty(redisCode)) {
//获取存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码
long currentTime = Long.parseLong(redisCode.split("_")[1]);
//以毫秒为单位,如果当前时间-存验证码的时间小于60s,就不能再发
if (System.currentTimeMillis() - currentTime < 60000) {
//60s内不能再发 返回错误信息给前端
return R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(),
BizCodeEnume.SMS_CODE_EXCEPTION.getMsg());
}
}
//如果为空 就新生成验证码
//2、验证码的再次效验 redis.存key-phone,value-code
//math.random 生成0.0-1的随机double值
int code = (int) ((Math.random() * 9 + 1) * 100000);
String codeNum = String.valueOf(code);//int转为字符串
//存入redis的验证码为实际验证码+当前时间
String redisStorage = codeNum + "_" + System.currentTimeMillis();
//存入redis,防止同一个手机号在60秒内再次发送验证码 过期时间为1分钟
stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,
redisStorage,1, TimeUnit.MINUTES);
//调用远程服务发送短信
//thirdPartFeignService.sendCode(phone, codeNum);
System.out.println(codeNum);
return R.ok();
}
短信申请不通过,没用。输入完验证码,点击注册,后台开始交验,校验通过将用户信息保存到数据库。然后跳转到登录页面。
后端接收:
/**
*
* TODO: 重定向携带数据:利用session原理,将数据放在session中。
* TODO:只要跳转到下一个页面取出这个数据以后,session里面的数据就会删掉
* TODO:分布下session问题
* RedirectAttributes:重定向也可以保留数据,不会丢失
* 用户注册
* @return
*/
//reg.html前端点击注册后会跳转到这里。验证码正确的话就调用member服务注册,将用户信息存到数据库
//这里采用了JSR303参数校验 @valid+BindingResult
//@Valid 和 BindingResult 是一 一对应的,如果有多个@Valid,那么每个@Valid后面都需要添加BindingResult用于接收bean中的校验信息
//form表单传过来的数据,不需要requestbody注解
@PostMapping(value = "/register")
public String register(@Valid UserRegisterVo vos, BindingResult result,
RedirectAttributes attributes) {
//如果有错误回到注册页面 如果校验有错误
if (result.hasErrors()) {
//封装成一个map,key就是参数值,value是错误信息
//双冒号是引用类的方法
Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
//存到attributes,返回给前端
attributes.addFlashAttribute("errors",errors);
//效验出错回到注册页面
return "redirect:http://auth.gulimall.com/reg.html";
}
//没有错误,1、效验验证码
String code = vos.getCode();
//获取存入Redis里的验证码
String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());
if (!StringUtils.isEmpty(redisCode)) {
//截取字符串
if (code.equals(redisCode.split("_")[0])) {
//删除验证码;令牌机制
stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX+vos.getPhone());
//验证码通过,真正注册,调用远程服务进行注册 将注册数据存到数据库
//成功的话返回的code为0
R register = memberFeignService.register(vos);
if (register.getCode() == 0) {
//成功到登录页面
return "redirect:http://auth.gulimall.com/login.html";
//失败的话从memberFeignService.register()的返回中获取错误信息
} else {
//失败
Map<String, String> errors = new HashMap<>();
errors.put("msg", register.getData("msg",new TypeReference<String>(){}));
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
//else就是输入的验证码和存在redis的验证码不一样
} else {
//效验出错回到注册页面
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
//else就是从redis获取的验证码为空,可能是过期了
} else {
//效验出错回到注册页面
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}
远程调用memberFeignService的register方法 非form表单传过来的数据,需要requestbody注解
controller:
@PostMapping(value = "/register")
public R register(@RequestBody MemberUserRegisterVo vo) {
try {
//调用service完成注册 可能会有异常,根据异常进行返回
memberService.register(vo);
//如果电话号码重复就返回电话的异常
} catch (PhoneException e) {
return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION.getCode(),
BizCodeEnume.PHONE_EXIST_EXCEPTION.getMsg());
//如果用户名存在就返回用户名的错误
} catch (UsernameException e) {
return R.error(BizCodeEnume.USER_EXIST_EXCEPTION.getCode(),
BizCodeEnume.USER_EXIST_EXCEPTION.getMsg());
}
//没有异常就返回正常
return R.ok();
}
service:
@Override
public void register(MemberUserRegisterVo vo) {
MemberEntity memberEntity = new MemberEntity();
//设置默认等级 从等级表中查询1级等级信息
MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
memberEntity.setLevelId(levelEntity.getId());
//设置其它的默认信息
//检查用户名和手机号是否唯一。感知异常,异常机制
//判断前端传过来的用户输入的手机号和用户名是不是已经存在数据库了,存在的话就不能注册成功
//这两个方法如果有异常就会抛出异常,在这里没处理,就回继续向上抛出给调用者,也就是MemberController的register方法。交给他去处理异常
checkPhoneUnique(vo.getPhone());
checkUserNameUnique(vo.getUserName());
//如果没存在,就存到对象中
memberEntity.setNickname(vo.getUserName());
memberEntity.setUsername(vo.getUserName());
//密码进行MD5加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode(vo.getPassword());
//密码
memberEntity.setPassword(encode);
//电话
memberEntity.setMobile(vo.getPhone());
memberEntity.setGender(0);
memberEntity.setCreateTime(new Date());
//保存数据
this.baseMapper.insert(memberEntity);
}
//判断手机号是否重复
public void checkPhoneUnique(String phone) throws PhoneException {
//如果重复 ,查出来的phoneCount就大于0,
Integer phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (phoneCount > 0) {
throw new PhoneException();
}
}
//判断用户名是否重复
@Override
public void checkUserNameUnique(String userName) throws UsernameException {
//如果重复,查出来的usernameCount就大于0.
Integer usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
if (usernameCount > 0) {
throw new UsernameException();
}
}
登录
1.账号密码登录:
前端点击登录,后端验证,验证通过回到gulimall.com
后端接收:
//用户名和密码登录 前端点击登录按钮 到这里 传过来用户名和密码
// 1. form表单往后端提交数据注意三点
// 1.所有获取用户输入标签都应该放在form表单里面
// 2.action属性控制往哪儿提交,method一般都是设置成post
// 3.提交按钮必须一定是type='submit'
// 4.input标签必须有name属性,形成键值对
@PostMapping(value = "/login")
public String login(UserLoginVo vo, RedirectAttributes attributes, HttpSession session) {
//远程登录 调用远程服务 传入前端传过来的用户名和密码
R login = memberFeignService.login(vo);
//远程服务返回0说明正常登录
if (login.getCode() == 0) {
MemberResponseVo data = login.getData("data", new TypeReference<MemberResponseVo>() {});
session.setAttribute(LOGIN_USER,data);
log.info("登录成功:用户信息:{}",data.toString());
return "redirect:http://gulimall.com";
} else {
//会返回错误信息,比如账号密码错误,获取到msg中的错误信息,封装
Map<String,String> errors = new HashMap<>();
errors.put("msg",login.getData("msg",new TypeReference<String>(){}));
//存起来返回给前端
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}
远程调用memberFeignService的login方法
controller:
//账号密码登录auth-server远程调用到这里
//这里不是前端表单穿过来的对象,所以必须加@RequestBody注解,将传过来的json数据转为对象
@PostMapping(value = "/login")
public R login(@RequestBody MemberUserLoginVo vo) {
//调用service 传入用户名和密码
MemberEntity memberEntity = memberService.login(vo);
//没返回空说明登陆成功了
if (memberEntity != null) {
return R.ok().setData(memberEntity);
} else {
//账号或密码错误
return R.error(BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getCode(),
BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
}
}
service:
//账号和密码登录服务
@Override
public MemberEntity login(MemberUserLoginVo vo) {
//获取到用户名和密码
String loginacct = vo.getLoginacct();
String password = vo.getPassword();
//查询是否存在 匹配用户名或者电话都可以
//1、去数据库查询 SELECT * FROM ums_member WHERE username = ? OR mobile = ?
MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>()
.eq("username", loginacct).or().eq("mobile", loginacct));
if (memberEntity == null) {
//登录失败 返回空
return null;
} else {
//获取到数据库里的password
String password1 = memberEntity.getPassword();
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//进行密码匹配
boolean matches = passwordEncoder.matches(password, password1);
if (matches) {
//登录成功
return memberEntity;
}
}
return null;
}
2.微博登录:
点击微博,会先发送一个链接去微博获取一个code值,通过这个code值再去微博获取token。
redirect_uri 是微博配置的重定向路径,意思就是获取到code之后的跳转路径。
这里路径进行了转换,不然会报重定向地址不匹配的错误21322错误。转换地址:https://www.anspoon.com/url-conveter。
后端接收:
//微博登录到这里 传过来一个code 通过这个code去微博获得token
@GetMapping(value = "/oauth2.0/weibo/success")
public String weibo(@RequestParam("code") String code, HttpSession session) throws Exception {
Map<String, String> map = new HashMap<>();
//微博申请的
map.put("client_id","2788393313");
map.put("client_secret","427c0d56b7f4f2827c6f0f337548a95a");
map.put("grant_type","authorization_code");
map.put("redirect_uri","http://auth.gulimall.com/oauth2.0/weibo/success");
map.put("code",code);
//1、根据用户授权返回的code换取access_token
HttpResponse response = HttpUtils.doPost("https://api.weibo.com", "/oauth2/access_token", "post", new HashMap<>(), map, new HashMap<>());
//2、处理 获取到token
if (response.getStatusLine().getStatusCode() == 200) {
//获取到了access_token,转为通用社交登录对象
String json = EntityUtils.toString(response.getEntity());
//String json = JSON.toJSONString(response.getEntity());
SocialUser socialUser = JSON.parseObject(json, SocialUser.class);
//知道了哪个社交用户
//1)、当前用户如果是第一次进网站,自动注册进来(为当前社交用户生成一个会员信息,以后这个社交账号就对应指定的会员)
System.out.println(socialUser.getAccess_token());
//调用远程服务 登录或者注册这个社交用户 传入通用社交对象
R oauthLogin = memberFeignService.oauthLogin(socialUser);
if (oauthLogin.getCode() == 0) {
MemberResponseVo data = oauthLogin.getData("data", new TypeReference<MemberResponseVo>() {});
log.info("登录成功:用户信息:{}",data.toString());
//1、第一次使用session,命令浏览器保存卡号,JSESSIONID这个cookie
//以后浏览器访问哪个网站就会带上这个网站的cookie
//TODO 1、默认发的令牌。当前域(解决子域session共享问题)
//TODO 2、使用JSON的序列化方式来序列化对象到Redis中
session.setAttribute(AuthServerConstant.LOGIN_USER,data);
//2、登录成功跳回首页
return "redirect:http://gulimall.com";
} else {
return "redirect:http://auth.gulimall.com/login.html";
}
} else {
return "redirect:http://auth.gulimall.com/login.html";
}
}
远程调用memberFeignService的oauthLogin方法
controller:
//微博登录
@PostMapping(value = "/oauth2/login")
public R oauthLogin(@RequestBody SocialUser socialUser) throws Exception {
MemberEntity memberEntity = memberService.login(socialUser);
if (memberEntity != null) {
return R.ok().setData(memberEntity);
} else {
return R.error(BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getCode(),
BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
}
}
service:
//微博登录方法
@Override
public MemberEntity login(SocialUser socialUser) throws Exception {
//具有登录和注册逻辑
//先获取到微博返回的用户uid
String uid = socialUser.getUid();
//通过id查询一下数据库是否有记录
//MemberEntity memberEntity = this.getById(socialUser.getUid());
MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("social_uid", uid));
//1、判断当前社交用户是否已经登录过系统
if (memberEntity != null) {
//能获取到对象说明这个用户已经注册过
//更新用户的访问令牌的时间和access_token
MemberEntity update = new MemberEntity();
update.setId(memberEntity.getId());
update.setAccessToken(socialUser.getAccess_token());
update.setExpiresIn(socialUser.getExpires_in());
this.baseMapper.updateById(update);
memberEntity.setAccessToken(socialUser.getAccess_token());
memberEntity.setExpiresIn(socialUser.getExpires_in());
return memberEntity;
} else {
//2、没有查到当前社交用户对应的记录我们就需要注册一个
MemberEntity register = new MemberEntity();
//3、查询当前社交用户的社交账号信息(昵称、性别等)
Map<String,String> query = new HashMap<>();
query.put("access_token",socialUser.getAccess_token());
query.put("uid",socialUser.getUid());
HttpResponse response = HttpUtils.doGet("https://api.weibo.com", "/2/users/show.json", "get", new HashMap<String, String>(), query);
//如果查询成功就保存一个新的用户
if (response.getStatusLine().getStatusCode() == 200) {
//查询成功 查询出来的数据先转为json字符串
String json = EntityUtils.toString(response.getEntity());
//然后json字符串转为对象,这是没有定义实体类的
JSONObject jsonObject = JSON.parseObject(json);
String name = jsonObject.getString("name");
String gender = jsonObject.getString("gender");
String profileImageUrl = jsonObject.getString("profile_image_url");
register.setNickname(name);
register.setGender("m".equals(gender)?1:0);
register.setHeader(profileImageUrl);
register.setCreateTime(new Date());
register.setSocialUid(socialUser.getUid());
register.setAccessToken(socialUser.getAccess_token());
register.setExpiresIn(socialUser.getExpires_in());
//把用户信息插入到数据库中
this.baseMapper.insert(register);
}
//获取用户信息失败的话就返回空对象
System.out.println(response.getStatusLine().getStatusCode());
return register;
}
}