spring boot +mybatis plus实现操作权限注解

业务场景

在前后端分离的情况下,设置某个用户是否有某个列表的编辑、删除等功能权限;设置之后,需要将当前人员是否具有该数据的操作权限,返回给前端,由前端控制相关功能按钮展示;

在结合了不同单位的展示数据,以及是否只控制当前单位数据的操作权限之后,需要在代码中,加入比较多的循环和判断,然后再给每条数据设置权限结果值,比较繁琐;目前通过mybatis拦截器实现了一套符合当前业务的注解,不够完善,但是能够比较方便的使用。

实现目标

在查询人员列表数据的时候,通过注解,直接在每一条返回数据的结构中,设置好是否能够编辑canEdit和是否能够删除canDel的权限值,布尔类型(true:有权限 false:无权限)或者是整形(1:有权限 0:无权限),前端直接通过每条数据中的相关权限字段值,控制是否显示操作按钮

功能实现

方案

第一步:在查询列表的SQL返回结果实体类中,添加相关权限控制字段canEdit,canDel和权限注解
第二步:通过mybatis拦截器,拦截到查询列表的结果集,在结果集中,根据权限注解的配置,以及查询到的结果数据,判断是否给对应返回结果数据的canEdit,canDel字段写入权限值

依赖

        <!--数据库 mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>

注解

/**
 * 操作权限注解
 *
 * @author likm
 * @date 2021/08/06
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface Operation {
    // authKey,权限值
    String value() default "";

    //权限返回值是否是布尔,默认false
    boolean isBoolean() default false;

    //是否限制控制权限为本单位
    boolean operationCom() default false;

    //设置comId字段名
    String comField() default "comId";
}

获取权限值相关


/**
 * 用户权限查询返回结构
 *
 * @author likm
 */
@Data
public class UserAuth implements Serializable {

    /**
     * 是否所有数据
     */
    private Boolean all;

    /**
     * 是否无权限
     */
    private Boolean none;

    /**
     * 限制的权限值
     */
    private List<Integer> ids;

}

枚举

/**
 * @author likm
 * @date 2021/7/29
 * @description 数据范围权限值相关枚举,模板
 */
public enum DataScopeViewTypeEnum {

    // 不能查看= 0
    //无权限
    VIEW_NONE(0, "无权限"),
    // 查看所有的= 1
    VIEW_ALL(1, "查看所有的"),

    // 查看自己的= 2
    VIEW_ME(2, "查看自己的"),

    // 查看本单位的= 3
    VIEW_COMPANY(3, "查看本单位的"),

    // 查看本单位及其下级单位的= 4
    VIEW_COMPANY_AND_SUB(4, "查看本单位及其下级单位的"),

    ;

    DataScopeViewTypeEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    private Integer code;

    private String name;

    public Integer getCode() {
        return code;
    }

    public String getName() {
        return name;
    }

    public static DataScopeViewTypeEnum getByValue(Integer value) {
        for (DataScopeViewTypeEnum transactType : values()) {
            if (transactType.getCode().equals(value)) {
                return transactType;
            }
        }
        return null;
    }

}

接口

/**
 * 数据范围权限需要业务层实现的接口
 *
 * @Author likm
 * @Date 2021/6/24
 * @Description //TODO
 * @Version 1.0
 **/
public interface UserAuthInterface {

    /**
     * 查询用户对应权限的限制范围值
     *
     * @param authKey 权限值key
     * @return 用户的权限值
     */
    UserAuth getUserAuth(String authKey);
}

接口实现


/**
 * @Author likm
 * @Date 2021/7/29
 * @Description //TODO
 * @Version 1.0
 **/
@Service
public class UserAuthInterfaceImpl implements UserAuthInterface {
	
    @Autowired
    @Lazy //不知道咋回事,在拦截器中自动注入mapper之后,会出现循环依赖,暂时未找到好的解决方式,只能通过懒加载注解来解决
    private BasicCompanyMapper companyMapper;

