SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—三胯盯、主要頁(yè)面及接口實(shí)現(xiàn)

目錄

SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—一、項(xiàng)目簡(jiǎn)介和開發(fā)環(huán)境準(zhǔn)備
SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—二哎甲、日志、接口文檔等實(shí)現(xiàn)
SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—三饲嗽、主要頁(yè)面及接口實(shí)現(xiàn)
SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—四炭玫、整合SpringSecurity(上)
SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—五、整合SpringSecurity(下)
SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—六貌虾、SpringSecurity整合jwt
SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—七吞加、處理一些問題
SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—八、AOP記錄用戶、異常日志
SpringSecurity權(quán)限管理系統(tǒng)實(shí)戰(zhàn)—九衔憨、數(shù)據(jù)權(quán)限的配置

前言

后端五分鐘叶圃,前端半小時(shí)。践图。

每次寫js都頭疼掺冠。

自己寫前端是不可能的,這輩子不可能自己寫前端的码党,只能找找別人的模板才能維持的了生存這樣子德崭。github,gitee上的模板又多揖盘,幫助文檔又詳細(xì)眉厨,我超喜歡這兩個(gè)平臺(tái)的。

(下一節(jié)進(jìn)入springsecurity)

一扣讼、菜單頁(yè)面

我們稍微分析一下數(shù)據(jù)表缺猛,只有菜單頁(yè)面的增刪改查幾乎是沒有涉及多個(gè)表的,所以我們最先從菜單頁(yè)面的邏輯開始寫椭符。

在templates/system目錄下新建menu文件夾,將PearAdmin自帶的power.html移動(dòng)到menu下耻姥,修改一下路由销钝。

頁(yè)面最終效果是這樣的

在這里插入圖片描述

layui的table數(shù)據(jù)表格的用法可以在layui官網(wǎng)上找到示例,我這里對(duì)于前端部分就不詳細(xì)解釋了琐簇,因?yàn)榍岸宋乙膊徽?huì)蒸健,都是根據(jù)別人的代碼改了改。

我直接貼上完整的power.html完整代碼

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/PearAdmin/component/layui/css/layui.css}" />
    <link rel="stylesheet" th:href="@{/PearAdmin/admin/css/pearCommon.css}"/>
</head>
<body class="pear-container">
<div class="layui-card">
    <div class="layui-card-body">
        <form class="layui-form" action="">
            <div class="layui-form-item">
                <label class="layui-form-label">菜單標(biāo)題</label>
                <div class="layui-input-inline">
                    <input type="text" name="name" placeholder="請(qǐng)輸入菜單標(biāo)題" class="layui-input">
                </div>
                <label class="layui-form-label">類型</label>
                <div class="layui-input-inline">
                    <select name="type">
                            <option value=""></option>
                            <option value="1">菜單</option>
                            <option value="2">按鈕</option>
                          </select>
                </div>
                <button class="pear-btn pear-btn-md pear-btn-primary" lay-submit lay-filter="menu-query">
                    <i class="layui-icon layui-icon-search"></i>
                    查詢
                </button>
                <button type="reset" class="pear-btn pear-btn-md">
                    <i class="layui-icon layui-icon-refresh"></i>
                    重置
                </button>
            </div>
        </form>
    </div>
</div>
<div class="layui-card">
    <div class="layui-card-body">
        <table id="power-table" lay-filter="power-table"></table>
    </div>
</div>

<script type="text/html" id="power-toolbar">
    <button class="pear-btn pear-btn-primary pear-btn-md" lay-event="add">
        <i class="layui-icon layui-icon-add-1"></i>
        新增
    </button>
    <button class="pear-btn pear-btn-danger pear-btn-md" lay-event="batchRemove">
        <i class="layui-icon layui-icon-delete"></i>
        刪除
    </button>
</script>

<script type="text/html" id="power-bar">
    <button class="pear-btn pear-btn-primary pear-btn-sm" lay-event="edit"><i class="layui-icon layui-icon-edit"></i></button>
    <button class="pear-btn pear-btn-danger pear-btn-sm" lay-event="remove"><i class="layui-icon layui-icon-delete"></i></button>
</script>

