SpringSecurity權限管理系統實戰—三、主要頁面及接口實現

系列目錄

前言

後端五分鐘,前端半小時。。

每次寫js都頭疼。

自己寫前端是不可能的,這輩子不可能自己寫前端的,只能找找別人的模板才能維持的了生存這樣子。github,gitee上的模板又多,幫助文檔又詳細,我超喜歡這兩個平台的。

(下一節進入springsecurity)

一、菜單頁面

我們稍微分析一下數據表,只有菜單頁面的增刪改查幾乎是沒有涉及多個表的,所以我們最先從菜單頁面的邏輯開始寫。

在templates/system目錄下新建menu文件夾,將PearAdmin自帶的power.html移動到menu下,修改一下路由

頁面最終效果是這樣的

在這裡插入圖片描述

layui的table數據表格的用法可以在layui官網上找到示例,我這裡對於前端部分就不詳細解釋了,因為前端我也不咋會,都是根據別人的代碼改了改。

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

<!DOCTYPE html>
<html lang="en" xmlns:th="//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">菜單標題</label>
                <div class="layui-input-inline">
                    <input type="text" name="name" placeholder="請輸入菜單標題" 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: '菜單標題'},
                    {field: 'icon', title: '圖標',templet:'#icon'},
                    {field: 'type', title: '類型',templet:'#power-type'},
                    {field: 'url', title: '路徑'},
                    {field: 'status', title: '是否可用',templet:'#power-status'},
                    {field: 'permission', title: '權限標識'},
                    {field: 'sort', title: '排序'},
                    {field: 'createTime', title: '創建日期'},
                    {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: {//這裡傳參  向後台
                    queryName: name,
                    queryType: type
                    //可傳多個參數到後台...  ,分隔
                }
                , url: '/api/menu'//後台做模糊搜索接口路徑
                , 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('確定刪除嗎,如果存在下級節點則一併刪除,此操作不能撤銷!', {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的數據,因為考慮到有一個模糊查詢返回的數據格式是一樣,所以可以合在一起寫。

MenuDao新建方法

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

因為之前在yml中已經配置了mapper.xml的路徑是在classpath:/mybatis-mappers/下,所以在resources目錄下新建mybatis-mappers文件夾,在其中新建MenuMapper.xml文件。
如果大家不想寫一些簡單的sql語句,推薦大家使用MybatisPlus或者JPA。MybatisPlus可能還要寫一些多表的sql語句,JPA幾乎見不到SQL。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.codermy.myspringsecurityplus.dao.MenuDao">
    <select id="getFuzzyMenu" resultType="com.codermy.myspringsecurityplus.entity.MyMenu">
        -- 建議大家在寫查詢語句的時候不要寫select * ,可以通過這篇文章了解(//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。效果如下圖

在這裡插入圖片描述

在這裡插入圖片描述

點擊箭頭就能快速定位到相應方法,非常好用。

然後就是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 = "系統:菜單管理")
public class MenuController {
    @Autowired
    private MenuService menuService;

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

前端代碼我已經給出來了,重啟項目,打開就是那個效果。

這裡稍微提一下RestFul風格

  • GET :請求從服務器獲取特定資源。舉個例子:GET /blog(獲取所有博客)
  • POST :在服務器上創建一個新的資源。舉個例子:POST /blog(新建博客)
  • PUT :更新服務器上的資源。舉個例子:PUT /blog/12(更新id為 12 的博客)
  • DELETE :從服務器刪除特定的資源。舉個例子:DELETE /blog/12(刪除id為 12 的博客)

還有就是不要類似getAllBlog這種,冗餘沒有意義,形式不固定,不同的開發者還需要了解文檔才能調用。

詳細看這篇文章

查已經完成了(模糊查詢同樣是這個接口,在前端頁面邏輯已經寫好了,裏面給了注釋),接下來就是增刪改了。

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("添加失敗");

    }
	//如果這裡刪除了菜單樹的父節點,把它的子節點一併刪除
    @Override
    public Result delete(Integer id) {
        menuDao.deleteById(id);
        menuDao.deleteByParentId(id);
        return Result.ok().message("刪除成功");
    }

我的後端邏輯寫的不是很完善,比如插入時菜單名是否為空等等,只是在前端寫了一些。這樣普通用戶用是沒有什麼問題,但是有些別有用心的人直接用你的接口,就會瘋狂報錯,造成服務器壓力。

MenuController中添加

	@GetMapping(value = "/edit")
    @ApiOperation(value = "跳轉修改菜單頁面")
    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 = "跳轉添加菜單頁面")
    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);
    }

那麼不難發現我們還需要兩個頁面,分別是menu-add.htmlmenu-edit.html
在對應位置創建,我直接給代碼

menu-add

<!DOCTYPE html>
<html lang="en"  xmlns:th="//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="請輸入菜單名" class="layui-input">
                            </div>
                        </div>
                        <div class="layui-form-item">
                            <label class="layui-form-label">圖標</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">權限標識</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="請輸入排序值" 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">
                                上級菜單
                            </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風格的數據格式
                    response:{message:"msg",statusCode:200},  //修改response中返回數據的定義
                    url: "/api/menu/build",
                    dataFormat: "list",  //配置data的風格為list
                    select: true, //指定下拉樹模式
                    selectTips: "不選默認是頂級目錄",
                    selectCardHeight: "150"
                });

                iconPicker.render({
                    // 選擇器,推薦使用input
                    elem: '#iconPicker',
                    // 數據類型:fontClass/unicode,推薦使用fontClass
                    type: 'fontClass',
                    // 是否開啟搜索:true/false,默認true
                    search: true,
                    // 是否開啟分頁:true/false,默認true
                    page: true,
                    // 每頁顯示數量,默認12
                    limit: 16,
                    // 點擊回調
                    click: function (data) {
                        console.log(data);
                    },
                    // 渲染成功後的回調
                    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個字符';
                        }
                    },
                    sort:  [
                        /^[1-9]\d*$/
                        ,'只能是整數哦'
                    ]
                });
                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));//關閉當前頁
                                    parent.location.reload();
                                });
                            }else{
                                layer.msg(result.msg,{icon:2,time:1000});
                            }
                        }
                    })
                    return false;
                });
            })
        </script>
    </body>