    @Override
    public UserAuth getUserAuth(String authKey) {
        UserAuth userAuth = new UserAuth();
        //通过authKey和当前用户userId,获取到authKey对应的权限值data
        //StorageUtils.getCurrentUserId() 的实现逻辑,是请求参数中在header中带上token,然后使用拦截器进行解析之后,将用户信息存放在threadLocal中
        Integer data = basicUserMapper.getAuthDataByKey(StorageUtils.getCurrentUserId(), authKey);
        if (!Objects.isNull(authData)) {
            switch (DataScopeViewTypeEnum.getByValue(data)) {
                case VIEW_NONE:
                    userAuth.setNone(Boolean.TRUE);
                    break;
                case VIEW_ALL:
                    userAuth.setAll(Boolean.TRUE);
                    break;
                case VIEW_COMPANY_AND_SUB:
                    List<BasicCompany> companyList = companyMapper.selectList(new LambdaQueryWrapper<BasicCompany>()
                            .eq(BasicCompany::getPid, StorageUtils.getCurrentUserComId()));
                    List<Integer> comIds = companyList.stream().map(BasicCompany::getId).collect(Collectors.toList());
                    comIds.add(StorageUtils.getCurrentUserComId());
                    userAuth.setIds(comIds);
                    break;
                 case VIEW_COMPANY:
                 default:
                 //StorageUtils.getCurrentUserComId() 获取原理同StorageUtils.getCurrentUserId(),都是存放在threadLocal中的数据 
                    userAuth.setIds(Arrays.asList(StorageUtils.getCurrentUserComId()));
                    break;
            }
        }
        return userAuth;
    }
}

mybatis 拦截器实现


