1. 程式人生 > >SpringSecurity許可權管理系統實戰—三、主要頁面及介面實現

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

## 系列目錄 [SpringSecurity許可權管理系統實戰—一、專案簡介和開發環境準備](https://www.cnblogs.com/codermy/p/13516372.html) [SpringSecurity許可權管理系統實戰—二、日誌、介面文件等實現](https://www.cnblogs.com/codermy/p/13516369.html) [SpringSecurity許可權管理系統實戰—三、主要頁面及介面實現](https://www.cnblogs.com/codermy/p/13516379.html) [SpringSecurity許可權管理系統實戰—四、整合SpringSecurity(上)](https://blog.csdn.net/HYDCS/article/details/107367064) [SpringSecurity許可權管理系統實戰—五、整合SpringSecurity(下)](https://blog.csdn.net/HYDCS/article/details/107510905) [SpringSecurity許可權管理系統實戰—六、SpringSecurity整合jwt](https://blog.csdn.net/HYDCS/article/details/107732916) [SpringSecurity許可權管理系統實戰—七、處理一些問題](https://blog.csdn.net/HYDCS/article/details/107765898) [SpringSecurity許可權管理系統實戰—八、AOP 記錄使用者日誌、異常日誌](https://blog.csdn.net/HYDCS/article/details/107965522) ## 前言 後端五分鐘,前端半小時。。 每次寫js都頭疼。 自己寫前端是不可能的,這輩子不可能自己寫前端的,只能找找別人的模板才能維持的了生存這樣子。github,gitee上的模板又多,幫助文件又詳細,我超喜歡這兩個平臺的。 **(下一節進入springsecurity)** ## 一、選單頁面 我們稍微分析一下資料表,只有選單頁面的增刪改查幾乎是沒有涉及多個表的,所以我們最先從選單頁面的邏輯開始寫。 在templates/system目錄下新建menu資料夾,將PearAdmin自帶的power.html移動到menu下,**修改一下路由**。 頁面最終效果是這樣的 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714172733473.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) layui的table資料表格的用法可以在[layui官網](https://www.layui.com/demo/table.html)上找到示例,我這裡對於前端部分就不詳細解釋了,因為前端我也不咋會,都是根據別人的程式碼改了改。 我直接貼上完整的power.html完整程式碼 ```html Title
``` 那麼首先我們要給的是table的資料,因為考慮到有一個模糊查詢返回的資料格式是一樣,所以可以合在一起寫。 MenuDao新建方法 ```java /** * * @param queryName 查詢的表題 * @param queryType 查詢型別 * @return */ List getFuzzyMenu(String queryName,Integer queryType); ``` 因為之前在yml中已經配置了mapper.xml的路徑是在classpath:/mybatis-mappers/下,所以在resources目錄下新建mybatis-mappers資料夾,在其中新建MenuMapper.xml檔案。 如果大家不想寫一些簡單的sql語句,推薦大家使用[MybatisPlus](https://mybatis.plus/)或者[JPA](https://spring.io/projects/spring-data-jpa)。MybatisPlus可能還要寫一些多表的sql語句,JPA幾乎見不到SQL。 ```xml
``` 這裡再給大家安利一款idea的外掛Free Mybatis plugin,它的作用就是可以快速通過xml找到mapper,或者mapper找到xml。效果如下圖 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714172835772.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714172859138.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) 點選箭頭就能快速定位到相應方法,非常好用。 然後就是service,impl,controller ```java /** * @author codermy * @createTime 2020/7/10 */ public interface MenuService { List getMenuAll(String queryName,Integer queryType); } ``` ```java @Service//別忘了註解 public class MenuServiceImpl implements MenuService { @Autowired private MenuDao menuDao; @Override public List getMenuAll(String queryName,Integer queryType) { return menuDao.getFuzzyMenu(queryName,queryType); } } ``` ```java @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這種,冗餘沒有意義,形式不固定,不同的開發者還需要了解文件才能呼叫。 詳細看[這篇文章](https://zhuanlan.zhihu.com/p/91240556) 查已經完成了(模糊查詢同樣是這個介面,在前端頁面邏輯已經寫好了,裡面給了註釋),接下來就是增刪改了。 MenuDao中新增如下方法 ```java @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中新增 ```xml update my_menu t parent_id = #{parentId},
`name` = #{name}, `icon` = #{icon}, url = #{url}, permission = #{permission}, sort = #{sort}, type = #{type}, update_time = #{updateTime}
where t.id = #{id}
``` MapperService ```java MyMenu getMenuById(Integer id) Result updateMenu(MyMenu menu); Result save(MyMenu menu); Result delete(Integer id); ``` MapperServiceImpl ```java @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 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中新增 ```java @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 savePermission(@RequestBody MyMenu myMenu) { return menuService.save(myMenu); } //todo 批量刪除 @DeleteMapping @ResponseBody @ApiOperation(value = "刪除選單") public Result deleteMenu(Integer id) { return menuService.delete(id); } ``` 那麼不難發現我們還需要兩個頁面,分別是`menu-add.html`和`menu-edit.html`。 在對應位置建立,我直接給程式碼 menu-add ```html Title
    ``` menu-edit ```html Title
      ``` 重啟專案,訪問一下 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714173043522.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714173054865.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) 這裡的修改是通過model傳來的資料,.通過getMenuById方法返回資料存入model,通過Thymeleaf模板引擎放入指定位置。這裡批量刪除的功能尚未實現,有興趣的同學可以自己實現。 這樣我們這個頁面基本就完成了,接下來的頁面基本都是一個套路。我就不貼全部的程式碼了,挑其中部分來說說,全部的程式碼可以在[gitee](https://gitee.com/witmy/my-springsecurity-plus)和[github](https://github.com/witmy/my-springsecurity-plus)中獲取,我已經按照每篇文章的進度新增tag,如果哪個部分沒出來的同學可以直接下載哪個部分.。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714172338439.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) ## 二、角色頁面 這個部分主要是有個選單樹,PearAdmin是選用的dtree來實現的。詳細用法請看[官網](http://www.wisdomelon.com/DTreeHelper/) (我認為很全面了,基本的用法都能找到示例) ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714173141542.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) 主要就是這個選單樹的資料怎麼傳,在dtree官網上可以看到開啟複選框需要json中有個checkArr值,為0是未選中,1是選中。 那麼我們新建一個MenuDto,來封裝一下我們需要的引數 ```java @Data public class MenuDto implements Serializable { private Integer id; private Integer parentId; private String checkArr = "0"; private String title; } ``` 在MenuDao中新增如下方法 ```java @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 listByRoleId(Integer roleId); ``` MenuServiceImpl中 ```java @Override public List buildMenuAllByRoleId(Integer roleId) { List listByRoleId = menuDao.listByRoleId(roleId); List permissionDtos = menuDao.buildAll(); List tree = TreeUtil.tree(listByRoleId, permissionDtos); return tree; } ``` 這裡我寫了一個TreeUtil工具類 ```java public class TreeUtil { //todo 判斷list是否為空 /** * * @param listByRoleId 通過角色id查詢的menuid * @param menuDtos 返回的menutree * @return */ public static List tree(List listByRoleId, List menuDtos ){ List collect = listByRoleId.stream().map(MenuDto::getId).collect(Collectors.toList()); List 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。我這個方法可能會有點繞,如果有小夥伴有更好的方法,歡迎留言告訴我。 然後這個頁面的有需要注意的部分,就是再刪除角色時,要先查詢是否已經有使用者是這個角色了,如果有就不能刪除 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714173203110.PNG#pic_center) ## 三、使用者介面 ![](https://img-blog.csdnimg.cn/20200714173232834.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714173303664.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) 這裡無非也就是一些增刪改查,要寫的完善點的話也就是新增使用者時手機號是否能相同等等。我這裡新增使用者時,會給他一個預設的密碼123456 ```java @PostMapping @ResponseBody @ApiOperation(value = "新增使用者") public Result 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的加密,但是這種密碼僅僅是加密了,相對而言會安全一些,但是如果兩個使用者的密碼是一樣的那麼他們加密後的密碼也是一樣的。那麼這其實也有辦法解決,就是給密碼加鹽,加鹽就是給密碼再加一個值,這樣即使不同使用者的相同的密碼在加密後也會不同。[詳細解釋](https://www.jianshu.com/p/f1954e6142ee)。之後會基於SpringSecurity的BCryptPasswordEncoder()方法進行加密,此方法自帶鹽。 那麼這個部分的程式碼就完成了,下一章正式進入SpringSecurity部分。 如果有同學不想寫前面部分,可以直接在[gitee](https://gitee.com/witmy/my-springsecurity-plus/tags)和[github](https://github.com/witmy/my-springsecurity-plus/tags)中下載v1.03的tag,裡面是到本篇文章結束的所有程式碼。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200714172338439.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hZRENT,size_16,color_FFFFFF,t_70#pic_center) ***注意:*** **裡面的是sql沒有更新,需要重新在倉庫中下載**。