系列目录
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完整代码
Title
那么首先我们要给的是table的数据,因为考虑到有一个模糊查询返回的数据格式是一样,所以可以合在一起写。
MenuDao新建方法
/**
*
* @param queryName 查询的表题
* @param queryType 查询类型
* @return
*/
List getFuzzyMenu(String queryName,Integer queryType);
因为之前在yml中已经配置了mapper.xml的路径是在classpath:/mybatis-mappers/下,所以在resources目录下新建mybatis-mappers文件夹,在其中新建MenuMapper.xml文件。
如果大家不想写一些简单的sql语句,推荐大家使用MybatisPlus或者JPA。MybatisPlus可能还要写一些多表的sql语句,JPA几乎见不到SQL。
这里再给大家安利一款idea的插件Free Mybatis plugin,它的作用就是可以快速通过xml找到mapper,或者mapper找到xml。效果如下图
点击箭头就能快速定位到相应方法,非常好用。
然后就是service,impl,controller
/**
* @author codermy
* @createTime 2020/7/10
*/
public interface MenuService {
List getMenuAll(String queryName,Integer queryType);
}
@Service//别忘了注解
public class MenuServiceImpl implements MenuService {
@Autowired
private MenuDao menuDao;
@Override
public List 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 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
MyMenu getMenuById(Integer id)
Result updateMenu(MyMenu menu);
Result 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 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 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
Title
menu-edit
Title
重启项目,访问一下
这里的修改是通过model传来的数据,.通过getMenuById方法返回数据存入model,通过Thymeleaf模板引擎放入指定位置。这里批量删除的功能尚未实现,有兴趣的同学可以自己实现。
这样我们这个页面基本就完成了,接下来的页面基本都是一个套路。我就不贴全部的代码了,挑其中部分来说说,全部的代码可以在gitee和github中获取,我已经按照每篇文章的进度添加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 listByRoleId(Integer roleId);
MenuServiceImpl中
@Override
public List buildMenuAllByRoleId(Integer roleId) {
List listByRoleId = menuDao.listByRoleId(roleId);
List permissionDtos = menuDao.buildAll();
List tree = TreeUtil.tree(listByRoleId, permissionDtos);
return tree;
}
这里我写了一个TreeUtil工具类
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。我这个方法可能会有点绕,如果有小伙伴有更好的方法,欢迎留言告诉我。
然后这个页面的有需要注意的部分,就是再删除角色时,要先查询是否已经有用户是这个角色了,如果有就不能删除
三、用户界面
这里无非也就是一些增删改查,要写的完善点的话也就是新增用户时手机号是否能相同等等。我这里新增用户时,会给他一个默认的密码123456
@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的加密,但是这种密码仅仅是加密了,相对而言会安全一些,但是如果两个用户的密码是一样的那么他们加密后的密码也是一样的。那么这其实也有办法解决,就是给密码加盐,加盐就是给密码再加一个值,这样即使不同用户的相同的密码在加密后也会不同。详细解释。之后会基于SpringSecurity的BCryptPasswordEncoder()方法进行加密,此方法自带盐。
那么这个部分的代码就完成了,下一章正式进入SpringSecurity部分。
如果有同学不想写前面部分,可以直接在gitee和github中下载v1.03的tag,里面是到本篇文章结束的所有代码。
注意: 里面的是sql没有更新,需要重新在仓库中下载。