springboot+easyui+jpa实现动态权限角色的后台管理系统

Posted lukizzz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot+easyui+jpa实现动态权限角色的后台管理系统相关的知识,希望对你有一定的参考价值。

最近因为多次需要使用easyui的后台管理系统,所以自己写了一个easyui后台管理系统的模版,可修改权限增加角色

(这里先放创建数据库和加载菜单,配置拦截器的方法和遇到的问题)

1.先创建数据库(我是在本地创建的数据库)

资源表:存放菜单资源

技术分享图片

角色表:存放角色数据

技术分享图片

角色和资源的中间表:关联角色表和资源表

技术分享图片

用户表:存放用户数据

技术分享图片

角色和用户中间表:关联角色和用户

技术分享图片

2.创建完数据库和数据表后在项目中创建相应的实体类,控制层,服务层和数据访问层

3.写前端的login.ftl和index.ftl

login.ftl就不放了,放上index.ftl,在加载页面的时候调用js打开一级菜单,在一级菜单打开的时候获取二级菜单

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <title>ace Admin</title>
    <meta name="description" content=""/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <#include "inc/inc.ftl"/>
</head>
<body>
<div id="test"></div>
<div class="easyui-layout" data-options="fit:true">
    <div data-options="region:‘north‘" class="head_body" style="overflow:hidden;">
        <div>
            <div class="head_left"></div>
            <div class="head_right">
                <div class="huanying">
                    欢迎 <span>${Session.userInfo.username}</span> 登陆 | 现在是<span id="show_date"></span>
                </div>
                <div class="anniu">
                    <div class="bb">
                        <a href="javascript:logout();" class="logout"></a>
                        <a id="open_change_password" class="changePwd" href="javascript:editPassword();"></a>
                    </div>
                    <div class="bs">
                        <a class="styleswitch a1" style="CURSOR: pointer" title="黑灰色" rel="black"></a>
                        <a class="styleswitch a2" style="CURSOR: pointer" title="天蓝色" rel="default"></a>
                        <a class="styleswitch a3" style="CURSOR: pointer" title="灰色" rel="bootstrap"></a>
                        <a class="styleswitch a4" style="CURSOR: pointer" title="浅灰色" rel="gray"></a>
                        <a class="styleswitch a5" style="CURSOR: pointer" title="白色" rel="metro"></a>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div data-options="region:‘west‘,split:true,title:‘导航菜单‘" style="width:188px;">
        <div class="easyui-accordion" id="main-accordion" data-options="fit:true,border:false">
        </div>
    </div>

    <div id="mainPanel" data-options="region:‘center‘">
        <div id="index_tabs" class="easyui-tabs" data-options="fit:true,border:false,tabHeight:30"
             style="height:600px;">
            <div title="Home">
                <div style="padding:10px 0 10px 10px">
                    <h2>系统介绍</h2>
                    <div class="light-info">

                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<div id="dlg" class="easyui-dialog" style="width:450px;height:auto;padding:10px 20px" buttons="#dlg-buttons"
     data-options="closed:true,modal:true">
    <form id="fm" method="post" enctype="multipart/form-data">
        <input type="hidden" id="userId" name="userId"/>
        <table class="grid">
            <tr>
                <td>密码:</td>
                <td>
                    <input type="text" name="password" class="easyui-passwordbox" iconWidth="28"
                           data-options="required:true,validType:[‘length[0,20]‘]"/>
                </td>
            </tr>
        </table>
    </form>
</div>
<div id="dlg-buttons">
    <a href="javascript:void(0)" class="easyui-linkbutton c8" data-options="iconCls:‘icon-ok‘"
       onclick="javascript:saveUser();" style="width:90px;">保存</a>
    <a href="javascript:void(0)" class="easyui-linkbutton c2" data-options="iconCls:‘icon-cancel‘"
       onclick="javascript:$(‘#dlg‘).dialog(‘close‘);" style="width:90px;">取消</a>
</div>
</body>
</html>

