5

在写区域工作人员端任务管理时需要获取登陆人员所管理的居民,这时就出现了一个问题——后台要怎么实现这些功能呢?
我们可以发现前台并没有把当前登录用户传给后台,所以理论上应该是直接从后台获取当前登录用户。
之前项目中在居民管理中也是这种情况,所以就先总结了一下。
把相应的分页查询数据传给后台C层后,后台就直接调用了residentService.page方法。

  public Page<Resident> page(String name,
                           . . .
                             Pageable pageable) {
    District district = this.filterDistrictOfCurrentUserAccess(districtId);
    Specification<Resident> specification = this.getSpec(name,
       . . .
        beVaccinated);
    Page<Resident> residents = this.residentRepository.findAll(specification, pageable);

    return residents;
  }

我们可以发现这里通过了filterDistrictOfCurrentUserAccess方法获取了当前登陆人员的区域ID,并把它加入到综合查询条件中。
其中对于综合查询中查询属于某个地区的居民是这样做的。

  public static Specification<Resident> belongDistrict(District district) {
    if (district == null ||
        district.getId() == null ||
        district.getType() == null ||
        TYPE_COUNTY.equals(district.getType())) {
      logger.debug("未传入区域信息或区域ID为null或传入了区域为根区域,忽略查询条件");
      return Specification.where(null);
    }

    return (root, criteriaQuery, criteriaBuilder) -> {
      logger.debug("分别按楼、小区、社区、乡镇(默认)进行查询");
      Join<Resident, Building> buildingJoin = root.join("houses")
          .join("building", JoinType.LEFT);
      Long districtId = district.getId();
      switch (district.getType()) {
        case TYPE_BUILDING:
          return criteriaBuilder.equal(buildingJoin.get("id").as(Long.class), districtId);
        case TYPE_VILLAGE:
          return criteriaBuilder.equal(buildingJoin.join("parent").get("id").as(Long.class),
              districtId);
        case TYPE_COMMUNITY:
          return criteriaBuilder.equal(buildingJoin
                  .join("parent", JoinType.LEFT)
                  .join("parent", JoinType.LEFT)
                  .get("id").as(Long.class),
              districtId);
        default:
          return criteriaBuilder.equal(buildingJoin
                  .join("parent", JoinType.LEFT)
                  .join("parent", JoinType.LEFT)
                  .join("parent", JoinType.LEFT)
                  .get("id").as(Long.class),
              districtId);
      }
    };
  }

看起来很多其实逻辑很简单,先是判断传入的区域信息是否完整,若不完整则返回null。
之后因为查询的最小单位是楼所以直接构造了一个从building起始的Join对象

Join<Resident, Building> buildingJoin = root.join("houses")
          .join("building", JoinType.LEFT);

其中的root代表的就是resident,之后进入houses属性再进入building属性。

criteriaBuilder.equal(buildingJoin.get("id").as(Long.class), districtId);

再根据传入的districtId查询等于buildingId的resident;
之后我们要做的就是根据传入的district.getType()进行分类讨论即如果是小区的话那么就在此building基础上获取parent再进行查询。

之后我们再来看一下在实际项目中是如何获取当前登陆用户并获取其区域ID的。
先是过滤一下传入的district,如果传入了districtId,则看当前登录用户是否拥有传入的区域ID的管理权限。有权限,返回对应区域;无权限,返回当前登录用户所在区域;如果未传入districtId直接返回登录用户所在区域。

在此情况下我们先讨论如何获取当前登陆用户。

public Optional<WebUser> getCurrentLoginWebUser() {
    return this.webUserRepository.findById(this.getCurrentLoginWebUserId()
        .orElseThrow(() -> new AccessDeniedException("当前登录类型不正确或未登录")));
  }

/**
获取当前登陆用户ID
**/
public Optional<Long> getCurrentLoginWebUserId() {
    AuthUserDetails authUserDetails = this.userService.getAuthUserDetailWithoutTransaction()
        .orElseThrow(() -> new AccessDeniedException("当前登录类型不正确或未登录"));
    if (authUserDetails instanceof WebUser) {
      return Optional.of(((WebUser) authUserDetails).getId());
    } else {
      return Optional.empty();
    }
  }
  public Optional<AuthUserDetails> getAuthUserDetailWithoutTransaction() {
    logger.debug("根据认证获取当前登录用户名,并获取该用户");
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication != null) {
      AuthUserDetails userDetail;
      if (authentication instanceof UsernamePasswordAuthenticationToken) {
        userDetail = (AuthUserDetails) authentication.getPrincipal();
      } else if (authentication instanceof AuthUserDetails) {
        userDetail = (AuthUserDetails) authentication;
      } else if (authentication instanceof AnonymousAuthenticationToken) {
        return Optional.empty();
      } else {
        throw new RuntimeException("获取类型不正确");
      }
      return Optional.of(userDetail);
    }

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

根据上述代码我们可以发现SpringBoot已经对登陆进行了一部分的封装,我们在登陆后只需下面这部分代码就可获取当前登陆用户的信息

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

之后我们在再根据返回的对象的类型进行分类讨论并把它转化为AuthUserDetails即可。

问题二

使用前后台登陆后发现菜单界面消失,但是手动输入地址可以进入到想要的页面.

图片.png
所以首先要做的就是了解菜单是怎么获取的,先用前台测试了一下,发现菜单可以正常显示,
之后再去前台代码中寻找后发现每个菜单都配有一个role选项和用户相对应.

{
          name: '就业人员',
          url: 'employed-persons',
          icon: 'fa fa-user',
          roles: [ROLE_TYPE.volunteer.value, ROLE_TYPE.admin.value]
        }

下面就是获取菜单的方法:

this.userService.currentLoginUser$.subscribe(
        user => {
          console.log(user);
          const roleKeys = user ? user.roles.map(role => role.value) : [];
          subscribe.next(
            MenuService.menus.filter(menu => {
              const menuRoleKeys = menu.roles;
              let found = false;
              menuRoleKeys.forEach(roleKey => {
                if (!found && (roleKeys.indexOf(roleKey) !== -1)) {
                  found = true;
                }
              });
              return found;
            })
          );
        }
      );

先获取当前登陆用户的角色数组,再判断是否和菜单中的角色数组相匹配再返回查找结果,于是尝试着打印获取到的角色:
图片.png,发现roles确实为空,之后再去数据库中查看后发现角色_用户的关联表也确实为空,将上述关系重新添加后恢复正常.


李明
441 声望18 粉丝