在SpringBoot当中使用Spring AOP来实现权限验证和消息管理

引言

1. 权限验证

在之前的Blog项目当中,其实已经体验过权限验证了,不过那时候用的是SpringSecurity API来实现的方法,当没使用API时,可以使用Spring的AOP来进行权限验证了.其实也可以通过使用拦截器来实现登录权限验证,但是AOP相对而言更加灵活
消息管理

2. 消息管理

通过Srping的AOP可以在用户对业务进行操作的时候执行时能发送消息,从而使用户能接收到来自业务的消息提醒.


什么是Spring AOP?

实现步骤

1. 创建一个注解

  1. 登录验证
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalInterceptor {
    boolean checkLogin() default false;
}

这里涉及到的注解相关知识就不再赘述(主要是在写这个知识点的时候已经能看懂代码了所以不想再赘述了)
2. 发送提醒消息

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordUserMessage {
    MessageTypeEnum messageType();
}

2. 创建Aspect切面类

  1. 登录验证
    这里代码逻辑简单就不需要特别解释了
@Aspect
@Component
@Slf4j
public class GlobalOperationAspect {

    @Resource
    private RedisComponent redisComponent;

//	在ControllerMethod方法从@GlobalInterceptor自定义注解来标记切面,在ControllerMethod方法前执行该方法
    @Before("@annotation(com.easylive.annotation.GlobalInterceptor)")
    public void interceptoDto(JoinPoint joinPoint) {
        log.error("开始执行登录校验切点拦截");
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        GlobalInterceptor annotation = method.getAnnotation(GlobalInterceptor.class);
        if (Objects.isNull(annotation)) {
            return;
        }
        if (annotation.checkLogin()) {
            checkLogin();
        }
    }

    private void checkLogin() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(Constants.TOKEN_WEB);
        if (Objects.isNull(token)) {
            throw new BusinessException(ResponseCodeEnum.CODE_901);
        }
        TokenUserInfoDto tokenInfo = redisComponent.getTokenInfo(token);
        if (Objects.isNull(tokenInfo)) {
            throw new BusinessException(ResponseCodeEnum.CODE_901);
        }
    }
}
  1. 发送提醒消息
@Aspect
@Component
@Slf4j
public class UserMessageOperationAspect {

    @Resource
    private RedisComponent redisComponent;
    @Resource
    private UserMessageService userMessageService;

    private static final String PARAMETERS_VIDEO_ID = "videoId";
    private static final String PARAMETERS_ACTION_TYPE = "actionType";
    private static final String PARAMETERS_REPLY_COMMENTID = "replyCommentId";
    private static final String PARAMETERS_CONTENT = "content";
    private static final String PARAMETERS_AUDIT_REJECT_REASON = "reason";


    @Around("@annotation(com.easylive.annotation.RecordUserMessage)")
    public ResponseVO interceptorDto(ProceedingJoinPoint joinPoint) throws Throwable {
        ResponseVO proceed = (ResponseVO) joinPoint.proceed();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RecordUserMessage annotation = method.getAnnotation(RecordUserMessage.class);
		//在这里joinPoint.getArgs()获取的是ControllerMethod的实际参数值,method.getParameters()获取的是控制器方法的参数元数据(参数定义名称和类型)
        if (annotation != null) {
            saveMessage(annotation, joinPoint.getArgs(), method.getParameters());
        }
        return proceed;
    }

