需求分析
产品原型
5个基础字段,这4个还有id
业务异常,数据异常,都是baseexception的子类
上面是登录接口的功能
登录校验功能
这样写看不懂
怎么拿到这个id,是缓存?
为什么拿到id就说明成功
package com.sky.interceptor;
import com.sky.constant.JwtClaimsConstant;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt,重写这个方法preHandle
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
// 这里看不懂
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
log.info("当前员工id:", empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
遇到的问题
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
at
解决
在启动类 或 配置类 添加注解@EnableWebMvc
密码加密
完成错误5次,账号锁定,1小时后试
redis起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
package com.sky.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration//配置类
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//自定义RedisTemplate
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//指定序列化方式
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
return template;
}
}
@Test
public void randomFun(){
String s = RandomStringUtils.randomAlphabetic(6);
System.out.println("======="+s);
for (int i=0;i<20;i++){
String string = RandomStringUtils.randomAlphabetic(5);
System.out.println(string);
}
}
登录的完整代码
package com.sky.service.impl;
import com.sky.constant.MessageConstant;
import com.sky.constant.StatusConstant;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.entity.Employee;
import com.sky.exception.AccountLockedException;
import com.sky.exception.AccountNotFoundException;
import com.sky.exception.LoginFailedException;
import com.sky.exception.PasswordErrorException;
import com.sky.mapper.EmployeeMapper;
import com.sky.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class EmployeeServiceImpl implements EmployeeService {
private static final String LOGiN_ERROR = "login:";
@Autowired
private EmployeeMapper employeeMapper;
@Autowired
private RedisTemplate redisTemplate;
/**
* 员工登录
*
* @param employeeLoginDTO
* @return
*/
@Override
public Employee login(EmployeeLoginDTO employeeLoginDTO) {
String username = employeeLoginDTO.getUsername();
String password = employeeLoginDTO.getPassword();
log.info("=====登录=====传参username{},password{}", username, password);
// 校验员工账号是否被锁定 todo
Object o = redisTemplate.opsForValue().get("login_lock_" + username);
if (ObjectUtils.isNotEmpty(o)) {
//不为空,说明被锁定了
throw new LoginFailedException(MessageConstant.ACCOUNT_LOCKED_NOW);
}
//1、根据用户名查询数据库中的数据
Employee employee = employeeMapper.getByUsername(username);
//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
//2判断密码是否为空
if (employee == null) {
//账号不存在
log.info("查询到用户名不存在,抛出异常");
throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
// throw new DataException(MessageConstant.ACCOUNT_NOT_FOUND);
}
//3密码比对
// TODO 后期需要进行md5加密,然后再进行比对
password = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
if (!password.equals(employee.getPassword())) {
log.info("密码对比错误");
//密码错误
// 3.1记录员工密码错误的标记,并设置密码有效期为5分钟
redisTemplate.opsForValue().set(getKey(username), "123", 5, TimeUnit.MINUTES);
//3.2获取该员工的错误标记,如果个数》=5,设置账号锁定的标记
//拿到这个开头的key,结果是集合
Set keys = redisTemplate.keys("login:" + username + "_*");
if (keys != null && keys.size() >= 5) {
log.info("员工5分钟内登录错误5次,账号锁定1小时");
//如果keys超过了5次,要锁定,设置一个key,设置一个有效期
redisTemplate.opsForValue().set("login_lock_" + username, "-", 1, TimeUnit.HOURS);
throw new LoginFailedException(MessageConstant.ACCOUNT_LOCKED_BEGIN);
}
//有异常,往上抛异常,到全局异常处理器
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
// 判断员工的账号状态是不是禁用
if (employee.getStatus().equals(StatusConstant.DISABLE)) {
log.info("登录账号被禁用");
//账号被锁定
throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
}
//3、返回实体对象
return employee;
}
/**
* 生成随机的key
*
* @param username
* @return
*/
private String getKey(String username) {
return "login:" + username + "_" + RandomStringUtils.randomAlphabetic(5);
}
}