【easylive】登录注册问题解决&思路分享

【easylive】登录注册问题解决&思路分享

【easyLive】登录注册分享

Easylive是一个来自B站知名UP主"程序员老罗"的优质开源项目,这个项目是一个模仿bibi的项目,代码只有关键逻辑部分,没有源代码,只用于学习分享,后续业务俺会持续更新喔

这篇文章讲述了easylive的验证码登录注册部分,涉及主要关键逻辑和我的一些对项目的思考以及完善,欢迎大家交流学习呀

文章目录

【easyLive】登录注册分享登录注册验证码逻辑获取验证码检验验证码

注册逻辑登录逻辑自动登录退出登录admin登录

登录注册

这部分web和admin逻辑有一点点不一样,web是大头,先说web最后一部分说admin

验证码逻辑

获取验证码

下图为后端返回数据,图片是一段base64格式图片,checkCodeKey为缓存redis的key(常量+checkCodeKey)

使用captcha插件直接生成验证码

1.6.21.6.2

com.github.whvcse

easy-captcha

${captcha.verion}

我的jdk版本的21使用的时候会报错

ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");

我的方法是增加一个依赖项可以正常使用,插入刷新maven就可以了

org.openjdk.nashorn

nashorn-core

15.4

整体思路

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 = new HashMap<>();

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[

🌸 相关推荐 🌸

常山赵子龙来自哪里?航拍河北正定古城
bte365娱乐场

常山赵子龙来自哪里?航拍河北正定古城

📅 10-05 👀 610
亚马逊中国
bte365娱乐场

亚马逊中国

📅 09-11 👀 4599
G20型气镐
bte365娱乐场

G20型气镐

📅 07-31 👀 9282