/**
 * @Author likm
 * @Date 2021/9/9
 * @Description //TODO
 * @Version 1.0
 **/
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class OperationInterceptor implements Interceptor {

    @Autowired
    private UserAuthInterface authInterface;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List resList = new ArrayList();
        Object target = invocation.getTarget();
        if (target instanceof DefaultResultSetHandler) {
            DefaultResultSetHandler defaultResultSetHandler = (DefaultResultSetHandler) target;
            MetaObject metaStatementHandler = SystemMetaObject.forObject(defaultResultSetHandler);
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("mappedStatement");
            //获取节点属性的集合
            List<ResultMap> resultMaps = mappedStatement.getResultMaps();
            Class<?> resultType = resultMaps.get(0).getType();
            //获取mybatis返回的实体类类型名
            int resultMapCount = resultMaps.size();
            if (resultMapCount > 0) {
                Statement statement = (Statement) invocation.getArgs()[0];
                ResultSet resultSet = statement.getResultSet();
                //获取表头注解,如果没有,则直接返回
                if (!resultType.isAnnotationPresent(Operation.class)) {
                    //注解为null,原数据返回
                    return invocation.proceed();
                }
                if (resultSet != null) {
                    //获得对应列名
                    ResultSetMetaData rsmd = resultSet.getMetaData();
                    Map<String, String> columnMap = new HashMap<>();
                    for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                        //用于比较的字段名,变成驼峰
                        String column = rsmd.getColumnLabel(i);
                        columnMap.put(HumpLineUtil.lineToHump(column), column);
                    }
                    Set<String> columnList = columnMap.keySet();
                    //根据实体类名称,获取authKey,查询data值,然后字段对应的值
                    Map<String, Object> operations = getOperations(resultType);
                    //字段数组
                    Field[] fields = resultType.getDeclaredFields();
                    while (resultSet.next()) {
                        LinkedHashMap<String, Object> resultMap = new LinkedHashMap<>();
                        for (Field field : fields) {
                            String colName = field.getName();
                            Operation operation = field.getAnnotation(Operation.class);
                            if (columnList.contains(colName)) {
                                resultMap.put(colName, resultSet.getString(columnMap.get(colName)));
                            }
                            if (operations.containsKey(colName)) {
                                //将需要注解了的字段值,设置为权限值
                                if (operation.operationCom() && columnList.contains(operation.comField())) {
                                    Integer comId = resultSet.getInt(columnMap.get(operation.comField()));
                                    if (Objects.equals(comId, StorageUtils.getCurrentUserComId())) {
                                        //判断是否限制为当前单位数据
                                        resultMap.put(colName, operations.get(colName));
                                    } else {
                                        resultMap.put(colName, operation.isBoolean() ? Boolean.FALSE : 0);
                                    }
                                } else {
                                    resultMap.put(colName, operations.get(colName));
                                }
                            }
                        }
                        Object o = resultType.newInstance();

                        ConvertUtilsBean convertUtils = BeanUtilsBean
                                .getInstance()
                                .getConvertUtils();
                        //设置,预防bigDecimal类型数据为null导致报错
                        convertUtils.register(false, false, 0);
                        //设置,预防Integer类型数据为null时自动转换为0;
                        convertUtils.register(new IntegerConverter(null), Integer.class);
                        //设置,预防BigDecimal类型数据为null时自动转换为0;
                        convertUtils.register(new BigDecimalConverter(null), BigDecimal.class);
                        BeanUtils.populate(o, resultMap);
                        resList.add(o);

                    }
                    return resList;
                }
            }
        }
        return invocation.proceed();
    }


    private Map<String, Object> getOperations(Class<?> resultType) {
        Map<String, Object> operations = new HashMap<>();
        Field[] fields = resultType.getDeclaredFields();
        Map<String, UserAuth> authData = new HashMap<>();

        for (Field field : fields) {
            if (field.isAnnotationPresent(Operation.class)) {
                Operation operation = field.getAnnotation(Operation.class);
                String authKey = operation.value();
                if (Objects.equals(authKey, "")) {
                    continue;
                }
                UserAuth auth;
                //先从map中获取值,避免当前
                if (authData.containsKey(authKey)) {
                    auth = authData.get(authKey);
                } else {
                    //根据authKey,查询对应data值
                    auth = authInterface.getUserAuth(authKey);
                    authData.put(authKey, auth);
                }
                Object value = null;
                if (!Objects.isNull(auth.getAll()) && auth.getAll()) {
                    if (operation.isBoolean()) {
                        value = Boolean.TRUE;
                    } else {
                        value = DataScopeViewTypeEnum.VIEW_ALL.getCode();
                    }
                }
                if (!Objects.isNull(auth.getNone()) && auth.getNone()) {
                    if (operation.isBoolean()) {
                        value = Boolean.FALSE;
                    } else {
                        value = DataScopeViewTypeEnum.VIEW_NONE.getCode();
                    }
                }
                if (!Objects.isNull(value)) {
                    operations.put(field.getName(), value);
                } else {
                    operations.put(field.getName(), operation.isBoolean() ? false : 0);
                }
            }
        }
        return operations;
    }

    /**
     * //主要是为了把这个拦截器生成一个代理放到拦截器链中
     * ^Description包装目标对象 为目标对象创建代理对象
     *
     * @Param target为要拦截的对象
     * @Return代理对象
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

注入拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    /**
     * 操作权限拦截器
     */
    @Bean
    public OperationInterceptor operationInterceptor() {
        return new OperationInterceptor();
    }
}

使用方式

数据范围权限的使用方式,是在mapper中使用@DataScope注解,在注解中,设置当前sql,所相关权限的authKey值,以及数据范围权限,所限制的表名

@Data
@Operation//这里的注解,控制是否解析当前类,
public class PersonnelVO implements Serializable {

    /**
     * 主键,自增
     */
    private Integer id;

    /**
     * 用户id
     */
    private Integer uid;
    /**
    * Operation注解,value值是控制该权限的authKey,
    * operationCom 表示是否控制为当前单位,
    * comField 是返回单位id的字段名
    * 返回值,根据传入的isBoolean值,返回Integer或者是Boolean
    */
    @Operation(value = "user_list_manage_del", operationCom = true)
    private Integer canDel;

    @Operation(value = "user_list_manage_edit", isBoolean = true)
    private Boolean canEdit;
}

查询

和一般的查询一样,不用做任何修改

@Repository
public interface BasicUserMapper extends BaseMapper<BasicUser> {

    List<BasicUser> getList(Page page, Params param);
}

实现效果

不同的列,操作按钮不一样。
其他的rest接口等相关逻辑,就不一一写出来了
不同的列,操作按钮不一样

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值