首先要讲一下面向切面编程也就是aop编程,我们在一个类前面加上@Aspect,就说明这是一个这个类中的方法是切面,@Aspect等于Advice与Joinpoint两部分组成。

@Aspect
@Component("needTokenAspect")

@Before("@annotation(com.akaedx.annotation.NeedToken)")//这是一个advice

在Spring AOP中支持4中类型的通知:

1:before advice 在方法执行前执行。

2:after returning advice 在方法执行后返回一个结果后执行。

3:after throwing advice 在方法执行过程中抛出异常的时候执行。

4:Around advice 在方法执行前后和抛出异常时执行,相当于综合了以上三种通知。

当一个方法执行的时候,Spring 能够拦截正在执行的方法,在方法执行的前或者后增加额外的功能和处理。

@annotation(com.akaedx.annotation.NeedToken)

@annotation就是表明了任何加@NeedToken注解的方法,这样系统就可以轻松找到需要token的方法,这个就是切点

我们找到切点后该怎么做呢,是不是要通过一定的方法去实现我们的业务啊

那么这个方法就是我们说的这个Joinpoint

public void NeedTokenDo(Joinpoint point){
 Method method = ((MethodSignature) point.getSignature()).getMethod();
            NeedToken needToken = method.getAnnotation(NeedToken.class);
try{
  if(NeedToken==Null){
    return;
    }
  if(needToken.checkLogin()){
    checkLogin();
    } 
  }catch(Throwable e){
    throw new BussinessExecption(Code.DEFAULT_ERROR);
  }
}

他其实就是一个切面,我们在由Spring注入一个point,然后每个前面标注@NeedToken的方法都会执行这样一个切面,去验证一下该方法是否需要Token,这就是切面编程的作用,把一些需要写在各个方法前的一些动作放在一个切面类里,在运行之前之后或者抛异常时执行,简化代码提高运行效率。

我们的业务为什么要用这样一个切面编程呢

1.png比如说bilibili网页,有些业务比如收藏功能需要在登录之后去实现,这样我们在Controller这个包下可以写对应的方法,不过在这个方法前要加@NeedToken这个注解。

APP利用token机制进行身份认证

用户在登录APP时,APP端会发送加密的用户名和密码到服务器,服务器验证用户名和密码,如果验证成功,就会生成相应位数的字符产作为token存储到服务器中,并且将该token返回给APP端。

以后APP再次请求时,凡是需要验证的地方都要带上该token,然后服务器端验证token,成功返回所需要的结果,失败返回错误信息,让用户重新登录。其中,服务器上会给token设置一个有效期,每次APP请求的时候都验证token和有效期。

讲完了AOP编程,我们现在来讲具体登录流程

首先还是前端用户输入的信息变成JSON,经过反序列化后变成LoginRequestDTO(下面这个代码就是LoginRequestDTO这个类)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequestDTO {
    private String email;
    private String password;
}

这个DTO就包含了,用户输入的账号密码,我们后端在收到这个DTO之后呢,要送到Controller层,先去验明账号密码是否跟数据库里的相同(本代码疑似没有写这个业务)

 @PostMapping("/login")
    public Response<SuccessLoginDTO> login(LoginRequestDTO requestDTO){
        SuccessLoginDTO login=userService.login(requestDTO);

        return Response.ok(login);

这个就是Controller层中关于登陆部分的方法

注解 @PostMapping("/login"):这个注解表示这个方法会处理发送到 /login 路径的 HTTP POST 请求。就是如果这个业务是登录业务,那从前端发来的数据会首先发到Controller层的这个方法中。

 SuccessLoginDTO login=userService.login(requestDTO);

如果没有问题的话就会给前端返回登陆成功这个信息

我们登录成功后是要给这个app页面一个token,用来验明下一次操作页面时是否登录

这句代码讲的是传进来的requestdto先被传到Controller层然后在Controller层调用userService这个接口内的方法去比较数据库里的数据

public interface UserService {
        void regisiter(RegisterRequestDTO requestDTO);
        SuccessLoginDTO login(LoginRequestDTO loginRequestDTO);

}

注:UserService接口里的SuccessLoginDTO与userController里的SuccessLoginDTO不一样,前者是以下的方法Login的返回类型,后者是对象

 @Override
    public SuccessLoginDTO login(LoginRequestDTO loginRequestDTO) {
        QueryWrapper<UserInfo> queryWrapper=new QueryWrapper<>();
        queryWrapper
                .lambda()
                .eq(UserInfo::getEmail,loginRequestDTO.getEmail())
                .eq(UserInfo::getPassword,StringTools.encodeMd5((loginRequestDTO.getPassword())));
        UserInfo userInfo=userInfoMapper.selectOne(queryWrapper);
        if(userInfo ==null){
            throw new BusinessException(CodeEnum.LOGIN_FAILED_01);
        }
        SuccessLoginDTO successLoginDTO=new SuccessLoginDTO();
        successLoginDTO.setLoginUser(SuccessLoginDTO.LoginUser.PO2LOGIN_UESER(userInfo));
        String token = UUID.randomUUID().toString();
        successLoginDTO.setToken(token);
        redisComment.token2UserId(token, successLoginDTO.getLoginUser().getId());
        redisComment.userId2SuccessLoginDTO(successLoginDTO.getLoginUser().getId(), successLoginDTO);
        return successLoginDTO;
    }

这个Login方法最终返回类型是SuccessLoginDTO,在登录信息从前端传到Controller层之后,Controller层调用Service层的接口也就是login方法去比较,如果错误就抛异常,没问题的话会创建一个SuccessLoginDTO引用,把没问题的UserInfo相关信息赋给SuccessLoginDTO,因为SuccessLogin这个类含loginUser与token,所以我们在这个方法中用UUID生成了一串字符来作为token一并赋给successLoginDTO,然后返回successLoginDTO

return Response.ok(login);

最后回到cotroller层把SuccessLoginDTO类型的login与返回类的codeEnum一并返回到前端(如下图所示)