libilibi项目总结(14)AOP校验登录和记录消息

打印 上一主题 下一主题

主题 1019|帖子 1019|积分 3057

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
登录校验



  • 定义@GlobalInterceptor注解
  1. @Target({ElementType.METHOD, ElementType.TYPE})
  2. //@Retention注解指定了GlobalInterceptor注解在运行时(RetentionPolicy.RUNTIME)仍然有效,这意味着可以通过反射获取注解的信息。
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface GlobalInterceptor {
  5.     //用于指定是否需要校验登录,默认值为false。
  6.     boolean checkLogin() default false;
  7. }
复制代码
全局拦截器

  1. @Component("operationAspect")
  2. @Aspect
  3. @Slf4j
  4. public class GlobalOperationAspect {
  5.     @Resource
  6.     private RedisUtils redisUtils;
  7.     //定义切入点
  8.     //@Before注解指定了在目标方法执行之前执行interceptorDo方法。
  9.     // 这里的表达式"@annotation(com.easylive.web.annotation.GlobalInterceptor)"
  10.     // 表示如果方法上有GlobalInterceptor注解,则执行interceptorDo方法。
  11.     @Before("@annotation(com.easylive.web.annotation.GlobalInterceptor)")
  12.     public void interceptorDo(JoinPoint point) {
  13. //        point.getSignature():这个方法返回当前连接点(JoinPoint)的签名(Signature)。
  14. //        在Spring AOP中,连接点代表被拦截的点,比如一个方法的执行。
  15. //        (MethodSignature):这是一个类型转换,将point.getSignature()的结果转换为MethodSignature类型。
  16. //        MethodSignature是Signature的一个子接口,它提供了获取方法相关信息的能力。
  17. //        getMethod():这个方法从MethodSignature对象中获取实际的Method对象,这个对象代表了被拦截的方法。
  18.         Method method = ((MethodSignature) point.getSignature()).getMethod();
  19. //        method.getAnnotation(GlobalInterceptor.class):这个方法使用反射机制来检查method对象是否有GlobalInterceptor注解。
  20. //        如果有,它将返回该注解的实例;如果没有,它将返回null。
  21. //        GlobalInterceptor interceptor:这是一个变量声明,用于存储获取到的GlobalInterceptor注解实例。
  22. //        如果method没有GlobalInterceptor注解,interceptor将被赋值为null。
  23.         GlobalInterceptor interceptor = method.getAnnotation(GlobalInterceptor.class);
  24.         //方法上没有该注解就直接返回
  25.         if (null == interceptor) {
  26.             return;
  27.         }
  28.         /**
  29.          * 校验登录
  30.          */
  31.         //该方法上的注解中checkLogin的值为true就要进行登录校验
  32.         if (interceptor.checkLogin()) {
  33.             checkLogin();
  34.         }
  35.     }
  36.     //校验登录
  37.     private void checkLogin() {
  38. //        RequestContextHolder.getRequestAttributes():RequestContextHolder是Spring框架提供的一个工具类,
  39. //        用于在不同的组件之间传递当前请求的上下文信息。getRequestAttributes()方法返回当前请求的RequestAttributes对象,
  40. //        该对象包含了请求相关的信息。
  41. //        (ServletRequestAttributes):这是一个类型转换,将RequestAttributes对象转换为ServletRequestAttributes类型。
  42. //        ServletRequestAttributes是RequestAttributes的一个子类,专门用于封装Servlet请求的属性。
  43. //        getRequest():这个方法从ServletRequestAttributes对象中获取实际的HttpServletRequest对象,
  44. //        这个对象代表了当前的HTTP请求。
  45.         HttpServletRequest request =
  46.                 ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  47. //        request.getHeader(Constants.TOKEN_WEB):getHeader方法是HttpServletRequest的一个方法,
  48. //        用于获取指定名称的请求头的值。在这个例子中,它被用来获取名为Constants.TOKEN_WEB的请求头的值,
  49. //        这个常量通常定义了令牌(token)的名称。
  50. //        String token:这是一个变量声明,用于存储从请求头中获取到的令牌值
  51.         String token = request.getHeader(Constants.TOKEN_WEB);
  52.         if (StringTools.isEmpty(token)) {
  53.             throw new BusinessException(ResponseCodeEnum.CODE_901);
  54.         }
  55.         TokenUserInfoDto tokenUserInfoDto =
  56.                 (TokenUserInfoDto) redisUtils.get(Constants.REDIS_KEY_TOKEN_WEB + token);
  57.         if (tokenUserInfoDto == null) {
  58.             throw new BusinessException(ResponseCodeEnum.CODE_901);
  59.         }
  60.     }
  61. }
复制代码
这段代码是一个 Spring AOP(面向切面编程)实现的全局操纵拦截器,专门用于校验哀求是否携带有用的登录凭证(token)。通过 @GlobalInterceptor 注解来标志必要校验登录的接口方法,AOP 在方法执行前会举行登录校验,确保用户已登录。
详细剖析:

