一.RBAC的实现
?RBAC重点是数据库的构建,和返回对应前端组件的树结构。
1.1角色树展示接口
xml根据用户id返回layui树组件
<!--查询用户对应的角色-->
<select id="selectUserRole" resultType="com.qcby.teaching.msgmanage.util.TreeNode">
SELECT
r1.id,
r1.role_name title,
CASE
WHEN t.role_id IS NULL THEN
0 ELSE 1
END AS checkArr
FROM
sys_role r1
LEFT JOIN (
SELECT
ur.*,
r.*
FROM
sys_ref_user_role ur
LEFT JOIN sys_role r ON ur.role_id = r.id
<where>
ur.user_id = #{userId}
</where>
) t ON t.id = r1.id
</select>
增加tree的构造函数?
public TreeNode(Long id,String title,String checkArr){
this.id = id;
this.title = title;
this.checkArr = checkArr;
this.pid=0;
}
contrller层
/**
* 根据用户id获取角色信息
* @param userId
* @return
*/
@RequestMapping("selectUserRole")
public List<TreeNode> selectUserRole(Long userId) {
return adminSysUserService.selectUserRole(userId);
}
?ajax接受数据
function getRoleById(oneRoleId){//对角色树进行加工
// var nowRole=new Array();
var roleAll=new Array();
$.ajax({
url: "/rest/role/selectRolePower?roleId="+oneRoleId,
type: "post",
dataType: "json",
async:false,
headers: {'Content-Type': 'application/json;charset=utf-8'}, //接口json格式
success: function (data) {
roleAll=data;
console.log(roleAll);
// for(var i=0;i<roleAll.length;i++){
// roleAll[i]["parentId"]=0;
// }
},
error: function (data) {
layer.alert(JSON.stringify(data), {
title: data
});
}
})
return roleAll;
}
?效果展示
?该用户有那些角色返回那些角色
1.2分配角色(权限)接口
此接口是修改用户表与角色表的关联表的数据
采用的思路是:先根据用户id删除,再添加
service层代码展示
service 层
//编辑用户角色
public boolean roleAssignments(Long userId,List<Long> roleIds);
servicelmpl层
//编辑用户角色
@Override
public boolean roleAssignments(Long userId, List<Long> roleIds) {
//根据用户id删除
sysRefUserRoleMapper.deleteByIds(userId);
//循环添加
for(Long i : roleIds){
SysRefUserRole sysRefUserRole =new SysRefUserRole();
sysRefUserRole.setUserId(userId);
sysRefUserRole.setRoleId(i);
//此处调用的是Mybatis plus的添加方法
sysRefUserRoleMapper.insert(sysRefUserRole);
}
return true;
}
contller层
/**
* 给用户分配角色
*
* @param userId
* @param roleIds
* @return
*/
@RequestMapping("roleAssignments")
public ResultJson roleAssignments(Long userId, @RequestParam List<Long> roleIds) {
log.info("userId" + userId, "roleIds" + roleIds);
boolean yes = adminSysUserService.roleAssignments(userId, roleIds);
ResultJson resultJson = new ResultJson();
resultJson = resultJson.ok("编辑成功");
return resultJson;
}
效果展示
?2.1权限树展示接口
xml对应tree组件
<!--获取角色权限列表-->
<select id="selectRolePower" resultType="com.qcby.teaching.msgmanage.util.TreeNode">
SELECT
p1.id,
p1.power_name title,
p1.father_id parentId,
CASE
WHEN t.role_id IS NULL THEN
0 ELSE 1
END AS checkArr
FROM
sys_power p1
LEFT JOIN (
SELECT
rp.role_id,
p.*
FROM
sys_ref_role_power rp
LEFT JOIN sys_power p ON rp.power_id = p.id
<where>
rp.role_id = #{roleId}
</where>
) t ON t.id = p1.id
</select>
增加tree的构造函数?
public TreeNode(Long id,String title,Integer pid,String checkArr){
this.id = id;
this.title=title;
this.checkArr=checkArr;
this.pid=pid;
}
ajax接受数据
function getRoleById(oneRoleId){//对角色树进行加工
// var nowRole=new Array();
var roleAll=new Array();
$.ajax({
url: "/rest/role/selectRolePower?roleId="+oneRoleId,
type: "post",
dataType: "json",
async:false,
headers: {'Content-Type': 'application/json;charset=utf-8'}, //接口json格式
success: function (data) {
roleAll=data;
console.log(roleAll);
// for(var i=0;i<roleAll.length;i++){
// roleAll[i]["parentId"]=0;
// }
},
error: function (data) {
layer.alert(JSON.stringify(data), {
title: data
});
}
})
return roleAll;
效果展示
?1.3动态菜单的实现
xml
<resultMap id="selectAll" type="com.qcby.teaching.msgmanage.util.TreeNode">
<result column="id" jdbcType="BIGINT" property="id"/>
<result column="role_id" jdbcType="BIGINT" property="roleId"/>
<collection property="children" column="{roleId=role_id,parentId=id}" select="com.qcby.teaching.msgmanage.mapper.MenuMapper.selectFather"/>
</resultMap>
<!---->
<select id="menu" resultMap="selectAll">
SELECT
rp.role_id,
p.id,
p.power_name title,
p.father_id parentId,
p.router href
FROM
sys_ref_role_power rp
LEFT JOIN sys_power p ON rp.power_id = p.id
<where>
rp.role_id = #{roleId}
AND p.father_id = 0
</where>
</select>
<select id="selectFather" resultType="com.qcby.teaching.msgmanage.util.TreeNode">
SELECT
p.id,
p.power_name title,
p.father_id parentId,
p.router href
from sys_power p
LEFT JOIN sys_ref_role_power rp ON rp.power_id = p.id
<where>
father_id =#{parentId} and rp.role_id=#{roleId}
</where>
</select>
Mapper层
List<TreeNode> menu(@Param("roleId") Long roleId);
List<TreeNode> selectFather(@Param("roleId")Long roleId,@Param("parentId")Long
parentId);
Service层
service:
List<TreeNode> menu(Long roleId);
servicelmpl:
@Autowired
private MenuMapper menuMapper;
@Override
public List<TreeNode> menu(Long roleId) {
return menuMapper.menu(roleId);
}
Contller层
登录获取登录角色的roleId,根据roleId获取到对应的权限
@RequestMapping(GlobalConstant.REST_URL_PREFIX + "/menu")
@RestController
@Slf4j
public class RestMenuController {
@Autowired
private GlobalContext globalContext;
@Autowired
private HttpServletRequest request;
@Autowired
private RoleService roleService;
@Autowired
private MenuService menuService;
@RequestMapping("loadIndexLeftMenuJson")
public List<TreeNode> index() {
// 角色信息
SysRole sysRole = JwtUtil.getRole(request, globalContext);
// role => 所有菜单的接口
//List<SysPower> role = roleService.getPowerByRoleIds(sysRole.getId());
log.info("====================" + sysRole.getId());
List<TreeNode> treeNodes = menuService.menu(sysRole.getId());
return treeNodes;
}
}
Html
var $,tab,dataStr,layer;
layui.config({
base : "/js/"
}).extend({
"bodyTab" : "bodyTab"
})
layui.use(['bodyTab','form','element','layer','jquery'],function(){
var form = layui.form,
element = layui.element;//Tab的切换功能,切换事件监听等,需要依赖element模块
$ = layui.$;
layer = parent.layer === undefined ? layui.layer : top.layer;
tab = layui.bodyTab({
openTabNum : "50", //最大可打开窗口数量
url : "/rest/menu/loadIndexLeftMenuJson" //获取菜单json地址
});
//通过顶部菜单获取左侧二三级菜单 注:此处只做演示之用,实际开发中通过接口传参的方式获取导航数据
function getData(json){
$.getJSON(tab.tabConfig.url,function(data){
if(data.code == 500){
dataStr = menuData;
layer.msg(data.msg,{icon:5, shift:6});
}else {
//dataStr = menuData;
dataStr = data;
}
效果展示
登录管理员账号?
显示管理员菜单
?登录教师账号
显示教师菜单
二,aop实现操作日志和权限验证
? ? ? ? ? ? ? ?定义注解实现操作日志和权限验证
package com.qcby.teaching.msgmanage.aop;
import com.qcby.teaching.msgmanage.annotation.LogInsert;
import com.qcby.teaching.msgmanage.common.constant.GlobalConstant;
import com.qcby.teaching.msgmanage.common.constant.GlobalContext;
import com.qcby.teaching.msgmanage.common.constant.LogOperationContext;
import com.qcby.teaching.msgmanage.common.web.ResultJson;
import com.qcby.teaching.msgmanage.entity.LogOperation;
import com.qcby.teaching.msgmanage.entity.SysPower;
import com.qcby.teaching.msgmanage.entity.SysRole;
import com.qcby.teaching.msgmanage.service.LogOperationService;
import com.qcby.teaching.msgmanage.service.RoleService;
import com.qcby.teaching.msgmanage.util.CookieUtil;
import com.qcby.teaching.msgmanage.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.List;
@Slf4j
@Aspect
@Component
public class OperationAop {
@Autowired
private HttpServletRequest request;
@Autowired
private LogOperationService logOperationService;
@Autowired
private RoleService roleService;
@Autowired
private GlobalContext globalContext;
//定义切点,注解作为切入点
@Pointcut("@annotation(com.qcby.teaching.msgmanage.annotation.LogInsert)")
public void viewRecordsPoinCut() {
}
@Around("viewRecordsPoinCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("进入Around通知....");
//获取登陆用户角色id
SysRole sysRole = JwtUtil.getRole(request,globalContext);
log.info("sysRole.getId():****************"+sysRole.getId());
//根据角色id获取权限字符串
List<SysPower> powerString= roleService.selectPowerString(sysRole.getId());
//获取输入的url
String url = request.getRequestURI();
log.info("url:+++++++++"+url);
int i=0;
int flag=-1;
while(i<powerString.size()){
String powers = powerString.get(i).getPowerString();
char[] a = powers.toCharArray();
char[] b = url.toCharArray();
flag = check(a,b);
if(flag<0){
i++;
continue;
}
else {
log.info("=======>校验通过!");
break;
}
}
// ResultJson resultJson = new ResultJson();
// log.error("无权访问===>"+new ResultJson(500, GlobalConstant.NO_POWERS).toString());
// return resultJson.error("无权访问");
if(flag<0){
ResultJson resultJson = new ResultJson();
log.error("无权访问===>"+new ResultJson(500, GlobalConstant.NO_POWERS).toString());
return resultJson.error("无权访问");
}
//获取操作日志
String token = CookieUtil.INSTANCE.getTokenFromCookie(request);
Long account = JwtUtil.getUserId(request);
log.info("识别到的账户:===>"+account);
// 校验合法
JwtUtil.verifyToken(token,account.toString());
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
log.info("method-name:{}",method.getName());
// 获取注解信息,用于记录操作日志
LogInsert logrRegister = method.getAnnotation(LogInsert.class);
if(logrRegister!=null){
String type = logrRegister.type();
LogOperation logOperation = new LogOperation();
logOperation.setAccount(account);
logOperation.setHandleType(Integer.valueOf(type));
logOperation.setHandleTime(LocalDateTime.now());
switch (type){
case LogOperationContext.ADD:
logOperation.setHandleDescription("插入操作=>"+method.getName());
break;
case LogOperationContext.SELECT:
logOperation.setHandleDescription("查询操作=>"+method.getName());
break;
case LogOperationContext.DELETE:
logOperation.setHandleDescription("删除操作=>"+method.getName());
break;
case LogOperationContext.UPDATE:
logOperation.setHandleDescription("编辑操作=>"+method.getName());
break;
}
boolean insertLog = logOperationService.save(logOperation);
}
Object r = joinPoint.proceed();
return r;
}
public static int check(char[] a,char[] b) {
int i = 0 , j =0;
while(i<a.length&&j<b.length) {
if(a[i] == b[j]) {
i++;
j++;
}else {
i = i-j+1;
j=0;
}
}
if(j==b.length) {
return 1;
}else {
return -1;
}
}
}
注解
//生成操作日志注解
@Retention(RetentionPolicy.RUNTIME) //运行时生效
@Target(ElementType.METHOD) // 只可以在方法上使用
public @interface LogInsert {
String type() default LogOperationContext.SELECT;
String id() default "id" ;
}
LogOperationContext是自定义的常量
public class LogOperationContext {
public final static String SELECT="1"; //查找
public final static String ADD="0"; //插入
public final static String DELETE="2"; //删除
public final static String UPDATE="3"; //修改
}
实现原理:
? ? ? ? 操作日志就是注解再增删查改各个方法上,使其type等于相应的常量,在封装到操作日志实体对象中,通过一个添加将其添加到数据库
? ? ? ? 而权限验证则是根据其角色id获取到相应权限表的权限字符串,与其手动输入的url对比,如果输入的url里面有对应的权限字符串,则可以访问该接口,如果没有则拒绝访问此接口
效果展示:
操作日志:
1.登录管理员查看角色管理:
2.查看角色列表
?查看数据库
权限验证:
1.登录教师账号
?2.因为登录的是老师他是没有查看角色管理的权限的,此时手动输入角色管理页面(返回无权访问,此处报错404更好,后续有时间有能力的话,我会完善一下)
? ??此处这次项目结束,从一开始的傻眼到最后查询资料和同学的帮助下,一步步解决,重要完结散花,以后的路道阻且长 ,继续不忘初心,砥砺前行。
|