</html>

menu-edit

<!DOCTYPE html>
<html xmlns:th="//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="請輸入菜單名" class="layui-input">
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label">圖標</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">權限標識</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="請輸入排序值" 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">
                        上級菜單
                    </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風格的數據格式
            response:{message:"msg",statusCode:200},  //修改response中返回數據的定義
            url: "/api/menu/build",
            dataFormat: "list",  //配置data的風格為list
            select: true, //指定下拉樹模式
            selectTips: "不選默認是頂級目錄",
            selectCardHeight: "200",
        });
        iconPicker.render({
            // 選擇器,推薦使用input
            elem: '#iconPicker',
            // 數據類型:fontClass/unicode,推薦使用fontClass
            type: 'fontClass',
            // 是否開啟搜索:true/false,默認true
            search: true,
            // 是否開啟分頁:true/false,默認true
            page: true,
            // 每頁顯示數量,默認12
            limit: 12,
            // 點擊回調
            click: function (data) {
                console.log(data);
            },
            // 渲染成功後的回調
            success: function(d) {
                console.log(d);
            }
        });
        form.verify({
            name: function(value){
                if(value.length < 2){
                    return '菜單名至少2個字符';
                }
            },
            sort:  [
                /^[1-9]\d*$/
            ,'只能是整數哦'
        ]
    });
        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));//關閉當前頁
                            parent.location.reload();//刷新頁面
                        });
                    }else{
                        layer.msg(result.msg,{icon:2,time:1000});
                    }
                }
            })
            return false;
        });
    })
</script>
<script type="text/javascript">
</script>
</body>
</html>

重啟項目,訪問一下

在這裡插入圖片描述

在這裡插入圖片描述
這裡的修改是通過model傳來的數據,.通過getMenuById方法返回數據存入model,通過Thymeleaf模板引擎放入指定位置。這裡批量刪除的功能尚未實現,有興趣的同學可以自己實現。

這樣我們這個頁面基本就完成了,接下來的頁面基本都是一個套路。我就不貼全部的代碼了,挑其中部分來說說,全部的代碼可以在giteegithub中獲取,我已經按照每篇文章的進度添加tag,如果哪個部分沒出來的同學可以直接下載哪個部分.。
在這裡插入圖片描述

二、角色頁面

這個部分主要是有個菜單樹,PearAdmin是選用的dtree來實現的。詳細用法請看官網 (我認為很全面了,基本的用法都能找到示例)

在這裡插入圖片描述
主要就是這個菜單樹的數據怎麼傳,在dtree官網上可以看到開啟複選框需要json中有個checkArr值,為0是未選中,1是選中。

那麼我們新建一個MenuDto,來封裝一下我們需要的參數

@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;
    }

這裡我寫了一個TreeUtil工具類

public class TreeUtil {
    //todo 判斷list是否為空
     /**
     * 
     * @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)) {// 如果存在這個數
                MenuDto menuDto = new MenuDto();
                menuDto = menuDtos.get(item-1);
                menuDto.setCheckArr("1");
                menuDtos.set(item-1,menuDto);
            }
        }
        return menuDtos;
    }
}

這個工具類的作用就是通過角色id查詢這個角色所擁有的菜單id,然後再查出所有的菜單id,把他們比較,如果這其中有重複的菜單id,就把這個id對應的MenuDto對象里的checkArr換成1。我這個方法可能會有點繞,如果有小夥伴有更好的方法,歡迎留言告訴我。

然後這個頁面的有需要注意的部分,就是再刪除角色時,要先查詢是否已經有用戶是這個角色了,如果有就不能刪除

在這裡插入圖片描述

三、用戶界面


在這裡插入圖片描述
這裡無非也就是一些增刪改查,要寫的完善點的話也就是新增用戶時手機號是否能相同等等。我這裡新增用戶時,會給他一個默認的密碼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("手機號已存在");
        }
        userDto.setPassword(MD5.crypt("123456"));
        return userService.save(userDto,userDto.getRoleId());
    }

目前用的時MD5的加密,但是這種密碼僅僅是加密了,相對而言會安全一些,但是如果兩個用戶的密碼是一樣的那麼他們加密後的密碼也是一樣的。那麼這其實也有辦法解決,就是給密碼加鹽,加鹽就是給密碼再加一個值,這樣即使不同用戶的相同的密碼在加密後也會不同。詳細解釋。之後會基於SpringSecurity的BCryptPasswordEncoder()方法進行加密,此方法自帶鹽。

那麼這個部分的代碼就完成了,下一章正式進入SpringSecurity部分。

如果有同學不想寫前面部分,可以直接在giteegithub中下載v1.03的tag,裏面是到本篇文章結束的所有代碼。
在這裡插入圖片描述
注意: 裏面的是sql沒有更新,需要重新在倉庫中下載