前言

对于设置修改和创建用户博主耽误了挺长时间的,此次也是对于教程装饰器模式有了一个更深的理解。

何为装饰器模式?

允许向一个现有的对象(接口)添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

需求描述

在每个实体上都增加createUserupdateUser字段,分别代表的是创建当前数据的用户和更新当前数据的用户。

难点分析:

1.找到注解使在数据在创建和更新时执行相应的操作
2.获取当前登陆用户

踩坑过程

1.在更新或者创建执行操作

通过关键字在google查询首先看到的是@PrePersist@PreUpdate

@PrePersist:该注解用在方法上可以使该方法在执行持久化操作之前执行
@PreUpdate:该注解用在方法上可以使该方法在执行数据更新并保存操作之前执行
image.png

2.获取当前登陆用户

描述:在@PrePersist@PreUpdate注解的方法上获取当前登陆用户并进行createUserupdateUser设置。

第一想法:
1.直接调用userServicegetCurrentLoginUser(获取当前登陆用户)方法

@Override
  public Optional<User> getCurrentLoginUser() {
    logger.debug("根据认证获取当前登录用户名,并获取该用户");
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication != null) {
      logger.debug("根据username调用userRepository获取用户")
      Optional<User> user = this.userRepository.findByUsername(authentication.getName());
      return user;
    }

    logger.debug("认证用户在数据库中不存在");
    return Optional.empty();
  }

结果:陷入死循环
image.png

其他尝试:
使用@Transient想着将在实体使用到的userRepository设置为实体不存在的属性,结果:又出了其他更多错。

@CreateBy and @LastModifiedBy

@CreateBy:设置创建数据的用户
@LastModifiedBy:设置最近修改数据的用户
注:@CreateBy@LastModifiedBy通常搭配AuditorAware使用

Auditing

What is database Auditing?
It keeps a track of who created or changed an entity and when the change happened.
so:AuditorAware是spring拥有该功能的一个接口

三者搭配使用

1.指定实体的字段使用@CreateBy@LastModifiedBy:

  @ManyToOne
  @JsonView(CreateUserJsonView.class)
  @NotFound(action = NotFoundAction.IGNORE)
  @CreatedBy
  private User createUser;

  @ManyToOne
  @JsonView(UpdateUserJsonView.class)
  @NotFound(action = NotFoundAction.IGNORE)
  @LastModifiedBy
  private User updateUser;

2.在实体上加上@EntityListeners(AuditingEntityListener.class)注解①(或者可以直接用实体实例化AuditorAware接口)②

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class YourClass{
}

@EnableWebMvc
@Configuration
@EnableJpaAuditing
public class WebConfig implements WebMvcConfigurer {
@Bean
    public AuditorAware<User> auditorProvider() {
        return new SpringSecurityAuditorAware();
    }

    private static class SpringSecurityAuditorAware implements AuditorAware<User> {

        @Autowired
        UserService userService;

        @Override
        public Optional<User> getCurrentAuditor() {
            // 获取当前登陆用户
        }
    }
}
@MappedSuperclass
public class BaseEntity implements AuditorAware<User> {
  @Override
  public Optional<User> getCurrentAuditor() {
    // 获取当前登陆用户
  }
}

效果

ERROR:当调用userRepository依然会循环。
由于我们调用的是UserRepositoryfindByUsername方法,它本质上调用CurdRepository的方法。那么它为啥会获取user后认为数据被创建或更新,进而循环呢?由于该方法并不是自己写的,故有很多未知。

解决

从UserDetails下手:书写自己的UserDetails

原来UserDetails是没有user字段的
增加user解决问题

public class UserDetailImpl extends org.springframework.security.core.userdetails.User implements UserDetails {
  private User user;
  public UserDetailImpl(String username, String password, Collection<? extends GrantedAuthority> authorities, User user) {
    super(username, password, authorities);
    this.user = user;
  }

  public User getUser() {
    return user;
  }
}

@Override
  public Optional<User> getCurrentLoginUserFirst() {
    logger.debug("根据认证获取当前登录用户名,并获取该用户");
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication != null) {
      UserDetailImpl userDetail;
      if (authentication instanceof UsernamePasswordAuthenticationToken) {
        logger.debug("第一种登陆后获取当前登陆用户,建议将这种情况摘出来,重新用方法验证");
        userDetail = (UserDetailImpl) authentication.getPrincipal();
      } else if (authentication instanceof UserDetailImpl) {
        logger.debug("第二种登陆用户存在,且在修改数据或新增情况");
        userDetail = (UserDetailImpl) authentication;
      } else if (authentication instanceof AnonymousAuthenticationToken) {
        logger.debug("第三种未登陆情况下修改或新增数据情况,如使用手机验证码修改密码");
        return Optional.empty();
      } else {
        throw new RuntimeException("获取类型不正确");
      }
      return Optional.of(userDetail.getUser());
    }

    logger.debug("认证用户在数据库中不存在");
    return Optional.empty();
  }

解释:其本质是就是运用装饰器模式,定义自己的UserDetails增加一个当前登陆用户user的快照。

效果:相当于给每个登陆用户一个快照,举个栗子:假如当前登陆用户为张三,那么张三登陆后便给张三照张相,那么在接下来这几天假设张三整容了,但是张三登陆的时候拍的照片是没有变化的,此处的思想也是如此

@PrePersist和@PreUpdate实现

前面说到,使用这两个注解出现的主要问题就是调用userRepository.findByUsername()出现的循环问题。修改了获取当前登陆用户方法后,便不再有同样的问题也,但是要注意UserService的注入:

@MappedSuperclass
public class BaseEntity {
  private static UserService userService;

  public static void setUserService(UserService userService) {
    BaseEntity.userService = userService;
  }

  @ManyToOne
  @JsonView(CreateUserJsonView.class)
  @NotFound(action = NotFoundAction.IGNORE)
  @CreatedBy
  private User createUser;

  @PrePersist
  public User setCreateUser() {
    // 在新增保存前执行
    // 获取当前登陆用户,可以参考前边代码,此方法是不调用UserRepository的方法的
    User user = this.userService.getCurrentLoginUserFirst();
    this.createUser = user;
  }
}


@Configuration
public class YourConfigClass {
  @Autowired
  UserService userService

  public YourConfigClass() {
    User.setUserService(this.userService);
  }
}

总结

关于这个需求的实现,我主要卡在了如何不调用UserRepository的findByUsername来获取当前登陆用户,最终是在老师的引导下启用了装饰器的模式,经过此次也是对于自己的编程思想有了一个提升,最后感谢老师的指导和凯强学长的讨论。


郝泽龙_HZ
182 声望2 粉丝