    private void saveMessage(RecordUserMessage recordUserMessage, Object[] args, Parameter[] parameters) {
        String videoId = null;
        Integer actionType = null;
        Integer replyCommentId = null;
        String content = null;

        for (int i = 0; i < parameters.length; i++) {
            if (PARAMETERS_VIDEO_ID.equals(parameters[i].getName())) {
                videoId = (String) args[i];
            } else if (PARAMETERS_ACTION_TYPE.equals(parameters[i].getName())) {
                actionType = (Integer) args[i];
            } else if (PARAMETERS_REPLY_COMMENTID.equals(parameters[i].getName())) {
                replyCommentId = (Integer) args[i];
            } else if (PARAMETERS_CONTENT.equals(parameters[i].getName())) {
                content = (String) args[i];
            }
        }
        MessageTypeEnum messageTypeEnum = recordUserMessage.messageType();
	/*
	 *这里还要单独设置一个条件判断是否为收藏是因为原来的doAction方法里注解默认是写LIKE的
	 *靠参数actionType来区分互动类型,互动只提示点赞和收藏不提示投币
	 *所以默认LIKE然后根据actionType参数来变更消息类型
	*/
        if (UserActionTypeEnum.VIDEO_COLLECT.getType().equals(actionType)) {
            messageTypeEnum = MessageTypeEnum.COLLECTION;
        }

        TokenUserInfoDto tokenUserInfoDto = getTokenUserInfoDto();

        userMessageService.saveUserMessage(videoId, tokenUserInfoDto == null ? null : tokenUserInfoDto.getUserId(), messageTypeEnum, content, replyCommentId);
    }

    protected TokenUserInfoDto getTokenUserInfoDto() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(Constants.TOKEN_WEB);
        return redisComponent.getTokenInfo(token);
    }
}

异步实现发送提醒消息

@Override
    @Async
    public void saveUserMessage(String videoId, String sendUserId, MessageTypeEnum messageTypeEnum, String content, Integer replyCommentId) {
        VideoInfo videoInfo = videoInfoMapper.selectByVideoId(videoId);
        if (Objects.isNull(videoInfo)) {
            return;
        }
        UserMessageExtendDto extendDto = new UserMessageExtendDto();
        extendDto.setMessageContent(content);

        String reciveUserId = videoInfo.getUserId();

//        收藏取消已经记录的不再记录
        if (ArrayUtils.contains(new Integer[]{MessageTypeEnum.LIKE.getType(), MessageTypeEnum.COLLECTION.getType()}, messageTypeEnum.getType())) {
            UserMessageQuery userMessageQuery = new UserMessageQuery();
            userMessageQuery.setUserId(reciveUserId);
            userMessageQuery.setVideoId(videoId);
            userMessageQuery.setMessageType(messageTypeEnum.getType());
            Integer count = userMessageMapper.selectCount(userMessageQuery);
            if (count > 0) {
                return;
            }
        }

        UserMessage userMessage = new UserMessage();
        userMessage.setUserId(reciveUserId);
        userMessage.setVideoId(videoId);
        userMessage.setMessageType(messageTypeEnum.getType());
        userMessage.setReadType(MessageReadTypeEnum.NO_READ.getType());
        userMessage.setCreateTime(new Date());
        userMessage.setSendUserId(sendUserId);

//        评论特殊处理
        if (Objects.nonNull(replyCommentId)) {
            VideoComment videoComment = videoCommentMapper.selectByCommentId(replyCommentId);
            if (Objects.nonNull(videoComment)) {
                reciveUserId = videoComment.getUserId();
                extendDto.setMessageContentReply(videoComment.getContent());
            }
        }
        if (reciveUserId.equals(sendUserId)) {
            return;
        }

//        系统消息特殊处理
        if (MessageTypeEnum.SYS.getType().equals(messageTypeEnum.getType())) {
            VideoInfoPost videoInfoPost = videoInfoPostMapper.selectByVideoId(videoId);
            extendDto.setAuditStatus(videoInfoPost.getStatus());
        }
        userMessage.setUserId(reciveUserId);
        userMessage.setExtendJson(JsonUtils.convertObj2Json(extendDto));
        userMessageMapper.insert(userMessage);
    }

表结构
image
image
这里还有个UserMessageExtendDto类
image
这个类在数据库表里的主要功能是提供灵活性,在表结构当中这个以JSON形式存储在数据库当中,取出返回的时候从JSON序列化为Object,这个可以给数据库表提供灵活性.

结束

其实之前的MHBLOG项目当中也使用过AOP来进行日志记录,但是那次忘记记录了,这次在这个项目当中使用到的AOP相关知识其实也覆盖了上次使用过的知识点.总之就是这么多了.

posted @ 2024-12-11 16:05  MingHaiZ  阅读(261)  评论(0)    收藏  举报