一般公司开发项目,多数以管理后台为多,基础功能有登录注册,用户角色权限等功能,今天要写的是在用户绑定角色,角色绑定功能权限以后,然后再去绑定数据权限。
数据权限主要作用是控制当前用户能访问什么样的数据,一般有几种,以我们项目为例:(1:全部可见 2:所在部门及子部门可见 3:所在部门可见 4:仅个人可见)用户登录系统以后会根据我设定的这几种数据权限去查询对应的数据列表。
数据库设计:需要一张sys_user,sys_org,sys_role,sys_user_role,sys_org_role,主要在控制角色绑定什么数据权限,需要一个字段data_scope,用来标识当前角色的数据权限,剩下的就需要在代码中处理。
第一步:需要创建自定义注解用来获取aop切面类中对象信息,注解一共有两个参数,第一个是部门表,第二个是用户表,当然你还可以加更对参数,对于不了解自定义注解的可以先学习一下,比如多数据源切换也会用到自定义注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 数据权限过滤注解
*
* @author guoyunlong
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/**
* 部门表的别名
*/
String deptAlias() default "";
/**
* 用户表的别名
*/
String userAlias() default "";
}
第二步:在SysUserServiceImpl里面查询方法加上@DataScope(deptAlias = “o”, userAlias = “u”)用于获取切面类方法
第三部:需要做数据权限的实体列统一extends BaseEntity
/**
* Entity基类
*
* @author guoyunlong
*/
@Data
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 请求参数
*/
@ApiModelProperty(hidden = true)
private Map<String, Object> param;
public Map<String, Object> getParam() {
if (param == null) {
param = new HashMap<>();
}
return param;
}
public void setParams(Map<String, Object> param) {
this.param = param;
}
}
第四步:编写Aop切面类,注意这个类的每个方法,这是实现数据权限重要的地方,里面我都有注释
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.pactera.model.entity.BaseEntity;
import com.pactera.model.entity.spaces.SpaceSysOrg;
import com.pactera.model.entity.spaces.SpaceSysRole;
import com.pactera.model.entity.spaces.SpaceSysUser;
import com.pactera.utlis.StringUtils;
/**
* 数据过滤处理
*
* @author guoyunlong
*/
@Aspect
@Component
public class DataScopeAspect {
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 部门及以下部门数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "4";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "dataScope";
/**
* 配置切入点
*/
@Pointcut("@annotation(com.pactera.acpect.DataScope)")
public void dataScopePointCut() {
}
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable {
handleDataScope(point);
}
protected void handleDataScope(final JoinPoint joinPoint) {
// 获得注解
DataScope controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null) {
return;
}
// 获取当前登录用户信息
// LoginUser loginUser = new LoginUser();
// loginUser.setToken("e958eeb6-fab3-48cc-8e16-d526cd51a5a5");
SpaceSysUser user = new SpaceSysUser();
user.setUserId(2L);
user.setOrgId(1l);
user.setUserName("along");
SpaceSysRole role = new SpaceSysRole();
role.setRoleId(4L);
role.setDataScope(2);
SpaceSysRole role1 = new SpaceSysRole();
role1.setRoleId(1L);
role1.setDataScope(1);
SpaceSysOrg sysOrg = new SpaceSysOrg();
sysOrg.setOrgId(1l);
sysOrg.setName("谷歌");
user.setOrg(sysOrg);
List<SpaceSysRole> roles = new ArrayList<>();
roles.add(role);
roles.add(role1);
user.setRoles(roles);
if (StringUtils.isNotNull(user)) {
boolean admin = user.isAdmin();
// 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(user) && !admin) {
dataScopeFilter(joinPoint, user, controllerDataScope.deptAlias(),
controllerDataScope.userAlias());
}
}
}
/**
* 数据范围过滤
*
* @param joinPoint 切点
* @param user 用户
* @param userAlias 别名
*/
public static void dataScopeFilter(JoinPoint joinPoint, SpaceSysUser user, String deptAlias, String userAlias) {
StringBuilder sqlString = new StringBuilder();
for (SpaceSysRole role : user.getRoles()) {
String dataScope = role.getDataScope().toString();
if (DATA_SCOPE_ALL.equals(dataScope)) {
sqlString = new StringBuilder();
break;
} else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
sqlString.append(StringUtils.format(
" OR {}.org_id IN ( SELECT org_id FROM space_sys_org WHERE org_id = {} or find_in_set( {} , "
+ "ancestors ) )",
deptAlias, user.getOrgId(), user.getOrgId()));
} else if (DATA_SCOPE_DEPT.equals(dataScope)) {
sqlString.append(StringUtils.format(" OR {}.org_id = {} ", deptAlias, user.getOrgId()));
} else if (DATA_SCOPE_SELF.equals(dataScope)) {
if (StringUtils.isNotBlank(userAlias)) {
sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
} else {
// 数据权限为仅本人且没有userAlias别名不查询任何数据
sqlString.append(" OR 1=0 ");
}
}
}
if (StringUtils.isNotBlank(sqlString.toString())) {
Object params = joinPoint.getArgs()[0];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) params;
baseEntity.getParam().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
}
}
}
/**
* 是否存在注解,如果存在就获取
*/
private DataScope getAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(DataScope.class);
}
return null;
}
}
第五步:在sql语句中加上获取注解,一定要放在sql最后面
SELECT
*
FROM
space_sys_user u
LEFT JOIN space_sys_org o ON u.org_id = o.org_id
WHERE
u.`status` !=3
<!-- 数据范围过滤 -->
${param.dataScope}
切面类具体流程是这样的,在用户访问login登陆以后,会去查询某个模块的数据列表,然后会根据serviceimpl里面具体查询方法上的自定义注解( @DataScope(deptAlias = “o”, userAlias = “u”))去访问aop切类,然后根据切面类中的dataScopePointCut先去获取配置的切入点,然后调用handleDataScope获取当前方法注解,在根据登录用户信息中获取到用户的角色,数据权限,下来就调用dataScopeFilter,进行权限判断,进行sql拼接,这个拼接是在最后查询sql最后面or一个查询方法,
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
了解这个对象的概念,因为他是实现切面类重要的组成部分,下面我会给出一个我写的例子,参考以后可以自己去实现
controller,不管你是传什么类型参数,都需要传对象,因为切面类 joinPoint.getArgs()[0];获取的是对象
/**
* 用户管理列表
*/
@ApiOperation("用户管理列表")
@GetMapping("list")
public ResponseEntity<ResultData> list(Integer pageNum, Integer pageSize, SpaceSysUser user) {
PageBean pageBean = initPageBean(pageNum, pageSize);
PageInfo<SpaceSysUser> list = sysUserService.list(user, pageBean);
pageBean.setTotal(list.getTotal());
return ResponseEntity.ok(new ResultData(convertPageToMap(list.getList(), pageBean)));
}
serviceimpl,这里面主要是要加上自定义注解
/**
* 用户管理列表
*
* @return
*/
@Override
@DataScope(deptAlias = "o", userAlias = "u")
public PageInfo<SpaceSysUser> list(SpaceSysUser user, PageBean pageBean) {
PageHelper.startPage(pageBean.getPageNum(), pageBean.getPageSize());
List<SpaceSysUser> sysUsers = sysUserMapper.selectUserList(user);
return new PageInfo<>(sysUsers);
}
xml,在sql最后加上获取注解参数,这样就可以把切面类中拼接的sql放到要执行的sql后面,完成数据权限的查询
<select id="selectUserList" parameterType="com.*.SpaceSysUser" resultMap="BaseResultMap">
SELECT
*
FROM
space_sys_user u
LEFT JOIN space_sys_org o ON u.org_id = o.org_id
WHERE
u.`status` !=3
<!-- 数据范围过滤 -->
${param.dataScope}
</select>
完结