【easyLive】登录注册分享
Easylive是一个来自B站知名UP主"程序员老罗"的优质开源项目,这个项目是一个模仿bibi的项目,代码只有关键逻辑部分,没有源代码,只用于学习分享,后续业务俺会持续更新喔
这篇文章讲述了easylive的验证码登录注册部分,涉及主要关键逻辑和我的一些对项目的思考以及完善,欢迎大家交流学习呀
文章目录
【easyLive】登录注册分享登录注册验证码逻辑获取验证码检验验证码
注册逻辑登录逻辑自动登录退出登录admin登录
登录注册
这部分web和admin逻辑有一点点不一样,web是大头,先说web最后一部分说admin
验证码逻辑
获取验证码
下图为后端返回数据,图片是一段base64格式图片,checkCodeKey为缓存redis的key(常量+checkCodeKey)
使用captcha插件直接生成验证码
我的jdk版本的21使用的时候会报错
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
我的方法是增加一个依赖项可以正常使用,插入刷新maven就可以了
整体思路
1、使用redis缓存验证码,key,value格式,key为常量 + (String key = uuid.toString()),value 即为验证码的答案。同时设置失效时间,之前做的项目都是存在session中的
@RequestMapping("/checkCode")
public ResponseVO checkCode(){
//参数是生成图的长,宽 px
ArithmeticCaptcha captcha = new ArithmeticCaptcha(100,42);
//code 为计算答案
String code = captcha.text();
//Redis的key
String CheckCodeKey = redisComponent.saveCheckCode(code);
//checkCodeBase64是base64格式的图片
String checkCodeBase64 = captcha.toBase64();
Map
result.put("checkCode",checkCodeBase64);
result.put("checkCodeKey",CheckCodeKey);
return getSuccessResponseVO(result);
}
redisComponent.saveCheckCode(code);逻辑如下
为什么要用uuid呢?
如果直接使用REDIS_KEY_CHECK_CODE Redis只用一个key了每个用户验证码都用这个key,会覆盖其用户的,所以key要加一个唯一标识
public String saveCheckCode(String code){
UUID uuid = UUID.randomUUID();
String key = uuid.toString();
redisUtils.setex(Constants.REDIS_KEY_CHECK_CODE + key,code,Constants.REDIS_KEY_EXPIRE_ONE_MIN*10);
return key;
}
检验验证码
在登录注册逻辑中需要对验证码进行检验,也就是说要从Redis中取出验证码的答案(Redis存的是验证码计算式的答案)
if(!redisComponent.getCheckCode(checkCodeKey).equalsIgnoreCase(checkCode)){
throw new BusinessException("图片验证码不正确");
}
public String getCheckCode(String checkCodeKey){
return (String) redisUtils.get(Constants.REDIS_KEY_CHECK_CODE+checkCodeKey);
}
这部分比较简单,原项目中忘记考虑一个点,如果验证码过期了,redisComponent.getCheckCode会报空指针错误,用户看到就是网络异常(ExceptionHandler处理返回前端的),我是这样改了一下的
try {
if (!redisComponent.getCheckCode(checkCodeKey).equalsIgnoreCase(checkCode)) {
throw new BusinessException("图片验证码不正确");
}
//业务逻辑
} catch (NullPointerException e) {
throw new BusinessException("验证码已过期");
}
注册逻辑
这部分其实没有什么关键的点,就是正常将用户信息写入数据库就OK了,新学到一个注解,@Email可以检验邮箱正确性
@NotEmpty @Email @Size(max = 150) String email,
前端已经校验参数了,后端再校验一遍,放君子不防止小人(防止直接调用后端接口)
@RequestMapping("/register")
public ResponseVO register(@NotEmpty @Email @Size(max = 150) String email,
@NotEmpty @Size String nickName,
@NotEmpty String password,
@NotEmpty String checkCodeKey,
@NotEmpty String checkCode)
登录逻辑
使用token来进行持久化,具体方式是使用Redis来缓存用户信息即TokenInfoDto类,并将这个TokenInfoDto返回给前端。返回的信息在用户中心都要用到,之后的业务需要这些信息的时候也可以在Redis中直接取出了
TokenInfoDto tokenInfoRes = redisComponent.saveTokenInfo(tokenInfoDto);
public TokenInfoDto saveTokenInfo(TokenInfoDto tokenInfoDto){
String token = UUID.randomUUID().toString();
tokenInfoDto.setExpireAt(System.currentTimeMillis()+Constants.REDIS_KEY_EXPIRE_ONE_DAY*7l);
tokenInfoDto.setToken(token);
redisUtils.setex(Constants.REDIS_KEY_TOKEN_WEB+token,tokenInfoDto,Constants.REDIS_KEY_EXPIRE_ONE_DAY*7l);
return tokenInfoDto;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class TokenInfoDto implements Serializable {
private String userId;
private String nickName;
private String avatar;
private Long expireAt;
private String token;
private Integer fansCount;
private Integer currentCoinCount;
private Integer focusCount;
}
关键点
token 对应的值 “token”: "5af7be1e-8a37-4450-870b-6f8e785c8504"对应是就是String token = UUID.randomUUID().toString(); 用来做Redis缓存的key来维持登录状态,然后将这个token存在Cookie中,这个前端可以存,但是后端都干了
//后端将token存入Cookie中的方法
protected void saveCookie(HttpServletResponse response,String token){
Cookie cookie = new Cookie(Constants.TOKEN_WEB,token);
//设置失效时间7天
cookie.setMaxAge((Constants.REDIS_KEY_EXPIRE_ONE_DAY/1000)*7);
//"/" 表示根路径,意味着该 Cookie 对该域名下的所有路径都有效(如 /login、/admin、/api/user 等都会带上这个 Cookie)。
cookie.setPath("/");
response.addCookie(cookie);
}
但是这么简单粗暴就会出现一个问题String token = UUID.randomUUID().toString();token的值是随机生成的UUID,也就是即使一个用户,他多次登录在Redis中就是缓存多份,占用空间,但是不影响Cookie,因为Cookie的key是token每次登录都会覆盖之前的Token,于是使用这段代码,在登录后将之前该用户的的Redis key清除掉
这一段其实有点难懂,就是在一次新的登录之前,他请求头携带的Cookie是上次的登录信息,对吧,就要在登录后在Redis中删除这份缓存,即代码中finally部分
try {
if (!redisComponent.getCheckCode(checkCodeKey).equalsIgnoreCase(checkCode)) {
throw new BusinessException("图片验证码不正确");
}
String ip = getIpAddr();
TokenInfoDto tokenInfoDto = userInfoService.login(email, password, ip) ;
saveCookie(response, tokenInfoDto.getToken());
//TODO 设置粉丝硬币数关注数
return getSuccessResponseVO(tokenInfoDto);
} catch (NullPointerException e) {
throw new BusinessException("验证码已过期");
}
finally {
redisComponent.cleanCheckCode(checkCodeKey);
String token = null;
Cookie[] cookies = request.getCookies();
//用户退出后所有Cookie都清除了,下次登录不这样会报空的
if(cookies!=null){
for(Cookie cookie : cookies){
if(cookie.getName().equals(Constants.TOKEN_WEB)){
token=cookie.getValue();
}
}
redisComponent.cleanToken(token);
}
}
我新加了一个if(cookies!=null) ,老罗是没有的,为什么要这样写呢,就是有一个bug,如果浏览器是第一次登录,Cookie中是不是就没有token呀,Cookie[] cookies = request.getCookies(); 获得的就是null,这样的话就会报空指针错误,所以要提前判断一下
自动登录
这是我第一次接触自动登录的功能,我写登录接口的时候其实就在思考,为什么要把token信息给存在Cookie中呢?每次前端传参把token做在请求头中不就OK了嘛,好像这个自动登录就用到了
//这个我觉得比较帅,不需要前端传HttpServletRequest在后端也可以直接获取
protected TokenInfoDto getTokenInfoDto(){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader(Constants.TOKEN_WEB);
return redisComponent.getTokenInfoDtoByToken(token);
}
@RequestMapping("/autoLogin")
public ResponseVO autoLogin(HttpServletResponse response){
TokenInfoDto tokenInfoDto = getTokenInfoDto();
if(tokenInfoDto==null){
return getSuccessResponseVO(null);
}
//这段的业务代码就是如果用户又4登录了就延迟Cookie过期时间,在bibi真的很像,经常自动登录就不用手动登录
if(tokenInfoDto.getExpireAt()-System.currentTimeMillis() < Constants.REDIS_KEY_EXPIRE_ONE_DAY){
TokenInfoDto tokenInfoDto1 = redisComponent.saveTokenInfo(tokenInfoDto);
saveCookie(response,tokenInfoDto1.getToken());
}
//TODo 设置硬币粉丝关注
return getSuccessResponseVO(tokenInfoDto);
}
我不懂前端,我猜这部分的流程应该是这样的,用户打开页面应该先会请求这个接口autoLogin如果返回null的话才会请求login接口
退出登录
@RequestMapping("/logout")
public ResponseVO logout(HttpServletResponse response){
cleanCookie(response);
return getSuccessResponseVO(null);
}
这就很简单了,清除Redis信息,清除Cookie信息
admin登录
第一点区别就是admin的账号信息是在yml文件中,而web在数据库中需要查库
admin:
account: admin
password: admin123
//用这个congig类就可以用admin的账户了
@Configuration
@Data
public class AppConfig {
@Value("${project.folder}")
private String projectFolder;
@Value("${admin.account}")
private String AdminAccount;
@Value("${admin.password}")
private String adminPassword;
}
其他情况和web就差不多了,记得改一下Redis和Cookie的key就OK了
public class Constants {
public static final String REDIS_KEY_PREFIX = "miniBili:";
public static final String REDIS_KEY_CHECK_CODE = REDIS_KEY_PREFIX + "checkCode";
public static final String REDIS_KEY_TOKEN_WEB = REDIS_KEY_PREFIX + "token:web";
public static final String REDIS_KEY_TOKEN_ADMIN = REDIS_KEY_PREFIX + "token:admin";
public static final String TOKEN_WEB = "token";
public static final String TOKEN_ADMIN = "adminToken";
public static final Integer REDIS_KEY_EXPIRE_ONE_MIN = 1000 * 60;
public static final Integer REDIS_KEY_EXPIRE_ONE_DAY = 1000 * 60 * 60 *24;//毫秒
public static final Integer LENGTH_10 = 10;
public static final Integer ONE = 1;
public static final Integer ZERO = 0;
}
```p[