个人复习使用 ,精简版
开发通行证主要包含:阿里云第三方服务发送验证码以及验证码校验功能,用户一键登录/注册功能,用户信息查询功能(数据库访问优化),分布式会话功能,会话拦截、状态激活拦截功能与AOP警告日志监控功能。
阿里云第三方服务发送验证码以及验证码校验功能
发送验证码:
controller层:
@Override
public GraceJSONResult getSMSCode(String mobile, HttpServletRequest request) {
//获取用户ip
String userIp = IPUtil.getRequestIp(request);
//根据用户的ip进行限制,限制用户在60秒内只能获得一次验证码
redis.setnx60s(MOBILE_SMSCODE + ":" + userIp, userIp);
//生成随机验证码并发送短信
String random = (int) ((Math.random() * 9 + 1) * 100000) + "";
smsUtils.sendSMS(mobile, random);
//把验证码存入redis,用于后续进行验证
// redis.set(MOBILE_SMSCODE + ":" + mobile, random, 30 * 60);
redis.set(MOBILE_SMSCODE + ":" + mobile, random);
return GraceJSONResult.ok();
}
- 限制短信发送频率:将ip信息写入缓存,60s失效。后续配置拦截器。
- 随机生成验证码,写入缓存,30min失效。并发送短信。
配置拦截器(访问controller前):判断缓存中是否存在ip信息
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取用户ip
String userIp = IPUtil.getRequestIp(request);
boolean keyIsExist = redis.keyIsExist(MOBILE_SMSCODE + ":" + userIp);
if(keyIsExist){
//TODO
GraceException.display(ResponseStatusEnum.SMS_NEED_WAIT_ERROR);//把错误信息抛给前端,抛的是exception,而不是json数据
// System.out.println("短信发送频率太高");
return false;
}
/**
* false:请求被拦截
* true:请求通过验证,放行
*/
return true;
}
注册拦截url:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public PassportInterceptor getPassportInterceptor() {
return new PassportInterceptor();
}
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getPassportInterceptor())
.addPathPatterns("/passport/getSMSCode");
}
}
注册、验证码校验:
@Override
public GraceJSONResult doLogin(@Valid RegistLoginBO registLoginBO,
BindingResult bindingResult,
HttpServletRequest request,
HttpServletResponse response) {
//0.判断BindingResult是否保存了验证错误的信息,如果有,那么需要返回
if (bindingResult.hasErrors()) {
Map<String, String> map = getErrors(bindingResult);
return GraceJSONResult.errorMap(map);
}
//1.校验验证码是否匹配
String mobile = registLoginBO.getMobile();//前端传来的用户输入信息
String smsCode = registLoginBO.getSmsCode();
String redisSMSCode = redis.get(MOBILE_SMSCODE + ":" + mobile);
if (StringUtils.isBlank(redisSMSCode) || !redisSMSCode.equalsIgnoreCase(smsCode)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.SMS_CODE_ERROR);
}
//2.查询数据库,判断该用户注册
AppUser user = userService.queryMobileIsExist(mobile);
if (user != null && user.getActiveStatus() == UserStatus.FROZEN.type) {
//如果用户存在,并且该用户被冻结,那么直接抛出异常,禁止登录
return GraceJSONResult.errorCustom(ResponseStatusEnum.USER_FROZEN);
}else if (user == null){
//如果用户没有注册过,则从mysql查询到的对象为null,需要将注册信息入库。
user = userService.createUser(mobile);
}
//3.保存用户分布式会话的相关操作
int userActiveStatus = user.getActiveStatus();
if(userActiveStatus != UserStatus.FROZEN.type) {
//保存token和user(json对象)到redis
String uToken = UUID.randomUUID().toString().trim();
redis.set(REDIS_USER_TOKEN + ":" +user.getId(),uToken);
redis.set(REDIS_USER_INFO + ":" +user.getId(), JsonUtils.objectToJson(user));
//保存用户token和id到cookie中
setCookie(request,response,"utoken",uToken,COOKIE_MONTH);
setCookie(request,response,"uid",user.getId(),COOKIE_MONTH);
}
// redis.del(MOBILE_SMSCODE + ":" + mobile);
//用户存在,状态正常。
return GraceJSONResult.ok(userActiveStatus);
}
- 对输入和redis验证码判空(@Valid和BindingResult )
- 判断与redis中是否相等->写入数据库或直接登录(无redis)
- 分布式会话:生成token->写入redis、写入cookie,后续设置拦截器验证token。
设置拦截器验证token:先判空(返回未登录),再核对(返回会话失效)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//也可以用cookie,但是安卓和小程序上没有cookie,因此用以下方法获token和id
String userId = request.getHeader("headerUserId");
String userToken = request.getHeader("headerUserToken");
//判断是否放行
Boolean run = verifyUserIdToken(userId, userToken,REDIS_USER_TOKEN);
System.out.println(run);
/**
* false:请求被拦截
* true:请求通过验证,放行
*/
return true;
}
public boolean verifyUserIdToken(String id,
String token,
String redisKeyPrefix){
if (StringUtils.isNotBlank(id)&&StringUtils.isNotBlank(token)){
String redisToken = redis.get(redisKeyPrefix+":"+id);
if(StringUtils.isBlank(id)){
GraceException.display(ResponseStatusEnum.UN_LOGIN);
}else {
if (!redisToken.equalsIgnoreCase(token)){
GraceException.display(ResponseStatusEnum.TICKET_INVALID);
return false;
}
}
}else {
GraceException.display(ResponseStatusEnum.UN_LOGIN);
return false;
}
return true;
}
用户信息查询功能(数据库访问优化)
先查询redis,若没有再查数据库,接着存redis。
@Override
public GraceJSONResult getAccountInfo(String userId) {
//0.判断参数不能为空
if(StringUtils.isBlank(userId)) {
return GraceJSONResult.errorCustom(ResponseStatusEnum.UN_LOGIN);
}
//1.根据userId查询用户信息
AppUser user = getUser(userId);
//2.返回用户信息(有些信息不用展示在前端)
UserAccountInfoVO userAccountInfo = new UserAccountInfoVO();
BeanUtils.copyProperties(user, userAccountInfo);
return GraceJSONResult.ok(userAccountInfo);
}
http://t.csdn.cn/358Nchttp://t.csdn.cn/358Nc缓存双写:
http://t.csdn.cn/rcXaghttp://t.csdn.cn/rcXag
AOP警告日志监控
对service加环绕通知,若执行时间过长,返回一个错误。
统一异常拦截:面向aop
/**
* 统一异常拦截处理
* 可以针对异常的类型进行捕获,然后返回json信息到前端
*/
@ControllerAdvice//面向aop
public class GraceExceptionHandler {
@ExceptionHandler(MyCustomException.class)
@ResponseBody
public GraceJSONResult returnMyException(MyCustomException e){
return GraceJSONResult.exception(e.getResponseStatusEnum());
}
}