springboot + Mybatis +前端layui 项目总结(二)

一.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更好,后续有时间有能力的话,我会完善一下)

    此处这次项目结束,从一开始的傻眼到最后查询资料和同学的帮助下,一步步解决,重要完结散花,以后的路道阻且长 ,继续不忘初心,砥砺前行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值