<script type="text/html" id="power-type">
    {{#if (d.type == '1') { }}
    <span>菜單</span>
    {{# }else if(d.type == '2'){ }}
    <span>按鈕</span>
    {{# } }}
</script>

<script type="text/html" id="power-status">
    <input type="checkbox" name="status" value="{{d.id}}" lay-skin="switch" lay-text="啟用|禁用" lay-filter="user-status" checked = "{{ d.id == 10003 ? 'true' : 'false' }}">
</script>

<script type="text/html" id="icon">
    <i class="layui-icon {{d.icon}}"></i>
</script>

<script th:src="@{/PearAdmin/component/layui/layui.js}" charset="utf-8"></script>
<script>
    layui.use(['table','form','jquery','treetable'],function () {
        let table = layui.table;
        let form = layui.form;
        let $ = layui.jquery;
        let treetable = layui.treetable;

        let MODULE_PATH = "operate/";

        window.render = function(){
            treetable.render({
                treeColIndex: 1,
                treeSpid: 0,
                treeIdName: 'powerId',
                treePidName: 'parentId',
                skin:'line',
                method:'post',
                treeDefaultClose: true,
                toolbar:'#power-toolbar',
                elem: '#power-table',
                url: '/api/menu',
                page: false,
                cols: [
                    [
                    {type: 'checkbox'},
                    {field: 'name', minWidth: 200, title: '菜單標(biāo)題'},
                    {field: 'icon', title: '圖標(biāo)',templet:'#icon'},
                    {field: 'type', title: '類型',templet:'#power-type'},
                    {field: 'url', title: '路徑'},
                    {field: 'status', title: '是否可用',templet:'#power-status'},
                    {field: 'permission', title: '權(quán)限標(biāo)識(shí)'},
                    {field: 'sort', title: '排序'},
                    {field: 'createTime', title: '創(chuàng)建日期'},
                    {title: '操作',templet: '#power-bar', width: 150, align: 'center'}
                    ]
                ]
            });
        }

        render();

        table.on('tool(power-table)',function(obj){
            if (obj.event === 'remove') {
                window.remove(obj);
            } else if (obj.event === 'edit') {
                window.edit(obj);
            }
        })


        table.on('toolbar(power-table)', function(obj){
            if(obj.event === 'add'){
                window.add();
            } else if(obj.event === 'refresh'){
                window.refresh();
            } else if(obj.event === 'batchRemove'){
                window.batchRemove(obj);
            }
        });

        form.on('submit(menu-query)', function(data){ //模糊查詢方法
            var formData = data.field;
            var name = formData.name;
            var type = formData.type;
            table.reload(('power-table'),{ // table重載
                where: {//這里傳參  向后臺(tái)
                    queryName: name,
                    queryType: type
                    //可傳多個(gè)參數(shù)到后臺(tái)...  婉商,分隔
                }
                , url: '/api/menu'//后臺(tái)做模糊搜索接口路徑
                , method: 'get'
            });
            return false;
        });
        window.add = function(){
            layer.open({
                type: 2,
                title: '新增',
                shade: 0.1,
                area: ['450px', '500px'],
                content: '/api/menu/add'
            });
        }

        window.edit = function(obj){
            var data = obj.data;
            layer.open({
                type: 2,
                title: '修改',
                shade: 0.1,
                area: ['450px', '500px'],
                content: '/api/menu/edit/?id='+data.id
            });
        }
        window.remove = function(obj){
            var data = obj.data;
            layer.confirm('確定刪除嗎,如果存在下級(jí)節(jié)點(diǎn)則一并刪除似忧,此操作不能撤銷!', {icon: 3, title:'提示'}, function(index){
                layer.close(index);
                let loading = layer.load();
                $.ajax({
                    url: "/api/menu/?id=" + data.id,
                    dataType:'json',
                    type:'delete',
                    success:function(result){
                        layer.close(loading);
                        if(result.success){
                            layer.msg(result.msg,{icon:1,time:1000},function(){
                                obj.del();
                            });
                        }else{
                            layer.msg(result.msg,{icon:2,time:1000});
                        }
                    }
                })
            });
        }
    })
</script>
</body>
</html>

那么首先我們要給的是table的數(shù)據(jù)丈秩,因?yàn)榭紤]到有一個(gè)模糊查詢返回的數(shù)據(jù)格式是一樣盯捌,所以可以合在一起寫。

MenuDao新建方法

     /**
     * 
     * @param queryName 查詢的表題
     * @param queryType 查詢類型
     * @return
     */
    List<MyMenu> getFuzzyMenu(String queryName,Integer queryType);

因?yàn)橹霸趛ml中已經(jīng)配置了mapper.xml的路徑是在classpath:/mybatis-mappers/下蘑秽,所以在resources目錄下新建mybatis-mappers文件夾饺著,在其中新建MenuMapper.xml文件。
如果大家不想寫一些簡(jiǎn)單的sql語(yǔ)句肠牲,推薦大家使用MybatisPlus或者JPA幼衰。MybatisPlus可能還要寫一些多表的sql語(yǔ)句,JPA幾乎見不到SQL缀雳。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.codermy.myspringsecurityplus.dao.MenuDao">
    <select id="getFuzzyMenu" resultType="com.codermy.myspringsecurityplus.entity.MyMenu">
        -- 建議大家在寫查詢語(yǔ)句的時(shí)候不要寫select * 渡嚣,可以通過這篇文章了解(https://blog.csdn.net/qq_36101933/article/details/93973266)
        select t.id,t.parent_id,t.name,t.icon,t.url,t.permission,t.sort,t.type,t.create_time,t.update_time
        from my_menu t
        <where>
            <if test="queryName != null and queryName != ''">
                AND t.name like CONCAT('%', #{queryName}, '%')
            </if>
            <if test="queryType != null and queryType != ''">
                AND t.type = #{queryType}
            </if>
        </where>
        order by t.sort
    </select>
</mapper>

這里再給大家安利一款idea的插件Free Mybatis plugin,它的作用就是可以快速通過xml找到mapper,或者mapper找到xml识椰。效果如下圖

在這里插入圖片描述
在這里插入圖片描述

點(diǎn)擊箭頭就能快速定位到相應(yīng)方法扬绪,非常好用。

然后就是service裤唠,impl挤牛,controller

/**
 * @author codermy
 * @createTime 2020/7/10
 */
public interface MenuService {

    List<MyMenu> getMenuAll(String queryName,Integer queryType);
}
@Service//別忘了注解
public class MenuServiceImpl implements MenuService {
    @Autowired
    private MenuDao menuDao;
    @Override
    public List<MyMenu> getMenuAll(String queryName,Integer queryType) {

        return menuDao.getFuzzyMenu(queryName,queryType);
    }
}
@Controller
@RequestMapping("/api/menu")
@Api(tags = "系統(tǒng):菜單管理")
public class MenuController {
    @Autowired
    private MenuService menuService;

    @GetMapping
    @ResponseBody
    @ApiOperation(value = "菜單列表")
    public Result getMenuAll(String queryName,Integer queryType){//這里沒選擇接收json字符串,前端傳參通過/api/menu种蘸?queryName=測(cè)試的方式
        return Result.ok().data(menuService.getMenuAll(queryName,queryType)).code(ResultCode.TABLE_SUCCESS);
    }
}

前端代碼我已經(jīng)給出來了墓赴,重啟項(xiàng)目,打開就是那個(gè)效果航瞭。

這里稍微提一下RestFul風(fēng)格

  • GET :請(qǐng)求從服務(wù)器獲取特定資源诫硕。舉個(gè)例子:GET /blog(獲取所有博客)
  • POST :在服務(wù)器上創(chuàng)建一個(gè)新的資源。舉個(gè)例子:POST /blog(新建博客)
  • PUT :更新服務(wù)器上的資源刊侯。舉個(gè)例子:PUT /blog/12(更新id為 12 的博客)
  • DELETE :從服務(wù)器刪除特定的資源章办。舉個(gè)例子:DELETE /blog/12(刪除id為 12 的博客)

還有就是不要類似getAllBlog這種,冗余沒有意義滨彻,形式不固定藕届,不同的開發(fā)者還需要了解文檔才能調(diào)用。

詳細(xì)看這篇文章

查已經(jīng)完成了(模糊查詢同樣是這個(gè)接口亭饵,在前端頁(yè)面邏輯已經(jīng)寫好了休偶,里面給了注釋),接下來就是增刪改了辜羊。

MenuDao中添加如下方法

    @Select("select t.id,t.parent_id,t.name,t.icon,t.url,t.permission,t.sort,t.type,t.create_time,t.update_time from my_menu t where t.id = #{id}")
    MyMenu getMenuById(Integer id);

    int update(MyMenu menu);

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into my_menu(parent_id, name, icon, url, permission, sort, type, create_time, update_time)values(#{parentId}, #{name}, #{icon}, #{url}, #{permission}, #{sort}, #{type}, now(), now())")
    int save(MyMenu menu);

    @Delete("delete from my_menu where id = #{id}")
    int deleteById(Integer id);

    @Delete("delete from my_menu where parent_id = #{parentId}")
    int deleteByParentId(Integer parentId);

MenuMapper.xml中添加

<update id="update">
        update my_menu t
        <set>
            <if test="parentId != null">
                parent_id = #{parentId},
            </if>
            <if test="name != null">
                `name` = #{name},
            </if>
            <if test="icon != null">
                `icon` = #{icon},
            </if>
            <if test="url != null">
                url = #{url},
            </if>
            <if test="permission != null">
                permission = #{permission},
            </if>
            <if test="sort != null">
                sort = #{sort},
            </if>
            <if test="type != null">
                type = #{type},
            </if>
            update_time = #{updateTime}
        </set>
        where t.id = #{id}
    </update>

MapperService

    MyMenu getMenuById(Integer id)
        
    Result updateMenu(MyMenu menu);

    Result<MyMenu> save(MyMenu menu);

    Result delete(Integer id);

MapperServiceImpl

    @Override
    public MyMenu getMenuById(Integer id) {
        return menuDao.getMenuById(id);
    }

    @Override
    public Result updateMenu(MyMenu menu) {
        return (menuDao.update(menu) > 0) ? Result.ok().message("修改成功") : Result.error().message("修改失敗");

    }

    @Override
    public Result<MyMenu> save(MyMenu menu) {
        return (menuDao.save(menu) > 0) ? Result.ok().message("添加成功") : Result.error().message("添加失敗");

    }
    //如果這里刪除了菜單樹的父節(jié)點(diǎn)踏兜,把它的子節(jié)點(diǎn)一并刪除
    @Override
    public Result delete(Integer id) {
        menuDao.deleteById(id);
        menuDao.deleteByParentId(id);
        return Result.ok().message("刪除成功");
    }

我的后端邏輯寫的不是很完善,比如插入時(shí)菜單名是否為空等等,只是在前端寫了一些八秃。這樣普通用戶用是沒有什么問題碱妆,但是有些別有用心的人直接用你的接口,就會(huì)瘋狂報(bào)錯(cuò)昔驱,造成服務(wù)器壓力疹尾。

MenuController中添加

    @GetMapping(value = "/edit")
    @ApiOperation(value = "跳轉(zhuǎn)修改菜單頁(yè)面")
    public String editPermission(Model model, MyMenu myMenu) {
        model.addAttribute("myMenu",menuService.getMenuById(myMenu.getId()));
        return "system/menu/menu-edit";
    }

    @PutMapping
    @ResponseBody
    @ApiOperation(value = "修改菜單")
    public Result updateMenu(@RequestBody MyMenu menu) {
        return menuService.updateMenu(menu);
    }


    @GetMapping(value = "/add")
    @ApiOperation(value = "跳轉(zhuǎn)添加菜單頁(yè)面")
    public String addMenu(Model model) {
        model.addAttribute("myMenu",new MyMenu());
        return "system/menu/menu-add";
    }

    @PostMapping
    @ResponseBody
    @ApiOperation(value = "添加菜單")
    public Result<MyMenu> savePermission(@RequestBody MyMenu myMenu) {
        return menuService.save(myMenu);
    }

    //todo 批量刪除
    @DeleteMapping
    @ResponseBody
    @ApiOperation(value = "刪除菜單")
    public Result deleteMenu(Integer id) {
        return menuService.delete(id);
    }

那么不難發(fā)現(xiàn)我們還需要兩個(gè)頁(yè)面,分別是menu-add.htmlmenu-edit.html舍悯。
在對(duì)應(yīng)位置創(chuàng)建航棱,我直接給代碼

menu-add

<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" th:href="@{/PearAdmin/component/layui/css/layui.css}" />
        <link rel="stylesheet" th:href="@{/PearAdmin/admin/css/pearCommon.css}"/>
        <link rel="stylesheet" th:href="@{/PearAdmin/admin/css/pear-tree/dtree.css}" />
        <link rel="stylesheet" th:href="@{/PearAdmin/admin/css/pear-tree/font/dtreefont.css}"/>
    </head>
    <body>
        <form class="layui-form" action="">
            <div class="mainBox">
                <div class="main-container">
                    <div class="main-container">
                        <input type="text" id="id" th:value="${myMenu.id}" name="id" style="display:none;" autocomplete="off" class="layui-input">
                        <div class="layui-form-item">
                            <label class="layui-form-label">
                                <span style="color: red">*</span>菜單名
                            </label>
                            <div class="layui-input-block">
                                <input type="text" th:value="${myMenu.name}" name="name" lay-verify="name" autocomplete="off" placeholder="請(qǐng)輸入菜單名" class="layui-input">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">圖標(biāo)</label>
                            <div class="layui-input-block">
                                <input type="text" id="iconPicker" name="icon" class="hide" th:value="${myMenu.icon}">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">路徑</label>
                            <div class="layui-input-block">
                                <input type="text"  name="url" th:value="${myMenu.url}" autocomplete="off" class="layui-input">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">權(quán)限標(biāo)識(shí)</label>
                            <div class="layui-input-block">
                                <input type="text"  name="permission" th:value="${myMenu.permission}" autocomplete="off" class="layui-input">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">
                                <span style="color: red">*</span>排序
                            </label>
                            <div class="layui-input-block">
                                <input type="text"  name="sort" th:value="${myMenu.sort}" lay-verify="sort" autocomplete="off" placeholder="請(qǐng)輸入排序值" class="layui-input">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">類型</label>
                            <div class="layui-input-block">
                                <input type="radio" name="type" value="1" title="菜單"  th:checked="${myMenu.type == 1}? 'true':'false'">
                                <input type="radio" name="type" value="2" title="按鈕" th:checked="${myMenu.type == 2}? 'true':'false'">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">
                                上級(jí)菜單
                            </label>
                            <div class="layui-input-block">
                                <input type="number" id="parentId" th:value="${myMenu.parentId}" name="parentId"  lay-verify="parentId" style="display:none;width: 0px" autocomplete="off" class="layui-input">
                                <ul id="dataTree" class="dtree" data-id="0" th:data-value="${myMenu.parentId}"></ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="bottom">
                <div class="button-container">
                    <button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit="" lay-filter="user-save">
                        <i class="layui-icon layui-icon-ok"></i>
                        提交
                    </button>
                    <button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">
                        <i class="layui-icon layui-icon-refresh"></i>
                        重置
                    </button>
                </div>
            </div>
        </form>
        <script th:src="@{/PearAdmin/component/layui/layui.js}" charset="utf-8"></script>
        <script>
            layui.use(['iconPicker','dtree','form','jquery'],function(){
                let form = layui.form;
                let $ = layui.jquery;
                let dtree = layui.dtree;
                let formDate = null;
                var iconPicker = layui.iconPicker;
                // 初始化樹
                dtree.render({
                    elem: "#dataTree",
                    initLevel: "1",
                    width: "100%",
                    method: 'get',
                    dataStyle: "layuiStyle",  //使用layui風(fēng)格的數(shù)據(jù)格式
                    response:{message:"msg",statusCode:200},  //修改response中返回?cái)?shù)據(jù)的定義
                    url: "/api/menu/build",
                    dataFormat: "list",  //配置data的風(fēng)格為list
                    select: true, //指定下拉樹模式
                    selectTips: "不選默認(rèn)是頂級(jí)目錄",
                    selectCardHeight: "150"
                });

                iconPicker.render({
                    // 選擇器,推薦使用input
                    elem: '#iconPicker',
                    // 數(shù)據(jù)類型:fontClass/unicode萌衬,推薦使用fontClass
                    type: 'fontClass',
                    // 是否開啟搜索:true/false饮醇,默認(rèn)true
                    search: true,
                    // 是否開啟分頁(yè):true/false,默認(rèn)true
                    page: true,
                    // 每頁(yè)顯示數(shù)量秕豫,默認(rèn)12
                    limit: 16,
                    // 點(diǎn)擊回調(diào)
                    click: function (data) {
                        console.log(data);
                    },
                    // 渲染成功后的回調(diào)
                    success: function(d) {
                        console.log(d);
                    }
                });
                var param = dtree.getNowParam("dataTree");
                formDate = $("#parentId");
                dtree.on("node('dataTree')" ,function(obj){
                    var param = dtree.getNowParam("dataTree");
                    $("#parentId").val(param.nodeId);
                    formDate = $("#parentId");
                });
                form.verify({
                    name: function(value){
                        if(value.length < 2){
                            return '菜單名至少2個(gè)字符';
                        }
                    },
                    sort:  [
                        /^[1-9]\d*$/
                        ,'只能是整數(shù)哦'
                    ]
                });
                form.on('submit(user-save)', function(data){
                    var permissionId = formDate;
                    var bs = data.field.parentId
                    data.field.parentId = Number (bs)
                    var json = JSON.stringify(data.field)
                    $.ajax({
                        url:'/api/menu',
                        data:json,
                        dataType:'json',
                        contentType:'application/json',
                        type:'post',
                        success:function(result){
                            if(result.success){
                                layer.msg(result.msg,{icon:1,time:1000},function(){
                                    parent.layer.close(parent.layer.getFrameIndex(window.name));//關(guān)閉當(dāng)前頁(yè)
                                    parent.location.reload();
                                });
                            }else{
                                layer.msg(result.msg,{icon:2,time:1000});
                            }
                        }
                    })
                    return false;
                });
            })
        </script>
    </body>
</html>

menu-edit

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/PearAdmin/component/layui/css/layui.css}" />
    <link rel="stylesheet" th:href="@{/PearAdmin/admin/css/pearCommon.css}"/>
    <link rel="stylesheet" th:href="@{/PearAdmin/admin/css/pear-tree/dtree.css}" />
    <link rel="stylesheet" th:href="@{/PearAdmin/admin/css/pear-tree/font/dtreefont.css}"/>
</head>
<body>
<form class="layui-form" action="">
    <div class="mainBox">
        <div class="main-container">
            <div class="main-container">
                <input type="text" id="id" th:value="${myMenu.id}" name="id" style="display:none;" autocomplete="off" class="layui-input">
                <div class="layui-form-item">
                    <label class="layui-form-label">
                        <span style="color: red">*</span>菜單名
                    </label>
                    <div class="layui-input-block">
                        <input type="text" th:value="${myMenu.name}" name="name" lay-verify="name" autocomplete="off" placeholder="請(qǐng)輸入菜單名" class="layui-input">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">圖標(biāo)</label>
                    <div class="layui-input-block">
                        <input type="text" id="iconPicker" name="icon" class="hide" th:value="${myMenu.icon}">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">路徑</label>
                    <div class="layui-input-block">
                        <input type="text"  name="url" th:value="${myMenu.url}" autocomplete="off" class="layui-input">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">權(quán)限標(biāo)識(shí)</label>
                    <div class="layui-input-block">
                        <input type="text"  name="permission" th:value="${myMenu.permission}" autocomplete="off" class="layui-input">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">
                        <span style="color: red">*</span>排序
                    </label>
                    <div class="layui-input-block">
                        <input type="text"  name="sort" th:value="${myMenu.sort}" lay-verify="sort" autocomplete="off" placeholder="請(qǐng)輸入排序值" class="layui-input">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">類型</label>
                    <div class="layui-input-block">
                        <input type="radio" name="type" value="1" title="菜單"  th:checked="${myMenu.type == 1}? 'true':'false'">
                        <input type="radio" name="type" value="2" title="按鈕" th:checked="${myMenu.type == 2}? 'true':'false'">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">
                        上級(jí)菜單
                    </label>
                    <div class="layui-input-block">
                        <input type="number" id="parentId" th:value="${myMenu.parentId}" name="parentId"  lay-verify="parentId" style="display:none;width: 0px" autocomplete="off" class="layui-input">
                        <ul id="dataTree" class="dtree" data-id="0" th:data-value="${myMenu.parentId}"></ul>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="bottom">
        <div class="button-container">
            <button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit="" lay-filter="user-save">
                <i class="layui-icon layui-icon-ok"></i>
                提交
            </button>
            <button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">
                <i class="layui-icon layui-icon-refresh"></i>
                重置
            </button>
        </div>
    </div>
</form>
<script th:src="@{/PearAdmin/component/layui/layui.js}" charset="utf-8"></script>
<script type="text/javascript">

    layui.use(['iconPicker','dtree','form','jquery'],function(){
        let form = layui.form;
        let $ = layui.jquery;
        let dtree = layui.dtree;
        var iconPicker = layui.iconPicker;
        // 初始化樹
        dtree.render({
            elem: "#dataTree",
            initLevel: "1",
            width: "100%",
            method: 'get',
            dataStyle: "layuiStyle",  //使用layui風(fēng)格的數(shù)據(jù)格式
            response:{message:"msg",statusCode:200},  //修改response中返回?cái)?shù)據(jù)的定義
            url: "/api/menu/build",
            dataFormat: "list",  //配置data的風(fēng)格為list
            select: true, //指定下拉樹模式
            selectTips: "不選默認(rèn)是頂級(jí)目錄",
            selectCardHeight: "200",
        });
        iconPicker.render({
            // 選擇器朴艰,推薦使用input
            elem: '#iconPicker',
            // 數(shù)據(jù)類型:fontClass/unicode观蓄,推薦使用fontClass
            type: 'fontClass',
            // 是否開啟搜索:true/false,默認(rèn)true
            search: true,
            // 是否開啟分頁(yè):true/false祠墅,默認(rèn)true
            page: true,
            // 每頁(yè)顯示數(shù)量侮穿,默認(rèn)12
            limit: 12,
            // 點(diǎn)擊回調(diào)
            click: function (data) {
                console.log(data);
            },
            // 渲染成功后的回調(diào)
            success: function(d) {
                console.log(d);
            }
        });
        form.verify({
            name: function(value){
                if(value.length < 2){
                    return '菜單名至少2個(gè)字符';
                }
            },
            sort:  [
                /^[1-9]\d*$/
            ,'只能是整數(shù)哦'
        ]
    });
        form.on('submit(user-save)', function(data){
            $.ajax({
                url:'/api/menu',
                data:JSON.stringify(data.field),
                dataType:'json',
                contentType:'application/json',
                type:'put',
                success:function(result){
                    if(result.success){
                        layer.msg(result.msg,{icon:1,time:1000},function(){
                            parent.layer.close(parent.layer.getFrameIndex(window.name));//關(guān)閉當(dāng)前頁(yè)
                            parent.location.reload();//刷新頁(yè)面
                        });
                    }else{
                        layer.msg(result.msg,{icon:2,time:1000});
                    }
                }
            })
            return false;
        });
    })
</script>
<script type="text/javascript">
</script>
</body>
</html>

重啟項(xiàng)目,訪問一下

在這里插入圖片描述
在這里插入圖片描述

這里的修改是通過model傳來的數(shù)據(jù)毁嗦,.通過getMenuById方法返回?cái)?shù)據(jù)存入model亲茅,通過Thymeleaf模板引擎放入指定位置。這里批量刪除的功能尚未實(shí)現(xiàn)狗准,有興趣的同學(xué)可以自己實(shí)現(xiàn)克锣。

這樣我們這個(gè)頁(yè)面基本就完成了,接下來的頁(yè)面基本都是一個(gè)套路腔长。我就不貼全部的代碼了袭祟,挑其中部分來說說,全部的代碼可以在giteegithub中獲取捞附,我已經(jīng)按照每篇文章的進(jìn)度添加tag巾乳,如果哪個(gè)部分沒出來的同學(xué)可以直接下載哪個(gè)部分.。

在這里插入圖片描述

二鸟召、角色頁(yè)面

這個(gè)部分主要是有個(gè)菜單樹胆绊,PearAdmin是選用的dtree來實(shí)現(xiàn)的。詳細(xì)用法請(qǐng)看官網(wǎng) (我認(rèn)為很全面了药版,基本的用法都能找到示例)

在這里插入圖片描述

主要就是這個(gè)菜單樹的數(shù)據(jù)怎么傳辑舷,在dtree官網(wǎng)上可以看到開啟復(fù)選框需要json中有個(gè)checkArr值,為0是未選中,1是選中肢础。

那么我們新建一個(gè)MenuDto还栓,來封裝一下我們需要的參數(shù)

@Data
public class MenuDto implements Serializable {
    private Integer id;
    private Integer parentId;
    private String checkArr = "0";
    private String title;
}

在MenuDao中添加如下方法

    @Select("select                     t.id,t.parent_id,t.name,t.icon,t.url,t.permission,t.sort,t.type,t.create_time,t.update_time from my_menu t where t.id = #{id}")
    MyMenu getMenuById(Integer id);
    @Select("select p.id,p.parent_id,p.name from my_menu p inner join my_role_menu rp on p.id = rp.menu_id where rp.role_id = #{roleId}")
    @Result(property = "title",column = "name")
    List<MenuDto> listByRoleId(Integer roleId);

MenuServiceImpl中

 @Override
    public List<MenuDto> buildMenuAllByRoleId(Integer roleId) {
        List<MenuDto> listByRoleId = menuDao.listByRoleId(roleId);
        List<MenuDto> permissionDtos = menuDao.buildAll();
        List<MenuDto> tree = TreeUtil.tree(listByRoleId, permissionDtos);
        return tree;
    }

這里我寫了一個(gè)TreeUtil工具類

public class TreeUtil {
    //todo 判斷l(xiāng)ist是否為空
     /**
     * 
     * @param listByRoleId 通過角色id查詢的menuid
     * @param menuDtos 返回的menutree
     * @return
     */
    public static List<MenuDto> tree(List<MenuDto> listByRoleId, List<MenuDto> menuDtos ){
       
        List<Integer> collect = listByRoleId.stream().map(MenuDto::getId).collect(Collectors.toList());
        List<Integer> collect1 = menuDtos.stream().map(MenuDto::getId).collect(Collectors.toList());
        for (Integer item : collect) {// 遍歷list2
            if (collect1.contains(item)) {// 如果存在這個(gè)數(shù)
                MenuDto menuDto = new MenuDto();
                menuDto = menuDtos.get(item-1);
                menuDto.setCheckArr("1");
                menuDtos.set(item-1,menuDto);
            }
        }
        return menuDtos;
    }
}

這個(gè)工具類的作用就是通過角色id查詢這個(gè)角色所擁有的菜單id,然后再查出所有的菜單id传轰,把他們比較剩盒,如果這其中有重復(fù)的菜單id,就把這個(gè)id對(duì)應(yīng)的MenuDto對(duì)象里的checkArr換成1慨蛙。我這個(gè)方法可能會(huì)有點(diǎn)繞辽聊,如果有小伙伴有更好的方法,歡迎留言告訴我期贫。

然后這個(gè)頁(yè)面的有需要注意的部分跟匆,就是再刪除角色時(shí),要先查詢是否已經(jīng)有用戶是這個(gè)角色了通砍,如果有就不能刪除

[圖片上傳失敗...(image-64a9a3-1597029061186)]

三玛臂、用戶界面

image

在這里插入圖片描述

這里無(wú)非也就是一些增刪改查烤蜕,要寫的完善點(diǎn)的話也就是新增用戶時(shí)手機(jī)號(hào)是否能相同等等。我這里新增用戶時(shí)迹冤,會(huì)給他一個(gè)默認(rèn)的密碼123456

    @PostMapping
    @ResponseBody
    @ApiOperation(value = "添加用戶")
    public Result<MyUser> saveUser(@RequestBody UserDto userDto){
        MyUser myUser = null;
        myUser = userService.getUserByPhone(userDto.getPhone());
        if(myUser !=null && !(myUser.getId().equals(userDto.getId())) ){
            return Result.error().code(20001).message("手機(jī)號(hào)已存在");
        }
        userDto.setPassword(MD5.crypt("123456"));
        return userService.save(userDto,userDto.getRoleId());
    }

目前用的時(shí)MD5的加密讽营,但是這種密碼僅僅是加密了,相對(duì)而言會(huì)安全一些泡徙,但是如果兩個(gè)用戶的密碼是一樣的那么他們加密后的密碼也是一樣的橱鹏。那么這其實(shí)也有辦法解決,就是給密碼加鹽堪藐,加鹽就是給密碼再加一個(gè)值莉兰,這樣即使不同用戶的相同的密碼在加密后也會(huì)不同。詳細(xì)解釋庶橱。之后會(huì)基于SpringSecurity的BCryptPasswordEncoder()方法進(jìn)行加密贮勃,此方法自帶鹽。

那么這個(gè)部分的代碼就完成了苏章,下一章正式進(jìn)入SpringSecurity部分寂嘉。

如果有同學(xué)不想寫前面部分,可以直接在giteegithub中下載v1.03的tag枫绅,里面是到本篇文章結(jié)束的所有代碼泉孩。

在這里插入圖片描述

注意: 里面的是sql沒有更新,需要重新在倉(cāng)庫(kù)中下載并淋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寓搬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子县耽,更是在濱河造成了極大的恐慌句喷,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兔毙,死亡現(xiàn)場(chǎng)離奇詭異唾琼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)澎剥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門锡溯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哑姚,你說我怎么就攤上這事祭饭。” “怎么了叙量?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵倡蝙,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我宛乃,道長(zhǎng)悠咱,這世上最難降的妖魔是什么蒸辆? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮析既,結(jié)果婚禮上躬贡,老公的妹妹穿的比我還像新娘。我一直安慰自己眼坏,他們只是感情好拂玻,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宰译,像睡著了一般檐蚜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沿侈,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天闯第,我揣著相機(jī)與錄音,去河邊找鬼缀拭。 笑死咳短,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛛淋。 我是一名探鬼主播咙好,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼褐荷!你這毒婦竟也來了勾效?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤叛甫,失蹤者是張志新(化名)和其女友劉穎层宫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體其监,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卒密,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棠赛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膛腐,死狀恐怖睛约,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哲身,我是刑警寧澤辩涝,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站勘天,受9級(jí)特大地震影響怔揩,放射性物質(zhì)發(fā)生泄漏捉邢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一商膊、第九天 我趴在偏房一處隱蔽的房頂上張望伏伐。 院中可真熱鬧,春花似錦晕拆、人聲如沸藐翎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吝镣。三九已至,卻和暖如春昆庇,著一層夾襖步出監(jiān)牢的瞬間末贾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工整吆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拱撵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓掂为,卻偏偏與公主長(zhǎng)得像裕膀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子勇哗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359