<script type="text/javascript">
    $(document).ready(function () {
//         设置tab高度,浏览器当前窗口可视区域高度 -头部高度
//         $("#index_tabs").height($(window).height()-$("#navbar").height());
        //显示系统时间
        var myDate = new Date();
        var week = [‘日‘, ‘一‘, ‘二‘, ‘三‘, ‘四‘, ‘五‘, ‘六‘];
        var month = myDate.getMonth() + 1;
        var weekDay = " 星期" + week[myDate.getDay()];
        var showDate = myDate.getFullYear() + "年" + month + "月" + myDate.getDate() + "日" + weekDay;
        $("#show_date").text(showDate);
        loadFirstMenu();
//        loadSecondMenu();
//        $(‘#main-accordion‘).accordion(‘getSelected‘).panel(‘collapse‘);//默认全部关闭

    });

    function loadFirstMenu() {
        $.ajax({
            url: ‘${request.contextPath}/resource/first_menu?userId=‘ +${Session.userInfo.userId},
            type: ‘get‘,
            success: function (data) {
                for (var i in data) {//不使用过滤
                    var mc = $(‘#main-accordion‘).accordion(‘add‘, {
                        title: data[i].name,
                        content: $.formatString(‘<ul id=‘{0}‘ class="easyui-tree menu_tree" style="padding:10px;"></ul>‘, "tree" + data[i].resourceId)
                    });
                    $(‘#‘ + "tree" + data[i].resourceId).tree({
                        url: ‘${request.contextPath}/resource/second_menu?resourceId=‘ + data[i].resourceId + ‘&userId=‘ +${Session.userInfo.userId},
                        method: ‘get‘,
                        parentField: ‘parentId‘,
                        onSelect: function (node) {
                            var start = node.path.indexOf("http");
                            var path = ‘${request.contextPath}‘ + node.path;
                            if (start == 0) {
                                var path = node.path;
                            }
                            addTab(node.text, path, node.iconCls);
                        }
                    });
                }
            }
        });
    }

    //初始化TABS组件
    $(‘#index_tabs‘).tabs({
        fit: true,
        border: false,
        tabHeight: 30,
        tools: [{
            iconCls: ‘icon-reload‘,
            handler: function () {
                var currTab = $(‘#index_tabs‘).tabs(‘getSelected‘);
                var index = $(‘#index_tabs‘).tabs(‘getTabIndex‘, currTab);
                if (index != 0) {
                    var updateUrl = $(currTab.panel(‘options‘).content).attr(‘src‘);
                    $(‘#index_tabs‘).tabs(‘update‘, {
                        tab: currTab,
                        options: {
                            content: refreshTab(updateUrl)
                        }
                    });
                }
            }
        }, {
            iconCls: ‘icon-clear‘,
            handler: function () {
                $.messager.confirm(‘提示‘, ‘确定要全部关闭选项卡?‘, function (r) {
                    if (r) {
                        var tabTitle = new Array();
                        var tabs = $(‘#index_tabs‘).tabs("tabs");
                        var tCount = tabs.length;
                        if (tCount > 0) {
                            for (var i = 0; i < tCount; i++) {
                                tabTitle.push(tabs[i].panel(‘options‘).title);
                            }
                            for (var i = 0; i < tabTitle.length; i++) {
                                if (tabTitle[i] != ‘首页‘) {
                                    $(‘#index_tabs‘).tabs("close", tabTitle[i]);
                                }
                            }
                        }
                    }
                });
            }
        }]
    });

    function editPassword() {
        $(‘#dlg‘).dialog(‘open‘).dialog(‘setTitle‘, ‘修改‘);
        $(‘#userId‘).val(${Session.userInfo.userId});
    }

    function saveUser() {
        $(‘#fm‘).form(‘submit‘, {
            url: "${request.contextPath}/sys/user_save",
            success: function (result) {
                var result = eval(‘(‘ + result + ‘)‘);
                if (result.code == 0) {
                    $.messager.show({
                        title: ‘提示‘,
                        msg: result.message
                    });
                    $(‘#dlg‘).dialog(‘close‘);
                }
            }
        });
    }
</script>

4.将获取一级菜单和二级菜单的http请求发送到对应controller层

package com.lk.modeleasyui.controller;

import com.lk.modeleasyui.beans.MenuDTO;
import com.lk.modeleasyui.domain.SysResource;
import com.lk.modeleasyui.service.SysResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: Lukizzz
 * @Date: 2018/9/11 11:39
 * @Description:
 */
@RestController
@RequestMapping(value = "/resource")
public class SysResourceController {

    @Autowired
    private SysResourceService sysResourceService;

    @GetMapping(value = "/first_menu")
    public List<SysResource> getFirstMenu(Long userId) throws Exception {
        return sysResourceService.getFirstMenu(userId);
    }

    @GetMapping(value = "/second_menu")
    public List<MenuDTO> getSecondMenu(Long resourceId) throws Exception {
        return sysResourceService.getSecondMenu(resourceId);
    }

}

注意使用restController的注解 不然前端无法接收到json

5.service:

package com.lk.modeleasyui.service;

import com.lk.modeleasyui.beans.MenuDTO;
import com.lk.modeleasyui.beans.TreeBean;
import com.lk.modeleasyui.dao.SysResourceDAO;
import com.lk.modeleasyui.dao.SysRoleResourceDAO;
import com.lk.modeleasyui.dao.SysRoleUserDAO;
import com.lk.modeleasyui.domain.SysResource;
import com.lk.modeleasyui.domain.SysRoleResource;
import com.lk.modeleasyui.domain.SysRoleUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: Lukizzz
 * @Date: 2018/9/11 11:51
 * @Description:
 */
@Service
public class SysResourceService {
    @Autowired
    private SysResourceDAO sysResourceDAO;
    @Autowired
    private SysRoleUserDAO sysRoleUserDAO;
    @Autowired
    private SysRoleResourceDAO sysRoleResourceDAO;

    public List<SysResource> getFirstMenu(Long userId) {
        List<SysRoleUser> list = sysRoleUserDAO.findAllByUserId(userId);
        List<Long> roleIds = new ArrayList<>();
        for (SysRoleUser sysRoleUser :list){
            roleIds.add(sysRoleUser.getRoleId());
        }
        List<SysRoleResource> resourceIds = sysRoleResourceDAO.findAllByRoleIdIn(roleIds);
        List<SysResource> sysResources = new ArrayList<>();
        for (SysRoleResource sysRoleResource:resourceIds){
            SysResource sysResource = new SysResource();
            sysResource.setResourceId(sysRoleResource.getResourceId());
            sysResources.add(sysResource);
        }
        return sysResourceDAO.findAllByResourceIds(sysResources);
    }

    public List<MenuDTO> getSecondMenu(Long resourceId) {
        List<SysResource> list = sysResourceDAO.findAllBylist(resourceId);
        List<MenuDTO> menus = new ArrayList<>();
        for(SysResource resource : list){
            MenuDTO menu = new MenuDTO();
            menu.setText(resource.getName());
            menu.setPath(resource.getUrl());
            menu.setIconCls(resource.getIcon());
            menus.add(menu);
        }
        return menus;
    }
}

这里创建了两个类:MenuDTO TreeBean

MenuDTO:

public class MenuDTO extends TreeBean {
    private String path;

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}
TreeBean:
@Data
public class TreeBean {

    private String id;
    private String text;
    private String state = "open";
    private boolean checked = false;
    private Object attributes;
    private String parentId;
    private String iconCls;

}

在测试过程中,如果没有登录直接打开index的页面会出现乱码,原因是没有登录的话session获取不到userInfo,因此我设置了一个拦截器,创建一个LoginInterceptor类和一个工具类WebAppConfig

LoginInterceptor:

public class LoginInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        if (session.getAttribute("userInfo") == null) {
            response.sendRedirect(request.getContextPath() + "/login");
            return false;
        }
        return super.preHandle(request, response, handler);
    }
}

如果session获取的userInfo为空的话就重定向到登陆界面

WebAppConfig:

@Configuration
public class WebAppConfig implements WebMvcConfigurer {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        String[] excludes = new String[]{"/static/**"};
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/login").excludePathPatterns("/do_login").excludePathPatterns(excludes);
        WebMvcConfigurer.super.addInterceptors(registry);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        WebMvcConfigurer.super.addResourceHandlers(registry);
    }

}

在配置拦截器的时候遇到几个小问题:

1.WebMvcConfigurerAdapter过时:

在网络上的大部分配置拦截器的教程都是继承WebMvcConfigurerAdapter,课时现在版本的WebMvcConfigurerAdapter已经被spring认为过时,我找了很久找到了它的替代品,也是官方推荐我们去使用的,实现 WebMvcConfigurer,具体的代码可见上面的WebAppConfig.

2.拦截器是配置成功了,但是登陆界面的静态资源也被拦截了

这个问题的解决方法WebAppConfig代码里也已经有了,首先String[] excluds定义加载的静态资源的路径,excludePathPatterns("/login")这个"/login"代表着这个路径不被拦截,所以我们在下面的代码中加入excludePathPatterns("excluds"),跳过拦截

 

 

在创建登陆的时候还遇到个小问题

    @RequestMapping(value = "/index")
    public String index() {
        return "index";
    }

    @RequestMapping(value = "/")
    public String toIndex() {
        return "index";
    }

本意是想跳转到index页面

但是一直出现报错,错误的大概意思是包名和视图名重复了,修改多次index的名字后无果后来发现是freemarker的依赖没有添加

 

放上效果图:

技术分享图片

 

其他的问题和过程写在下一篇的随笔里

 

以上是关于springboot+easyui+jpa实现动态权限角色的后台管理系统的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法

springboot JPA 一对多关联查询 ,动态复杂查询 去除重复数据 in语句使用

Spring data jpa 实现简单动态查询的通用Specification方法

SpringBoot动态查询 Specification使用

基于Spring Boot,使用JPA动态调用Sql查询数据

springboot-jpa-querydsl