SpringSecurity許可權管理系統實戰—三、主要頁面及介面實現

codermy發表於2020-08-18

系列目錄

SpringSecurity許可權管理系統實戰—一、專案簡介和開發環境準備
SpringSecurity許可權管理系統實戰—二、日誌、介面文件等實現
SpringSecurity許可權管理系統實戰—三、主要頁面及介面實現
SpringSecurity許可權管理系統實戰—四、整合SpringSecurity(上)
SpringSecurity許可權管理系統實戰—五、整合SpringSecurity(下)
SpringSecurity許可權管理系統實戰—六、SpringSecurity整合jwt
SpringSecurity許可權管理系統實戰—七、處理一些問題
SpringSecurity許可權管理系統實戰—八、AOP 記錄使用者日誌、異常日誌

前言

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

每次寫js都頭疼。

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

(下一節進入springsecurity)

一、選單頁面

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

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

頁面最終效果是這樣的

在這裡插入圖片描述

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

我直接貼上完整的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">選單標題</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"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.codermy.myspringsecurityplus.dao.MenuDao">
    <select id="getFuzzyMenu" resultType="com.codermy.myspringsecurityplus.entity.MyMenu">
        -- 建議大家在寫查詢語句的時候不要寫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。效果如下圖

在這裡插入圖片描述

在這裡插入圖片描述

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

然後就是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="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="請輸入選單名" 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="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="請輸入選單名" 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沒有更新,需要重新在倉庫中下載

相關文章