1. 类定义和注解

  1. @Component("operationAspect")
  2. @Aspect
  3. @Slf4j
  4. public class GlobalOperationAspect {
复制代码


  • @Component("operationAspect"): 将该类声明为一个 Spring Bean,名为 operationAspect,使其能被 Spring 管理。
  • @Aspect: 标志该类为一个切面,表示它包罗横切关注点(cross-cutting concerns),这里是举行方法执行前的拦截。
  • @Slf4j: 提供日记功能,用于记录日记信息。
2. 字段注入

  1. @Resource
  2. private RedisUtils redisUtils;
复制代码


  • 使用 @Resource 注解主动注入 RedisUtils,用于后续从 Redis 中获取用户的 token 信息。
3. 切入点定义

  1. @Before("@annotation(com.easylive.web.annotation.GlobalInterceptor)")
  2. public void interceptorDo(JoinPoint point) {
复制代码


  • @Before 注解:指定在目标方法执行之前执行 interceptorDo() 方法。
  • @annotation(com.easylive.web.annotation.GlobalInterceptor): 这是一个切点表达式,表示只会拦截那些方法上有 @GlobalInterceptor 注解的接口。当 @GlobalInterceptor 注解标志的方法被调用时,interceptorDo() 方法会被执行。
好的,第四点紧张是关于获取方法上的注解(@GlobalInterceptor)并举行相应处置惩罚的部分,下面将详细表明这一部分。
4. 获取方法和注解

  1. Method method = ((MethodSignature) point.getSignature()).getMethod();
  2. GlobalInterceptor interceptor = method.getAnnotation(GlobalInterceptor.class);
  3. if (null == interceptor) {
  4.     return;
  5. }
复制代码
表明步骤:


  • point.getSignature():

    • point 是 JoinPoint 范例的参数,代表当前切面所拦截的毗连点(即当前被调用的方法)。JoinPoint 提供了多个方法来访问当前方法的信息。
    • point.getSignature() 返回一个 Signature 范例的对象。Signature 是 AOP 中的一个基础类,它代表了被拦截方法的签名信息(例如方法名、参数范例等)。

  • (MethodSignature) point.getSignature():

    • Signature 是一个接口,MethodSignature 是它的一个子接口,表示的是方法的签名(即方法的信息)。MethodSignature 比 Signature 提供了更多与方法相关的细节信息。
    • 使用 (MethodSignature) 对 point.getSignature() 举行范例转换,将其转换为 MethodSignature,如许我们就能访问更多特定于方法的信息,例如方法的参数范例、返回值范例等。

  • method.getAnnotation(GlobalInterceptor.class):

    • method 是当前目标方法的 Method 对象,它是通过 ((MethodSignature) point.getSignature()).getMethod() 获得的。
    • getAnnotation(GlobalInterceptor.class) 方法通过反射获取当前方法上的 @GlobalInterceptor 注解。如果该方法上没有这个注解,返回 null。
    • GlobalInterceptor 是一个自定义的注解,用于标志必要举行某些特定操纵(例如校验登录)的目标方法。

  • if (null == interceptor) { return; }:

    • 如果当前方法上没有 @GlobalInterceptor 注解,则 interceptor 会为 null。
    • 在这种环境下,if 语句会判定 interceptor 是否为 null,如果是 null,则直接返回,不做任那边理。如许就跳过了当前方法的拦截,意味着不必要执行后续的登录校验逻辑。

目标

这一部分的目标是:

  • 检查目标方法是否有 @GlobalInterceptor 注解

    • 如果方法上有 @GlobalInterceptor 注解,AOP 切面会对该方法举行处置惩罚,执行 interceptorDo() 方法中的逻辑。
    • 如果方法上没有这个注解,说明该方法不必要经过此切面的拦截,以是直接返回,跳过校验。

  • 通过反射获取注解实例

    • method.getAnnotation(GlobalInterceptor.class) 用来通过反射机制获取 @GlobalInterceptor 注解实例。@GlobalInterceptor 注解可以包罗一些配置参数,比如 checkLogin 属性,指示是否必要举行登录校验。

  • 决定是否继承执行拦截操纵

    • if (null == interceptor) 判定是否存在该注解,如果没有找到 @GlobalInterceptor 注解,则当前方法不必要举行进一步的拦截处置惩罚。否则,继承执行后续的逻辑(如校验登录)。

举例

假设我们有以下一个方法:
  1. @GlobalInterceptor(checkLogin = true)
  2. public void someProtectedMethod() {
  3.     // 业务逻辑
  4. }
复制代码
当 someProtectedMethod() 被调用时,GlobalOperationAspect 切面会被触发。AOP 会执行以卑鄙程:

  • 在 interceptorDo() 方法中,point.getSignature() 获取到 someProtectedMethod 方法的签名信息。
  • ((MethodSignature) point.getSignature()).getMethod() 获取到该方法的 Method 对象。
  • method.getAnnotation(GlobalInterceptor.class) 会返回 @GlobalInterceptor 注解实例,由于 someProtectedMethod() 上确实标志了该注解。
  • checkLogin = true 表示必要举行登录校验,于是调用 checkLogin() 方法举行校验。
如果 someProtectedMethod() 没有标志 @GlobalInterceptor 注解,那么 method.getAnnotation(GlobalInterceptor.class) 会返回 null,然后通过 if (null == interceptor) 判定,直接跳过校验,不会执行 checkLogin() 方法。
总结

这部分代码的核心是通过反射获取方法上的注解,并根据注解的内容决定是否执行后续的操纵(如登录校验)。这种方法使得我们可以机动地对必要登录校验的接口方法举行拦截,而不必要在每个方法中重复编写登录验证逻辑,只需在方法上添加 @GlobalInterceptor(checkLogin = true) 注解即可。


  • point.getSignature(): 获取当前毗连点(即被拦截的方法)的签名信息。
  • MethodSignature:Signature 的子接口,提供了关于方法签名的更多细节。
  • method.getAnnotation(GlobalInterceptor.class): 使用反射检查目标方法是否标志了 @GlobalInterceptor 注解。如果没有该注解,直接返回,不做处置惩罚。
5. 校验登录

  1. if (interceptor.checkLogin()) {
  2.     checkLogin();
  3. }
复制代码


  • 如果 @GlobalInterceptor 注解的 checkLogin 属性为 true,则执行 checkLogin() 方法,举行登录校验。
6. 校验登录逻辑(checkLogin())

  1. private void checkLogin() {
  2.     HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  3.     String token = request.getHeader(Constants.TOKEN_WEB);
  4.     if (StringTools.isEmpty(token)) {
  5.         throw new BusinessException(ResponseCodeEnum.CODE_901);
  6.     }
  7.     TokenUserInfoDto tokenUserInfoDto = (TokenUserInfoDto) redisUtils.get(Constants.REDIS_KEY_TOKEN_WEB + token);
  8.     if (tokenUserInfoDto == null) {
  9.         throw new BusinessException(ResponseCodeEnum.CODE_901);
  10.     }
  11. }
复制代码


  • 获取 HTTP 哀求信息

    • RequestContextHolder.getRequestAttributes(): 用于获取当前哀求的上下文对象(RequestAttributes)。这里使用的是 ServletRequestAttributes,它封装了 HTTP 哀求的相关信息。
    • getRequest(): 从 RequestAttributes 中获取当前的 HttpServletRequest 对象,代表了当前的 HTTP 哀求。

  • 从哀求头中获取 token

    • request.getHeader(Constants.TOKEN_WEB): 通过 request.getHeader() 获取哀求头中的 token 值。Constants.TOKEN_WEB 是存储 token 的常量。

  • 判定 token 是否为空

    • StringTools.isEmpty(token): 判定 token 是否为空或空字符串,如果为空,表示用户没有提供登录凭证,抛出 BusinessException 异常,错误码为 CODE_901(表示未登录)。

  • 从 Redis 中获取用户信息

    • redisUtils.get(Constants.REDIS_KEY_TOKEN_WEB + token): 根据 token 在 Redis 中查找对应的用户信息。如果 token 不存在,表示用户未登录或者 token 无效,也抛出 BusinessException 异常,返回 CODE_901 错误码。

7. 异常处置惩罚

  1. throw new BusinessException(ResponseCodeEnum.CODE_901);
复制代码


  • 如果 token 无效或 Redis 中没有对应的用户信息,则抛出 BusinessException 异常。CODE_901 是一个业务错误码,通常表示登录失效或用户未登录。
代码总结

该段代码的核心功能是:

  • 拦截标志了 @GlobalInterceptor 注解的方法。
  • 如果注解中的 checkLogin 属性为 true,则举行登录校验。
  • 校验逻辑包括从哀求头获取 token、检查 token 是否有用(即是否存在于 Redis 中),如果无效则抛出异常,提示用户未登录。
应用场景

这个全局拦截器通常用于必要登录认证的 API 接口。通过这种方式,开辟者可以在方法上使用 @GlobalInterceptor 注解,简化登录校验逻辑,而不必要在每个方法内部重复编写认证逻辑。
例如,某些方法大概是必要登录才能访问的,开辟者只必要在方法上添加 @GlobalInterceptor(checkLogin = true) 注解,AOP 就会主动执行登录校验,确保哀求中携带有用的 token。
扩展

当我们说“它代表了被拦截方法的签名信息”时,实际上是在形貌 Signature 和它的子类 MethodSignature 在 AOP 中所代表的对象的寄义。
1. 签名(Signature)是什么?

在 Java 中,签名(Signature)指的是方法的唯一标识符,它包罗了方法的根本信息。对于 AOP 来说,方法签名通常指一个方法的名称、参数范例以及其他大概的修饰符(如返回范例等)。可以简单地理解为方法的 “签名” 就是这个方法的 “标识符”。
对于 AOP 切面编程中的拦截,它会涉及到“被拦截的方法”的签名信息。Spring AOP 通过 JoinPoint 和 Signature 来形貌目标方法的元数据。


  • Signature:是一个接口,它表示了一个方法(或者构造函数、字段等)的“签名”信息。Signature 只有一些常见的方法,比如获取方法名、获取方法参数范例等。
  • MethodSignature:是 Signature 接口的一个子接口,专门用于方法签名的表示。MethodSignature 继承了 Signature 并提供了更多的与方法相关的元数据,比如获取方法的返回范例、参数范例、方法参数、方法对象等。
2. point.getSignature() 表明

  1. Method method = ((MethodSignature) point.getSignature()).getMethod();
复制代码


  • point.getSignature(): point 是 AOP 的 JoinPoint 对象,它代表当前切点的信息。通过 getSignature() 方法,可以获取该毗连点的签名信息。

    • 对于方法切点(即拦截的是方法),point.getSignature() 返回的是 Signature 范例的对象。如果拦截的是一个方法,getSignature() 返回的是 MethodSignature 范例的对象,如许我们就可以进一步获取关于该方法的详细信息。

  • (MethodSignature) point.getSignature():由于 point.getSignature() 返回的是一个 Signature 范例的对象,而我们关心的是方法签名,因此必要将其强制转换为 MethodSignature。如许,我们可以使用 MethodSignature 提供的专门用于方法的信息获取功能。
  • .getMethod():MethodSignature 提供了 getMethod() 方法,用于获取被拦截的方法的 Method 对象。这个 Method 对象代表了 Java 中的反射 Method 类,答应我们获取方法的相关信息,例如方法名、参数范例、返回范例、注解等。
3. 方法签名包罗的信息

通过 MethodSignature 和 Method 对象,我们可以获取被拦截方法的多种信息,以下是一些常见的方法:


  • getMethodName():获取方法的名称。
  • getParameterTypes():获取方法的参数范例数组。
  • getReturnType():获取方法的返回范例。
  • getAnnotations():获取方法上的所有注解。
  • getParameterNames():获取方法参数的名称(对于 JDK 1.8 及以上版本,必要开启编译时的 -parameters 参数)。
这些信息可以帮助我们在切面中做更精细的处置惩罚,比如动态调解方法调用,举行日记记录、权限控制等。
4. 签名在 AOP 中的作用

在 AOP 中,签名(Signature)信息至关紧张,由于它答应我们知道当前被拦截的具体方法是什么,从而在切面中做出相应的处置惩罚。例如:


  • 如果我们想要在日记中记录方法名、参数,或者方法的执行时间,我们可以通过签名信息获取到这些数据。
  • 如果我们必要对某些方法应用差异的逻辑(如差异的权限校验、差异的缓存策略等),通过签名可以区分差异的方法。
5. 总结

“签名信息”是指方法本身的标识信息,它包括了方法名、参数范例、返回值范例、注解等内容。在 AOP 中,我们通过 MethodSignature 来获取被拦截的目标方法的签名信息。这个签名信息让我们可以或许在切面中做进一步的操纵,比如获取方法的参数、返回范例、注解等信息,从而举行精准的处置惩罚和控制。
简而言之,签名信息就是方法的“身份标识”,它使得 AOP 可以或许知道目标方法的相关细节,以便执行适当的操纵。
消息记录

这是一个自定义注解 @RecordUserMessage 的定义。下面我将详细表明它的各个部分及其作用:
1. 注解的定义

  1. @Target({ElementType.METHOD, ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface RecordUserMessage {
  4.     MessageTypeEnum messageType();
  5. }
复制代码
1.1 @Target 注解

  1. @Target({ElementType.METHOD, ElementType.TYPE})
复制代码


  • @Target 注解定义了注解可以被应用的 Java 元素。ElementType 是一个枚举,表示注解的目标可以是差异的 Java 元素范例。
  • ElementType.METHOD 表示这个注解可以用于方法。
  • ElementType.TYPE 表示这个注解可以用于类、接口或枚举范例。
因此,@RecordUserMessage 注解可以应用于方法或范例(类、接口、枚举等)。
1.2 @Retention 注解

  1. @Retention(RetentionPolicy.RUNTIME)
复制代码


  • @Retention 注解定义了注解的保存策略,即注解在哪个阶段可用。
  • RetentionPolicy.RUNTIME 表示注解会在 运行时 保存,并且可以通过反射读取。这是最常见的策略,由于它答应程序在运行时动态访问注解的信息。
简而言之,@RecordUserMessage 注解会在程序运行时存在,因此可以通过反射读取它的信息。
1.3 public @interface RecordUserMessage

  1. public @interface RecordUserMessage {
复制代码


  • @interface 是用于声明注解的关键字,它标识了 RecordUserMessage 是一个注解范例。与类和接口差异,注解范例通常不包罗具体的业务逻辑。
  • public 表示这个注解是公共的,可以被其他类或包访问。
1.4 messageType() 方法

  1. MessageTypeEnum messageType();
复制代码


  • messageType() 是该注解的一个成员方法,返回一个 MessageTypeEnum 枚举值。这个成员方法是 RecordUserMessage 注解的一个参数,意味着每次使用该注解时,都必要提供一个 messageType 值。
  • MessageTypeEnum 应该是一个枚举范例,表示差异的消息范例。枚举范例通常用于定义一组常量,例如大概的消息范例。
2. 注解的作用

@RecordUserMessage 注解用于记任命户消息,注解中的 messageType 属性用来指定消息的范例。通过将该注解应用到方法或类上,可以将用户消息的记录功能与方法或类绑定,便于后续处置惩罚(如日记记录、消息发送等)。
3. 示例用法

假设我们有以下的 MessageTypeEnum 枚举:
  1. public enum MessageTypeEnum {
  2.     LOGIN, LOGOUT, PURCHASE, SIGNUP;
  3. }
复制代码
我们可以在方法上使用 @RecordUserMessage 注解来标志该方法执行时必要记录的消息范例:
  1. //发布评论
  2.     @RequestMapping("/postComment")
  3.     @RecordUserMessage(messageType = MessageTypeEnum.COMMENT)
  4.     @GlobalInterceptor(checkLogin = true)
  5.     public ResponseVO postComment(@NotEmpty String videoId,
  6.                                   Integer replyCommentId,
  7.                                   @NotEmpty @Size(max = 500) String content,
  8.                                   @Size(max = 50) String imgPath) {
  9.         TokenUserInfoDto tokenUserInfoDto = getTokenUserInfoDto();
  10.         VideoComment comment = new VideoComment();
  11.         comment.setUserId(tokenUserInfoDto.getUserId());
  12.         comment.setAvatar(tokenUserInfoDto.getAvatar());
  13.         comment.setNickName(tokenUserInfoDto.getNickName());
  14.         comment.setVideoId(videoId);
  15.         comment.setContent(content);
  16.         comment.setImgPath(imgPath);
  17.         videoCommentService.postComment(comment, replyCommentId);
  18.         return getSuccessResponseVO(comment);
  19.     }
复制代码
这里,userLogin() 方法会被标志为必要记录一个 LOGIN 范例的用户消息。
也可以用于类上:

  1. @RecordUserMessage(messageType = MessageTypeEnum.PURCHASE)
  2. public class PurchaseService {
  3.     public void processPurchase() {
  4.         // 处理购买逻辑
  5.     }
  6. }
复制代码
在这个例子中,PurchaseService 类的所有方法都被标志为必要记录 PURCHASE 范例的用户消息。
4. 怎样使用

通常,在业务逻辑中,我们会通过 AOP(面向切面编程)来拦截带有 @RecordUserMessage 注解的方法或者类,从而在方法执行前后记录相应的消息。以下是怎样通过 AOP 实现此功能的一个简化示例:
  1. @Aspect
  2. @Component
  3. public class RecordUserMessageAspect {
  4.     @Before("@annotation(recordUserMessage)") // 针对所有被 @RecordUserMessage 注解标注的方法
  5.     public void recordMessage(JoinPoint joinPoint, RecordUserMessage recordUserMessage) {
  6.         MessageTypeEnum messageType = recordUserMessage.messageType();
  7.         // 记录用户消息逻辑,根据 messageType 执行相应的操作
  8.         System.out.println("Recording message of type: " + messageType);
  9.     }
  10. }
复制代码
在这个 AOP 切面中,@Before("@annotation(recordUserMessage)") 会在目标方法执行之前拦截被 @RecordUserMessage 注解标注的方法,并从注解中获取 messageType 参数。然后可以在切面中记录或处置惩罚这个信息。
5. 总结



  • @RecordUserMessage 是一个自定义注解,用于标志方法,以便记录与用户相关的消息。
  • 它通过 messageType 属性来指定消息范例,MessageTypeEnum 枚举范例可以表示差异的消息范例(例如:登录、登出、购买、注册等)。
  • @Target 表示该注解可以应用于方法或类。
  • @Retention 表示该注解在运行时保存,答应通过反射读取。
  • 它通常共同 AOP 使用,在方法执行前后执行相应的逻辑,如记录日记、发送消息等。
枚举类说明

  1. public enum MessageTypeEnum {
  2.     SYS(1, "系统消息"),
  3.     LIKE(2, "点赞"),
  4.     COLLECTION(3, "收藏"),
  5.     COMMENT(4, "评论");
  6.     private Integer type;
  7.     private String desc;
  8.     MessageTypeEnum(Integer type, String desc) {
  9.         this.type = type;
  10.         this.desc = desc;
  11.     }
  12.     public Integer getType() {
  13.         return type;
  14.     }
  15.     public String getDesc() {
  16.         return desc;
  17.     }
  18.     public static MessageTypeEnum getByType(Integer type) {
  19.         for (MessageTypeEnum statusEnum : MessageTypeEnum.values()) {
  20.             if (statusEnum.getType().equals(type)) {
  21.                 return statusEnum;
  22.             }
  23.         }
  24.         return null;
  25.     }
  26. }
复制代码
这是一个枚举类 MessageTypeEnum,它定义了几种范例的消息,每种消息有一个 type(范例标识符)和 desc(形貌信息)。下面我将详细表明这个枚举类的每一部分。
1. 枚举类的声明

  1. public enum MessageTypeEnum {
  2.     SYS(1, "系统消息"),
  3.     LIKE(2, "点赞"),
  4.     COLLECTION(3, "收藏"),
  5.     COMMENT(4, "评论");
复制代码


  • public enum MessageTypeEnum:定义了一个公共的枚举范例 MessageTypeEnum,它表示消息的差异范例。
  • 枚举值:

    • SYS(1, "体系消息"):表示体系消息,1 是它的范例标识,"体系消息" 是形貌。
    • LIKE(2, "点赞"):表示点赞消息,2 是它的范例标识,"点赞" 是形貌。
    • COLLECTION(3, "收藏"):表示收藏消息,3 是它的范例标识,"收藏" 是形貌。
    • COMMENT(4, "评论"):表示评论消息,4 是它的范例标识,"评论" 是形貌。

枚举常量是 MessageTypeEnum 类的实例,并且每个常量都有本身的构造函数参数 type 和 desc。
2. 构造函数

  1. MessageTypeEnum(Integer type, String desc) {
  2.     this.type = type;
  3.     this.desc = desc;
  4. }
复制代码


  • 枚举的构造函数 MessageTypeEnum(Integer type, String desc) 用于初始化每个枚举常量的 type 和 desc 字段。构造函数是 私有的,因此它只能在枚举内部被调用。
  • type 表示消息的范例标识符,desc 是该范例的形貌信息。
3. 字段和方法

  1. private Integer type;
  2. private String desc;
复制代码


  • type:用于存储消息的范例标识符,例如 1 表示体系消息,2 表示点赞消息等。
  • desc:用于存储消息范例的形貌,例如 "体系消息"、"点赞" 等。
  1. public Integer getType() {
  2.     return type;
  3. }
  4. public String getDesc() {
  5.     return desc;
  6. }
复制代码


  • getType() 方法返回消息的范例标识符(Integer 范例)。
  • getDesc() 方法返回消息的形貌(String 范例)。
4. 静态方法:根据范例获取枚举值

  1. public static MessageTypeEnum getByType(Integer type) {
  2.     for (MessageTypeEnum statusEnum : MessageTypeEnum.values()) {
  3.         if (statusEnum.getType().equals(type)) {
  4.             return statusEnum;
  5.         }
  6.     }
  7.     return null;
  8. }
复制代码


  • getByType(Integer type) 方法用于根据消息的 type 获取对应的 MessageTypeEnum 枚举实例。

    • MessageTypeEnum.values() 返回所有枚举常量的数组。
    • for (MessageTypeEnum statusEnum : MessageTypeEnum.values()) 遍历所有的枚举常量。
    • if (statusEnum.getType().equals(type)) 判定当前枚举常量的 type 是否等于传入的 type。
    • 如果匹配成功,则返回对应的枚举常量。
    • 如果遍历所有枚举常量后没有找到匹配的,返回 null。

5. 示例:怎样使用 MessageTypeEnum

你可以通过枚举范例的 type 值来获取对应的枚举实例。例如:
获取枚举实例

  1. MessageTypeEnum typeEnum = MessageTypeEnum.getByType(2);
  2. System.out.println(typeEnum.getDesc());  // 输出:点赞
复制代码
输出所有枚举范例及其形貌

  1. for (MessageTypeEnum typeEnum : MessageTypeEnum.values()) {
  2.     System.out.println(typeEnum.getType() + ": " + typeEnum.getDesc());
  3. }
复制代码
这将打印:
  1. 1: 系统消息
  2. 2: 点赞
  3. 3: 收藏
  4. 4: 评论
复制代码
6. 总结



  • MessageTypeEnum 是一个枚举类,用于定义差异范例的消息,如体系消息、点赞、收藏和评论。
  • 每个枚举常量都关联有一个 type(范例标识符)和 desc(形貌)。
  • 通过 getType() 和 getDesc() 方法,可以获取枚举常量的具体信息。
  • getByType(Integer type) 是一个静态方法,通过 type 获取相应的枚举常量。这在必要根据某个 type 查找对应消息范例的场景中非常有用。
该枚举类的设计使得消息范例的管理更加结构化和范例安全,避免了使用原始整数值来表示消息范例,提供了更直观和易于理解的代码。
消息记录全全局拦截器

  1. @Component("userMessageOperationAspect")
  2. @Aspect
  3. @Slf4j
  4. public class UserMessageOperationAspect {
  5.     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_AUDIT_REJECT_REASON = "reason";    private static final String PARAMETERS_CONTENT = "content";    @Resource    private RedisUtils redisUtils;    @Resource    private UserMessageService userMessageService;    //环绕关照,作用于方法调用前后    @Around("@annotation(com.easylive.annotation.RecordUserMessage)")    //环绕关照使用的是ProceedingJoinPoint point    public ResponseVO interceptorDo(ProceedingJoinPoint point) throws Exception {        try {            //调用该原始方法并获取返回值            ResponseVO result = (ResponseVO) point.proceed();
  6.             Method method = ((MethodSignature) point.getSignature()).getMethod();
  7.             RecordUserMessage recordUserMessage = method.getAnnotation(RecordUserMessage.class);            if (recordUserMessage != null) {                //传入注解中的值(消息范例),方法中的实际参数,方法中每个参数的名称(是个对象,不是string范例)                saveUserMessage(recordUserMessage, point.getArgs(), method.getParameters());            }            return result;        } catch (BusinessException e) {            log.error("全局拦截器异常", e);            throw e;        } catch (Exception e) {            log.error("全局拦截器异常", e);            throw e;        } catch (Throwable e) {            log.error("全局拦截器异常", e);            throw new BusinessException(ResponseCodeEnum.CODE_500);        }    }    private void saveUserMessage(RecordUserMessage recordUserMessage, Object[] arguments, Parameter[] parameters) {
  8.         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) arguments[i];            } else if (PARAMETERS_ACTION_TYPE.equals(parameters[i].getName())) {                actionType = (Integer) arguments[i];            } else if (PARAMETERS_REPLY_COMMENTID.equals(parameters[i].getName())) {                replyCommentId = (Integer) arguments[i];            } else if (PARAMETERS_AUDIT_REJECT_REASON.equals(parameters[i].getName())) {                content = (String) arguments[i];            } else if (PARAMETERS_CONTENT.equals(parameters[i].getName())) {                content = (String) arguments[i];            }        }        //获取消息范例        //这行代码从recordUserMessage注解实例中获取messageType属性的值,并将其赋给messageTypeEnum变量。        // messageType是一个方法,返回MessageTypeEnum枚举范例,代表消息的范例。        MessageTypeEnum messageTypeEnum = recordUserMessage.messageType();
  9.         //点赞和收藏使用的是同一个方法(注解中的值是value),以是要再次判定举动范例        if (UserActionTypeEnum.VIDEO_COLLECT.getType().equals(actionType)) {            messageTypeEnum = MessageTypeEnum.COLLECTION;        }        TokenUserInfoDto tokenUserInfoDto = getTokenUserInfoDto();
  10.         //管理端获取不到用户信息,体系发送,不必要用户id        userMessageService.saveUserMessage(videoId, tokenUserInfoDto == null ? null : tokenUserInfoDto.getUserId(), messageTypeEnum, content, replyCommentId);
  11.     }    private TokenUserInfoDto getTokenUserInfoDto() {        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();        String token = request.getHeader(Constants.TOKEN_WEB);        return (TokenUserInfoDto) redisUtils.get(Constants.REDIS_KEY_TOKEN_WEB + token);    }}
复制代码
这段代码实现了一个名为 UserMessageOperationAspect 的 AOP 切面类,用于在方法执行过程中主动记任命户消息。通过该切面类,体系可以在方法执行前后拦截特定的业务方法,根据方法的参数主动生成并生存用户消息。下面详细表明每个部分的作用:
1. 类注解与切面定义

  1. @Component("userMessageOperationAspect")
  2. @Aspect
  3. @Slf4j
  4. public class UserMessageOperationAspect {
复制代码


  • @Component("userMessageOperationAspect"):将这个类注册为 Spring 容器中的一个 Bean,便于依赖注入。
  • @Aspect:标志这个类为 AOP 切面类,表示它用于处置惩罚切面逻辑。
  • @Slf4j:这是 Lombok 提供的日记注解,主动生成一个 log 对象,用于日记记录。
2. 常量字段

  1. private static final String PARAMETERS_VIDEO_ID = "videoId";
  2. private static final String PARAMETERS_ACTION_TYPE = "actionType";
  3. private static final String PARAMETERS_REPLY_COMMENTID = "replyCommentId";
  4. private static final String PARAMETERS_AUDIT_REJECT_REASON = "reason";
  5. private static final String PARAMETERS_CONTENT = "content";
复制代码
这些常量是方法参数的名称,用于在后续代码中辨认通报给方法的参数。通过这些名称,可以在方法执行时动态地获取参数值。
3. 依赖注入

  1. @Resource
  2. private RedisUtils redisUtils;
  3. @Resourceprivate UserMessageService userMessageService;
复制代码


  • redisUtils 用于与 Redis 交互,紧张用于从 Redis 中获取用户的 Token 信息。
  • userMessageService 用于生存用户消息。
4. 环绕关照

  1. @Around("@annotation(com.easylive.annotation.RecordUserMessage)")
  2. public ResponseVO interceptorDo(ProceedingJoinPoint point) throws Exception {
复制代码


  • @Around:环绕关照,表示在目标方法执行之前和之后都会执行这个关照方法。ProceedingJoinPoint 是环绕关照特有的对象,答应你控制目标方法的执行。
  • @annotation(com.easylive.annotation.RecordUserMessage):这个切入点表达式指定了该切面只会拦截标注了 @RecordUserMessage 注解的方法。
5. 执行原始方法并捕获返回值

  1. ResponseVO result = (ResponseVO) point.proceed();
复制代码


  • point.proceed():调用被拦截的方法并获取其返回值。proceed() 方法会继承执行原方法的逻辑,并返回执行结果。
  • result 生存了原始方法的返回值,范例为 ResponseVO。
6. 获取方法信息与注解

  1. Method method = ((MethodSignature) point.getSignature()).getMethod();
  2. RecordUserMessage recordUserMessage = method.getAnnotation(RecordUserMessage.class);
复制代码


  • Method method = ((MethodSignature) point.getSignature()).getMethod();
    :获取当前执行方法的 Method 对象。
  • recordUserMessage = method.getAnnotation(RecordUserMessage.class);:获取方法上的 @RecordUserMessage 注解实例,用于读取注解中的配置。
7. 生存用户消息

  1. if (recordUserMessage != null) {
  2.     saveUserMessage(recordUserMessage, point.getArgs(), method.getParameters());
  3. }
复制代码
如果方法上有 @RecordUserMessage 注解,则调用 saveUserMessage 方法生存用户消息。point.getArgs() 获取方法的参数,method.getParameters() 获取方法参数的元数据(即参数名称和范例)。
8. 生存用户消息的逻辑

这段代码是 UserMessageOperationAspect 类中的一个方法 saveUserMessage。它的作用是在业务方法执行时,基于传入的参数和注解 @RecordUserMessage,主动生成并生存用户消息。具体来说,方法从目标方法的参数中提取相关数据(如 videoId、actionType 等),并使用这些数据构建用户消息。下面是对代码的逐行详细表明:
方法签名

  1. private void saveUserMessage(RecordUserMessage recordUserMessage, Object[] arguments, Parameter[] parameters) {
复制代码


  • RecordUserMessage recordUserMessage:这是传入的注解对象,包罗了 @RecordUserMessage 注解的所有信息。在这个方法中,我们紧张通过它来获取消息范例(messageType)。
  • Object[] arguments:这是目标方法的参数值数组,生存了调用目标方法时传入的实际参数。
  • Parameter[] parameters:这是目标方法的参数元数据,包罗了方法参数的名称和范例。通过它可以动态获取参数的名称,用于从 arguments 中提取对应的值。
初始化变量

  1. String videoId = null;
  2. Integer actionType = null;
  3. Integer replyCommentId = null;
  4. String content = null;
复制代码


  • 这些变量是用来存储方法参数中提取的信息。它们分别对应视频 ID、动作范例、回复的评论 ID 和内容。
遍历参数并提取值

  1. for (int i = 0; i < parameters.length; i++) {
  2.     if (PARAMETERS_VIDEO_ID.equals(parameters[i].getName())) {
  3.         videoId = (String) arguments[i];
  4.     } else if (PARAMETERS_ACTION_TYPE.equals(parameters[i].getName())) {
  5.         actionType = (Integer) arguments[i];
  6.     } else if (PARAMETERS_REPLY_COMMENTID.equals(parameters[i].getName())) {
  7.         replyCommentId = (Integer) arguments[i];
  8.     } else if (PARAMETERS_AUDIT_REJECT_REASON.equals(parameters[i].getName())) {
  9.         content = (String) arguments[i];
  10.     } else if (PARAMETERS_CONTENT.equals(parameters[i].getName())) {
  11.         content = (String) arguments[i];
  12.     }
  13. }
复制代码


  • 这段代码遍历了目标方法的所有参数。对于每一个参数,起首通过 parameters.getName() 获取该参数的名称,然后与预先定义的常量举行比力。

    • PARAMETERS_VIDEO_ID:如果参数名称是 videoId,则将对应的 arguments 值赋给 videoId 变量。
    • PARAMETERS_ACTION_TYPE:如果参数名称是 actionType,则将对应的 arguments 值赋给 actionType 变量。
    • PARAMETERS_REPLY_COMMENTID:如果参数名称是 replyCommentId,则将对应的 arguments 值赋给 replyCommentId 变量。
    • PARAMETERS_AUDIT_REJECT_REASONPARAMETERS_CONTENT:如果参数名称是 content 或 reason,则将对应的 arguments 值赋给 content 变量。

  • 这种方法可以根据方法的实际参数动态提取相应的值。
获取消息范例

  1. MessageTypeEnum messageTypeEnum = recordUserMessage.messageType();
复制代码


  • recordUserMessage.messageType():从注解中获取消息范例。这是 @RecordUserMessage 注解中的一个属性,返回 MessageTypeEnum 枚举值,表示消息的范例。
  • messageTypeEnum 存储了从注解中获取到的消息范例,例如:体系消息、点赞消息、收藏消息等。
特殊处置惩罚:点赞和收藏

  1. if (UserActionTypeEnum.VIDEO_COLLECT.getType().equals(actionType)) {
  2.     messageTypeEnum = MessageTypeEnum.COLLECTION;
  3. }
复制代码


  • 这段代码是为了处置惩罚特殊环境:如果动作范例(actionType)是视频收藏(VIDEO_COLLECT),则将消息范例(messageTypeEnum)强制设置为 收藏消息(MessageTypeEnum.COLLECTION)。
  • UserActionTypeEnum.VIDEO_COLLECT.getType() 获取的是视频收藏的范例值,actionType 是目标方法的参数,表示当前用户执行的操纵范例。
  • 如果 actionType 是视频收藏操纵范例,那么纵然注解中指定的消息范例是其他范例(如点赞),也将消息范例修改为 收藏
获取用户信息

  1. TokenUserInfoDto tokenUserInfoDto = getTokenUserInfoDto();
复制代码


  • 调用 getTokenUserInfoDto() 方法从 Redis 中获取当前用户的信息。这个方法通过读取哀求中的 Token,从 Redis 缓存中查找并返回 TokenUserInfoDto 对象,包罗当前用户的详细信息(如用户 ID)。
生存用户消息

  1. userMessageService.saveUserMessage(videoId, tokenUserInfoDto == null ? null : tokenUserInfoDto.getUserId(), messageTypeEnum, content, replyCommentId);
复制代码


  • 调用 userMessageService.saveUserMessage() 方法生存用户消息。

    • videoId:视频的 ID,表示这条消息关联的视频。
    • tokenUserInfoDto == null ? null : tokenUserInfoDto.getUserId():当前用户的 ID,如果没有找到用户信息(例如管理员或无效 Token),则通报 null。
    • messageTypeEnum:消息的范例(通过注解获取或根据 actionType 修改)。
    • content:消息的内容(大概是评论内容或审核拒绝理由等)。
    • replyCommentId:如果是回复评论,则通报被回复评论的 ID。

额外说明

1. 方法参数提取

这段代码根据目标方法的参数名称动态提取参数值。由于 Java 中的反射通常只能获取方法参数的范例和位置,而不能直接获取参数名称,因此 parameters.getName() 联合注解中的参数名称提供了方便的方式来举行映射。
2. Token 校验与用户信息获取

在实际应用中,用户信息通常存储在服务器端的缓存或数据库中,而通过 Token 来举行用户身份验证。getTokenUserInfoDto() 方法通过 HTTP 哀求中的 Token 从 Redis 获取用户信息,如许可以确保消息记录与实际用户相关。
3. 消息范例和业务逻辑

MessageTypeEnum 枚举类和 UserActionTypeEnum 枚举类用于定义差异的操纵和消息范例。业务逻辑中根据 actionType 对消息范例举行调解,例如将点赞操纵映射为 “点赞” 消息,将视频收藏映射为 “收藏” 消息。
总结



  • 该方法 saveUserMessage 紧张是从目标方法的参数中提取出必要的信息,如视频 ID、动作范例、内容等,然后根据注解中的配置主动生成并生存用户消息。
  • 它使用 AOP 主动化处置惩罚消息记录逻辑,无需在每个业务方法中手动添加消息生存的代码,提拔了代码的可维护性和扩展性。
9. 获取用户信息

  1. private TokenUserInfoDto getTokenUserInfoDto() {
  2.     HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  3.     String token = request.getHeader(Constants.TOKEN_WEB);
  4.     return (TokenUserInfoDto) redisUtils.get(Constants.REDIS_KEY_TOKEN_WEB + token);
  5. }
复制代码


  • 该方法从 HTTP 哀求头中获取 TOKEN_WEB,然后从 Redis 中查找对应的用户信息,返回 TokenUserInfoDto 对象。
10. 异常处置惩罚

  1. catch (BusinessException e) {
  2.     log.error("全局拦截器异常", e);
  3.     throw e;
  4. } catch (Exception e) {
  5.     log.error("全局拦截器异常", e);
  6.     throw e;
  7. } catch (Throwable e) {
  8.     log.error("全局拦截器异常", e);
  9.     throw new BusinessException(ResponseCodeEnum.CODE_500);
  10. }
复制代码


  • 通过 try-catch 块捕获各种异常(BusinessException、Exception、Throwable),记录错误日记并抛出相应的异常。
11. 总结



  • 这个类实现了一个 AOP 切面,紧张作用是记任命户消息。通过 @RecordUserMessage 注解标注的方法,在执行时会主动记任命户消息。
  • 切面在执行目标方法前后拦截,获取方法的参数,并根据注解配置生存消息。
  • 该功能通过 userMessageService.saveUserMessage() 方法生存消息,并使用 Redis 从中获取用户信息(通过 Token)。
为什么用 ProceedingJoinPoint 而不是 JoinPoint?

ProceedingJoinPoint 是环绕关照 (@Around) 中使用的特定范例的毗连点(JoinPoint)。它与常规的 JoinPoint 差异,紧张用于环绕关照,由于它提供了更多的控制能力,尤其是在执行目标方法时。
为什么用 ProceedingJoinPoint 而不是 JoinPoint?

在 Spring AOP 中,@Before, @After, @AfterReturning, 和 @AfterThrowing 通常使用 JoinPoint,它提供了关于目标方法的一些信息(比如方法签名、参数等)。然而,@Around 关照必要更强的控制能力,由于它答应你在执行目标方法之前或之后举行操纵,甚至决定是否执行目标方法。
ProceedingJoinPoint 是 JoinPoint 的子接口,扩展了更多功能,最紧张的就是它有一个 proceed() 方法。proceed() 方法让你可以在目标方法调用前后控制方法的执行,甚至可以选择不执行目标方法,或者修改目标方法的返回值。
ProceedingJoinPoint 的作用


  • 调用目标方法

    • 通过 proceed() 方法,你可以显式地调用目标方法并获取返回值。
    • proceed() 方法会继承执行目标方法,并返回方法的返回值。

  • 控制目标方法的执行

    • 你可以在调用 proceed() 之前执行一些逻辑,比如日记记录、权限检查等。
    • 你还可以根据条件决定是否调用目标方法,或者修改返回值。

示例:

  1. @Around("@annotation(com.easylive.annotation.RecordUserMessage)")
  2. public ResponseVO interceptorDo(ProceedingJoinPoint point) throws Exception {
  3.     try {        // 调用原始方法并获取返回值        ResponseVO result = (ResponseVO) point.proceed();
  4.                 // 获取方法的注解信息        Method method = ((MethodSignature) point.getSignature()).getMethod();
  5.         RecordUserMessage recordUserMessage = method.getAnnotation(RecordUserMessage.class);                // 如果有注解,记任命户消息        if (recordUserMessage != null) {            saveUserMessage(recordUserMessage, point.getArgs(), method.getParameters());        }        return result; // 返回原方法的执行结果    } catch (Exception e) {        log.error("全局拦截器异常", e);        throw e; // 如果发生异常,继承抛出    }}
复制代码
ProceedingJoinPoint 提供的关键方法


  • proceed():这是最关键的方法,用来继承执行目标方法。如果你没有调用 proceed(),目标方法将不会执行。
  • getArgs():获取目标方法的参数列表,你可以通过它访问目标方法传入的参数。
  • getSignature():获取目标方法的签名(包括方法名、返回范例、参数范例等)。
  • getTarget():获取目标对象(即被署理的对象)。
为什么在这段代码中使用 ProceedingJoinPoint?

在你的 UserMessageOperationAspect 类中,使用 ProceedingJoinPoint 是由于:


  • 必要在目标方法执行前后举行处置惩罚:例如,在执行原始方法之前,大概必要做一些日记记录、参数处置惩罚、用户消息生存等操纵。
  • 获取目标方法的返回值:使用 proceed() 可以获取目标方法的返回值,并且你可以在此基础上举行修改或扩展操纵。
  • 控制方法执行:你可以通过 proceed() 控制目标方法的执行机遇,甚至可以根据条件决定是否调用目标方法。
总结:ProceedingJoinPoint 是 @Around 关照专用的接口,它为开辟者提供了更多的机动性,可以或许控制目标方法的执行逻辑、修改方法参数或返回值等,因此它是环绕关照的理想选择。
userMessageService.saveUserMessage(videoId, tokenUserInfoDto == null ? null : tokenUserInfoDto.getUserId(), messageTypeEnum, content, replyCommentId);


  1.         @Override
  2.     @Async
  3.     public void saveUserMessage(String videoId, String sendUserId, MessageTypeEnum messageTypeEnum, String content, Integer replyCommentId) {
  4.         VideoInfo videoInfo = this.videoInfoPostMapper.selectByVideoId(videoId);
  5.         if (videoInfo == null) {
  6.             return;
  7.         }
  8.         //扩展信息
  9.         UserMessageExtendDto extendDto = new UserMessageExtendDto();
  10.         extendDto.setMessageContent(content);
  11.         //接受消息的用户id
  12.         String userId = videoInfo.getUserId();
  13.         //收藏,点赞 已经记录过消息,不在记录
  14.         if (ArrayUtils.contains(new Integer[]{MessageTypeEnum.LIKE.getType(), MessageTypeEnum.COLLECTION.getType()}, messageTypeEnum.getType())) {
  15.             UserMessageQuery userMessageQuery = new UserMessageQuery();
  16.             //userMessageQuery.setUserId(userId);
  17.             userMessageQuery.setSendUserId(sendUserId);
  18.             userMessageQuery.setVideoId(videoId);
  19.             userMessageQuery.setMessageType(messageTypeEnum.getType());
  20.             Integer count = userMessageMapper.selectCount(userMessageQuery);
  21.             if (count > 0) {
  22.                 return;
  23.             }
  24.         }
  25.         UserMessage userMessage = new UserMessage();
  26.         userMessage.setUserId(userId);
  27.         userMessage.setVideoId(videoId);
  28.         userMessage.setReadType(MessageReadTypeEnum.NO_READ.getType());
  29.         userMessage.setCreateTime(new Date());
  30.         userMessage.setMessageType(messageTypeEnum.getType());
  31.         userMessage.setSendUserId(sendUserId);
  32.         //评论特殊处理
  33.         if (replyCommentId != null) {
  34.             VideoComment commentInfo = videoCommentMapper.selectByCommentId(replyCommentId);
  35.             if (null != commentInfo) {
  36.                 //发布评论的人收到该信息
  37.                 userId = commentInfo.getUserId();
  38.                 extendDto.setMessageContentReply(commentInfo.getContent());
  39.             }
  40.         }
  41.         if (userId.equals(sendUserId)) {
  42.             return;
  43.         }
  44.         //系统消息特殊处理
  45.         if (MessageTypeEnum.SYS == messageTypeEnum) {
  46.             VideoInfoPost videoInfoPost = videoInfoPostMapper.selectByVideoId(videoId);
  47.             extendDto.setAuditStatus(videoInfoPost.getStatus());
  48.         }
  49.         userMessage.setUserId(userId);
  50.         userMessage.setExtendJson(JsonUtils.convertObj2Json(extendDto));
  51.         this.userMessageMapper.insert(userMessage);
  52.     }
复制代码
这段代码是一个异步方法 saveUserMessage,用于生存用户消息的业务逻辑,紧张处置惩罚用户评论、点赞、收藏等操纵时生成的消息。它涉及到一些数据库查询、条件判定、消息处置惩罚和存储等操纵。下面是详细的表明:
方法签名

  1. @Override
  2. @Async
  3. public void saveUserMessage(String videoId, String sendUserId, MessageTypeEnum messageTypeEnum, String content, Integer replyCommentId)
复制代码


  • @Override:表示该方法是重写父类或接口中的方法。
  • @Async:表示该方法是异步执行的。Spring 会在单独的线程中执行该方法,而不阻塞调用者,适用于不必要立即返回结果的场景,例如日记记录、消息推送等。
  • String videoId:视频的 ID,指明这条消息是与哪一个视频相关。
  • String sendUserId:发送消息的用户 ID,表示是谁发出了这个操纵(如评论、点赞等)。
  • MessageTypeEnum messageTypeEnum:消息范例的枚举值,表示这条消息是属于哪种范例(评论、点赞、收藏等)。
  • String content:消息内容,通常是评论的文本或相关信息。
  • Integer replyCommentId:回复评论的 ID,如果是回复某条评论,则传入该评论 ID。
1. 获取视频信息

  1. VideoInfo videoInfo = this.videoInfoPostMapper.selectByVideoId(videoId);
  2. if (videoInfo == null) {
  3.     return;
  4. }
复制代码


  • 使用 videoId 从数据库中查询该视频的详细信息(VideoInfo)。
  • 如果该视频信息不存在(即 videoInfo == null),则方法直接返回,表示不举行后续的消息生存操纵。
2. 创建扩展信息

  1. UserMessageExtendDto extendDto = new UserMessageExtendDto();
  2. extendDto.setMessageContent(content);
复制代码


  • 创建一个 UserMessageExtendDto 对象,用于存储消息的扩展信息。这个对象包罗了消息的内容(如评论文本)。
  • 将传入的 content 设置为扩展信息中的消息内容。
3. 获取接收消息的用户 ID

  1. String userId = videoInfo.getUserId();
复制代码


  • 从 videoInfo 中获取视频发布者的用户 ID(即接收消息的用户)。这意味着该视频的发布者将是消息的接收者。
4. 处置惩罚已记录的点赞和收藏消息

  1. if (ArrayUtils.contains(new Integer[]{MessageTypeEnum.LIKE.getType(), MessageTypeEnum.COLLECTION.getType()}, messageTypeEnum.getType())) {
  2.     UserMessageQuery userMessageQuery = new UserMessageQuery();
  3.     userMessageQuery.setSendUserId(sendUserId);
  4.     userMessageQuery.setVideoId(videoId);
  5.     userMessageQuery.setMessageType(messageTypeEnum.getType());
  6.     Integer count = userMessageMapper.selectCount(userMessageQuery);
  7.     if (count > 0) {
  8.         return;
  9.     }
  10. }
复制代码


  • 检查是否已经记录过点赞或收藏消息

    • 如果消息范例是 点赞(LIKE)收藏(COLLECTION),则通过查询数据库检查是否已经记录了相同的消息。
    • 如果 该消息已经记录(count > 0),则直接返回,不再生存相同的消息。

5. 创建新的用户消息对象

  1. UserMessage userMessage = new UserMessage();
  2. userMessage.setUserId(userId);
  3. userMessage.setVideoId(videoId);
  4. userMessage.setReadType(MessageReadTypeEnum.NO_READ.getType());
  5. userMessage.setCreateTime(new Date());
  6. userMessage.setMessageType(messageTypeEnum.getType());
  7. userMessage.setSendUserId(sendUserId);
复制代码


  • 创建一个新的 UserMessage 对象,预备将其生存到数据库中。
  • 设置以部属性:

    • userId:接收消息的用户 ID(视频的发布者)。
    • videoId:视频 ID,表示消息与哪个视频相关。
    • readType:消息的阅读状态,设置为未读。
    • createTime:消息的创建时间。
    • messageType:消息范例(如评论、点赞等)。
    • sendUserId:发送消息的用户 ID(即操纵视频的用户)。

6. 处置惩罚评论的特殊逻辑

  1. if (replyCommentId != null) {
  2.     VideoComment commentInfo = videoCommentMapper.selectByCommentId(replyCommentId);
  3.     if (null != commentInfo) {
  4.         userId = commentInfo.getUserId();
  5.         extendDto.setMessageContentReply(commentInfo.getContent());
  6.     }
  7. }
复制代码


  • 如果 replyCommentId 不为 null,表示这条消息是回复某条评论。
  • 根据 replyCommentId 从数据库中查询对应的评论(VideoComment)。
  • 如果找到了该评论,更新:

    • userId:接收消息的用户 ID 更新为评论的发布者(即回复评论的人)。
    • extendDto.setMessageContentReply(commentInfo.getContent()):设置扩展信息中的回复内容(即原评论的内容)。

7. 防止发送者收到本身的消息

  1. if (userId.equals(sendUserId)) {
  2.     return;
  3. }
复制代码


  • 如果发送消息的用户是接收消息的用户(即 userId 和 sendUserId 相同),则直接返回,不再生存消息。
  • 如许避免了用户本身发的评论、点赞等操纵被重复记录为消息。
8. 体系消息的特殊处置惩罚

  1. if (MessageTypeEnum.SYS == messageTypeEnum) {
  2.     VideoInfoPost videoInfoPost = videoInfoPostMapper.selectByVideoId(videoId);
  3.     extendDto.setAuditStatus(videoInfoPost.getStatus());
  4. }
复制代码


  • 如果消息范例是 体系消息(SYS),则获取该视频的信息(VideoInfoPost),并将视频的审核状态(status)参加扩展信息 extendDto 中。
  • 体系消息大概包罗视频审核状态等特定信息。
9. 生存用户消息到数据库

  1. userMessage.setUserId(userId);
  2. userMessage.setExtendJson(JsonUtils.convertObj2Json(extendDto));
  3. this.userMessageMapper.insert(userMessage);
复制代码


  • 设置用户消息对象的 extendJson 属性,将 extendDto 对象转为 JSON 格式并存储到数据库中。
  • 最后,调用 userMessageMapper.insert(userMessage) 将这条用户消息生存到数据库。
总结

该方法紧张负责以下操纵:

  • 获取视频信息:通过视频 ID 查询视频信息,判定该视频是否有用。
  • 创建用户消息:根据差异的消息范例(如评论、点赞、收藏等),创建用户消息对象,并设置相关的消息内容、接收用户等信息。
  • 处置惩罚特殊逻辑

    • 对于 点赞和收藏,避免重复记录消息。
    • 对于 评论,处置惩罚回复评论的特殊逻辑。
    • 对于 体系消息,参加视频的审核状态等信息。

  • 存储消息:将最终生成的消息对象存储到数据库中。
该方法是一个典型的异步处置惩罚方案,适用于必要延迟处置惩罚的操纵(如记录消息),并避免在用户操纵过程中增长相应时间。
@Async

@Async 注解是 Spring 提供的异步处置惩罚功能,答应方法在独立的线程中执行,避免阻塞调用者。如许,方法的执行不会影响主流程,特殊适用于一些耗时的操纵,如 I/O 操纵、数据库操纵等。在你的代码中,@Async 被用来异步生存用户消息。下面是对 @Async 的详细表明:
1. @Async 的作用

@Async 注解告诉 Spring,这个方法应该异步执行,也就是说方法会在另一个线程中执行,调用者无需等待方法执行完成,可以继承执行其他任务。
2. 怎样工作



  • 线程管理:当方法被标志为 @Async 时,Spring 会在内部使用一个线程池来执行这个方法。如果没有自定义线程池,Spring 会使用默认的线程池。
  • 返回值:@Async 注解的方法通常必要返回 Future、CompletableFuture 或 ListenableFuture,这些返回值可以用于后续获取方法的执行结果。
  • 非阻塞:调用 @Async 注解的方法时,调用者不会等待方法执行完成,而是可以立即执行后续代码,方法会在后台线程中执行。
3. @Async 的关键点



  • 线程池:当方法标志为 @Async 后,Spring 会从线程池中获取一个线程来执行该方法。
  • 返回范例:异步方法的返回值通常是 Future、ListenableFuture 或 CompletableFuture,这些范例用于跟踪异步操纵的结果。
  • 配置:默认环境下,Spring 使用一个简单的 ThreadPoolTaskExecutor 来管理线程池。但是,你也可以自定义线程池,控制线程数、队列容量等。
  • 异常处置惩罚:如果异步方法抛出异常,它不会直接通报给调用者。你必要在异步方法内部处置惩罚异常(例如通过回调或 Future 中处置惩罚)。
4. 你代码中的 @Async 表明

在你的代码中,@Async 被应用于 saveUserMessage 方法:
  1. @Async
  2. public void saveUserMessage(String videoId, String sendUserId, MessageTypeEnum messageTypeEnum, String content, Integer replyCommentId) {
  3.     // 处理保存用户消息的逻辑
  4. }
复制代码
这意味着每次调用 saveUserMessage 方法时,它将异步执行,即方法会在后台的独立线程中执行,调用者无需等待其完成。
调用 saveUserMessage 方法时:



  • 调用者举动:方法被调用时,调用者不会阻塞等待方法执行完成,可以继承执行后续任务。
  • 线程处置惩罚:Spring 会从线程池中选择一个线程来执行 saveUserMessage 方法。
  • 没有返回值:这个方法没有返回值,调用者无法获得异步执行的结果,也不必要等待它完成,适合像生存消息如许的操纵。
5. @Async 使用的配置要求



  • 启用异步支持:要使 @Async 生效,必要在 Spring 配置类中启用异步处置惩罚。这可以通过添加 @EnableAsync 注解来实现:
    1. @Configuration
    2. @EnableAsync
    3. public class AsyncConfig {
    4. }
    复制代码
  • 自定义线程池(可选):你可以定义一个自定义的线程池,如果希望控制线程池的大小、队列容量等参数,可以如许配置:
    1. @Bean
    2. public Executor taskExecutor() {
    3.     ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    4.     executor.setCorePoolSize(5);  // 核心池大小
    5.     executor.setMaxPoolSize(10);  // 最大池大小
    6.     executor.setQueueCapacity(25);  // 队列容量
    7.     executor.setThreadNamePrefix("Async-");  // 线程名称前缀
    8.     executor.initialize();
    9.     return executor;
    10. }
    复制代码
    如许可以控制线程池的举动,比如最大线程数、队列容量等。
6. @Async 在你的代码中的作用

在你的场景中,saveUserMessage 方法被标志为 @Async,目标是在用户举行某些操纵(比如发表评论、点赞或收藏)后,异步生存相关的用户消息。生存消息是一个与用户界面交互无关的后台任务,用户不必要等待消息生存完成即可得到反馈(比如立即返回操纵结果)。


  • 避免阻塞:如果没有使用 @Async,每次用户举行评论等操纵时,体系必须比及消息生存到数据库后才能返回相应,如许会导致用户体验不佳,特殊是在高并发场景下。而使用 @Async 后,消息生存操纵会在后台异步执行,不会阻塞主流程。
  • 异步执行的上风:通过异步执行,用户可以在评论、点赞或收藏后立即获得相应,不必要等待数据库操纵完成。如许可以提拔用户体验和体系相应速度。
7. 使用 @Async 的注意事项



  • 并发处置惩罚:由于异步方法是在独立的线程中执行的,以是必要确保方法是线程安全的,避免并发题目。
  • 异常处置惩罚:异步方法抛出的异常不会直接通报给调用者。你必要在异步方法中显式处置惩罚异常,或者通过 Future 等机制处置惩罚。
  • 性能思量:如果在程序中滥用 @Async 或举行过多的长时间运行的异步操纵,大概会导致线程池耗尽、资源紧张等题目。你必要公道配置线程池,并对异步操纵举行优化。
8. 总结

@Async 注解是 Spring 提供的一个强大功能,可以帮助你将一些耗时的操纵(如数据库操纵、文件处置惩罚等)放到后台线程中执行,避免阻塞主线程,从而提拔应用的相应性能和用户体验。在使用时,必要公道配置线程池、处置惩罚异常,并确保方法是线程安全的。
@Async和ExecutorService的区别

@Async 和 ExecutorService 都是 Java 中用来实现异步执行的机制,但它们有差异的使用方式、设计理念和适用场景。下面是对这两者的详细对比:
1. 概述



  • @Async:是 Spring 框架提供的一种异步执行机制,答应方法在另一个线程中异步执行。通过使用 @Async,Spring 会主动管理线程池和异步执行的调度,简化了并发编程的实现。它通常与 Spring 的 AOP(面向切面编程)联合使用,方法的调用会被主动署理到异步执行。
  • ExecutorService:是 Java 提供的一个并发工具类,用于管理线程池和任务的异步执行。它提供了更机动的线程管理和执行控制,适合必要高度自定义线程池配置和管理的场景。
2. 使用方式



  • @Async 使用方式

    • 声明异步方法:在方法上使用 @Async 注解,将该方法标志为异步方法。
    • 返回范例:通常返回 Future、CompletableFuture 或 ListenableFuture,这些范例可以用于获取异步执行的结果或异常。
    • 线程池:Spring 主动为异步方法提供一个线程池,你可以通过配置自定义线程池,也可以使用默认线程池。
    • 主动署理:@Async 基于 Spring AOP 机制,通过署理的方式将方法异步执行,调用者不必要显式创建线程池或管理线程。
    1. @Async
    2. public Future<String> someAsyncMethod() {
    3.     // 异步执行的任务
    4.     return new AsyncResult<>("Task Completed");
    5. }
    复制代码

  • ExecutorService 使用方式

    • 创建线程池:必要显式创建线程池(通过 Executors 或 ThreadPoolExecutor 等),并向线程池提交任务。
    • 提交任务:通过调用 submit() 或 execute() 方法提交任务,返回 Future 对象可以用来获取任务结果或异常。
    • 管理线程池:你必要管理线程池的创建、烧毁、线程数和任务队列等配置。可以根据需求机动配置线程池的大小和其他参数。
    1. ExecutorService executorService = Executors.newFixedThreadPool(10);
    2. Callable<String> task = () -> {
    3.     // 执行任务
    4.     return "Task Completed";
    5. };
    6. Future<String> future = executorService.submit(task);
    7. String result = future.get();  // 阻塞直到任务完成
    复制代码

3. 异步执行的控制



  • @Async

    • 主动署理:Spring 会主动为标志为 @Async 的方法生成署理,并在后台线程池中异步执行。无需手动管理线程池。
    • 简化编程:通过注解简化了异步编程,不必要手动创建和配置线程池,适用于多数简单的异步任务。
    • 线程池配置:可以通过 Spring 配置文件或 Java 配置类自定义线程池,例如 ThreadPoolTaskExecutor。

  • ExecutorService

    • 机动性更高:你可以根据任务的需求机动创建和管理线程池,例如使用 ThreadPoolExecutor 配置核心线程数、最大线程数、队列容量等。
    • 更复杂的控制:你可以直接控制任务的提交、执行、调度等,适合必要更精细控制的场景。

4. 异常处置惩罚



  • @Async

    • 异常不直接抛出:@Async 方法中的异常不会直接抛出给调用者,而是包装在 Future 对象中,调用者必要通过 Future.get() 方法获取并处置惩罚异常。
    • 全局异常处置惩罚:可以通过配置 Spring 的 @Async 异常处置惩罚器来捕获全局异常。

  • ExecutorService

    • 异常捕获:如果通过 submit() 提交的任务抛出异常,异常会被封装在 Future 中,可以通过 Future.get() 方法获取并抛出。
    • 任务执行结果的控制:ExecutorService 必要手动处置惩罚任务执行的异常,通常可以通过 try-catch 来处置惩罚。

5. 线程池管理



  • @Async

    • 线程池管理主动化:Spring 会主动管理线程池,虽然你可以自定义线程池,但大部分环境下,Spring 会根据配置主动分配资源。适用于一样平常环境下的异步任务,不必要手动管理线程池的细节。
    • 简化线程池管理:Spring 的 @Async 使用了 AOP 主动管理线程池和异步执行的调度,开辟者不必要直接操纵线程池。

  • ExecutorService

    • 手动管理线程池:必要显式创建和配置线程池。你可以根据需求创建差异范例的线程池,如固定大小线程池、单线程池、缓存线程池等。
    • 机动性高:ExecutorService 提供了更多的线程池管理功能,答应开辟者自由定制线程池的举动。

6. 性能与适用场景



  • @Async

    • 适用简单场景:适用于方法调用不必要精细控制线程池或任务调度的场景。它的简单性使其适合大多数中小型项目中异步操纵的需求。
    • 性能影响:如果使用默认线程池或没有公道配置线程池,大概会造成线程池资源紧张,从而影响性能。在高并发场景下,大概必要自定义线程池。

  • ExecutorService

    • 适用复杂场景:适合必要细粒度控制线程池、任务调度、任务优先级等的场景。你可以使用 ThreadPoolExecutor 来完全控制线程池的配置。
    • 高并发和大规模任务:适用于必要高并发、复杂线程池管理、或多个异步任务协调的场景。

7. 优缺点总结

特性@AsyncExecutorService易用性使用简单,只需在方法上加注解必要手动创建线程池和管理任务提交机动性机动性较差,适合简单场景非常机动,适用于复杂任务调度线程池管理主动管理,少量配置即可必要手动管理线程池,机动配置线程池举动性能默认线程池适用于一样平常场景,性能不一定最优可根据需求配置线程池,适应高并发和复杂场景异常处置惩罚异常通过 Future 处置惩罚,支持全局异常处置惩罚异常通过 Future.get() 获取和处置惩罚适用场景简单的异步任务,方法级别的异步执行高度自定义线程池,复杂的任务调度和管理 8. 总结



  • 使用 @Async 适合简单的异步任务,尤其是在 Spring 项目中,可以或许简化异步编程,并主动管理线程池,适用于大多数常见的异步任务。
  • ExecutorService 提供更机动和细致的控制,适合复杂的异步任务和高并发场景,尤其是当你必要精细调解线程池配置、任务调度等时。
根据具体的需求来选择使用 @Async 还是 ExecutorService,通常如果是 Spring 项目,且需求较简单,可以优先选择 @Async;而如果必要更复杂的线程管理和调度,则建议使用 ExecutorService。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

欢乐狗

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表