【爆肝推荐】手摸手带你做后台管理项目(第五章)动态目录和knife4j整合

142 阅读5分钟

前情提要

上一篇弄好了菜单和角色, 剩余用户和角色关联还没做, 动态菜单还没做, html按钮控制也没做, 这篇都弄完, 然后knife4j也整合进去, 上一篇太长了, 所以都留在这一篇完成了

直接进入主题,开整

SysUserEntity增加非表字段roles

 	//多个角色id,非表字段
    @TableField(exist = false)
    private List<Integer> roles;

改造sysUserController

改动处加了注释,增删改查四个接口都动过

import com.macro.Vo.UserParam;
import com.macro.entity.UserEntity;
import com.macro.service.UserService;
import com.macro.utils.Result;
import com.macro.utils.shiro.ShiroUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("sysUser")
public class sysUserController {
    @Autowired
    private UserService userService;

    @GetMapping("list")
    public Result list(UserParam param){
        Result result = userService.findByParam(param);
        return result;
    }

    @PostMapping("add")
    public Result add(@RequestBody UserEntity user){
        //密码加密
        user.setPassword(ShiroUtils.getMdPassWord(user.getPassword()));
        //改动
        boolean save = userService.insert(user);
        if(save){
            return Result.success();
        }
        return Result.error("添加失败");
    }

    //只添加了@RequiresPermissions("sys:user:info")
    @RequiresPermissions("sys:user:info")
    @GetMapping("info/{id}")
    public Result info(@PathVariable("id") Integer id){
        //改动
        UserEntity userEntity = userService.findById(id);
        return Result.success(userEntity);
    }


    @PostMapping("update")
    public Result update(@RequestBody UserEntity user){
        user.setPassword(ShiroUtils.getMdPassWord(user.getPassword()));
        //改动
        boolean type = userService.updateData(user);
        return type ? Result.success() : Result.error("更新失败");
    }

    @PostMapping("del")
    public Result del(@RequestBody String[] ids){
        if(ids.length > 0){
            //改动
            boolean type = userService.delIds(ids);
            return type ? Result.success() : Result.error("删除失败");
        }
        return  Result.success();
    }


    @GetMapping("setting")
    public Result setting(){
        //直接通过ShiroUtils.getUserId()获取用户id
        UserEntity userEntity = userService.getById(ShiroUtils.getUserId());
        return Result.success(userEntity);
    }
}

UserService

import com.baomidou.mybatisplus.extension.service.IService;
import com.macro.Vo.UserParam;
import com.macro.entity.UserEntity;
import com.macro.utils.Result;

public interface UserService extends IService<UserEntity> {
    UserEntity findByUserName(String username);

    Result findByParam(UserParam param);

    boolean insert(UserEntity user);

    UserEntity findById(Integer id);

    boolean updateData(UserEntity user);

    boolean delIds(String[] ids);
}

UserServiceImpl

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.macro.Vo.UserParam;
import com.macro.dao.UserDao;
import com.macro.entity.SysUserRoleEntity;
import com.macro.entity.UserEntity;
import com.macro.service.SysUserRoleService;
import com.macro.service.UserService;
import com.macro.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl extends ServiceImpl<UserDao, UserEntity> implements UserService {

    @Autowired
    private UserDao userDao;
    @Autowired
    private SysUserRoleService userRoleService;

    @Override
    public UserEntity findByUserName(String username) {
        QueryWrapper<UserEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("username",username);
        return userDao.selectOne(wrapper);
    }

    //新增
    @Override
    public Result findByParam(UserParam param) {
        PageHelper.startPage(param.getPage(), param.getLimit());
        List<UserEntity> list = userDao.findByParam(param);
        PageInfo<UserEntity> pageInfo = new PageInfo<>(list);
        return Result.success(0,pageInfo.getTotal(),list);
    }

    //新增
    @Override
    public boolean insert(UserEntity user) {
        int insert = userDao.insert(user);
        if(insert > 0){
            List<Integer> roles = user.getRoles();
            if(roles != null && roles.size() > 0){
                for (int i = 0; i < roles.size(); i++) {
                    SysUserRoleEntity entity  = new SysUserRoleEntity();
                    entity.setRoleId(roles.get(i));
                    entity.setUserId(user.getId());
                    userRoleService.save(entity);
                }
            }
        }
        return insert>0?true:false;
    }
    //新增
    @Override
    public UserEntity findById(Integer id) {
        UserEntity user = userDao.selectById(id);
        if(user!= null){
            List<SysUserRoleEntity> roleList = userRoleService.list(new QueryWrapper<SysUserRoleEntity>().eq("user_id",id));
            user.setRoles(roleList.stream().map(SysUserRoleEntity::getRoleId).collect(Collectors.toList()));
        }
        return user;
    }
    //新增
    @Override
    public boolean updateData(UserEntity user) {
        List<Integer> roles = user.getRoles();
        List<SysUserRoleEntity> roleList = userRoleService.list(new QueryWrapper<SysUserRoleEntity>().eq("user_id",user.getId()));
        List<Integer> list = roleList.stream().map(SysUserRoleEntity::getRoleId).collect(Collectors.toList());
        //新的id如果没在旧的中查询出来,则是新增的
        for (int i = 0; i < roles.size(); i++) {
            if(!list.contains(roles.get(i))){
                SysUserRoleEntity entity = new SysUserRoleEntity();
                entity.setUserId(user.getId());
                entity.setRoleId(roles.get(i));
                userRoleService.save(entity);
            }
        }
        //旧的没有在新的里面查出来,则是要删除的
        for (int i = 0; i < roleList.size(); i++) {
            if(!roles.contains(roleList.get(i).getRoleId())){
                userRoleService.removeById(roleList.get(i).getId());
            }
        }
        int i = userDao.updateById(user);
        return i >0?true:false;
    }
    //新增
    @Override
    public boolean delIds(String[] ids) {
        for (int i = 0; i < ids.length; i++) {
            List<SysUserRoleEntity> list = userRoleService.list(new QueryWrapper<SysUserRoleEntity>().eq("user_id", ids[i]));
            List<Integer> userRoleIds = list.stream().map(SysUserRoleEntity::getId).collect(Collectors.toList());
            userRoleService.removeByIds(userRoleIds);
            userDao.deleteById(ids[i]);
        }
        return true;
    }
}

sysUser.html改动

性别下方增加角色多选下拉框

<div class="layui-form-item">
	<label class="layui-form-label">角色 </label>
		<div class="layui-input-inline">
			<el-select  v-model="user.roles" multiple   placeholder="可多选">
				<el-option v-for="item in roleList" 
				:value="item.id" :key="item.id" :label="item.roleName"></el-option>
			</el-select>
		</div>
</div>

在这里插入图片描述

sysUser.js

data中增加roleList :[] mounted中调用 this.initRoles();

 //新增初始化请求
        initRoles(){
            axios({
                url:"role/roleList",
                method: "get"
            }).then(res =>{
                if(res.data.code == 200){
                    vm.roleList = res.data.data;
                }
            });
        },

在这里插入图片描述

SysRoleController 新增roleList

    //初始化获取所有角色
    @GetMapping("roleList")
    public Result roleList(){
        List<SysRoleEntity> list = roleService.list();
        return Result.success(list);
    }

运行之后是没什么问题的 在这里插入图片描述

动态目录

删除掉index.html中的这一块,并且给ul增加 id="menu", 目录和菜单都在js中完成在这里插入图片描述 js部分增加menuList,并将menuList方法在mounted中调用 menuList中有两个for 第一层的for为目录,第二层为菜单,菜单可以点击,所以增加jump方法,像上面那种图设置跳转地址会跳出iframe所以自定义了一个jump方法,也算一个坑,最下方加入了一个layui的重新渲染 在这里插入图片描述

menuList代码

menuList() {
                //手动赋值,vue与layui不兼容
                axios({
                    url: "menu/user/list",
                    methods: "get"
                }).then(res => {
                    let list = res.data.data;
                    if (list != null && list.length > 0) {
                        let li = "";
                        for (let i = 0; i < list.length; i++) {
                            let menu = "<li class='layui-nav-item' style='background-color: #001529 !important;'>" +
                                "<a href='javascript:;' >  <i class='layui-icon "+list[i].icon+"'> "+list[i].name+"</i></a><dl class='layui-nav-child'>";
                            if(list[i].childList != null && list[i].childList.length){
                                for (let j = 0; j <list[i].childList.length; j++) {
                                    menu = menu + "<dd><a onclick=jump('"+list[i].childList[j].path + "','"+list[i].childList[j].name+"')"+" >"+
                                        "<i class='layui-icon "+list[i].childList[j].icon+"'> "+list[i].childList[j].name+"</i> </a></dd>"
                                }
                            }
                            menu = menu + "</dl></li>";
                            li = li + menu;
                        }
                        $("#menu").html(li);
                    }
                    //重新渲染
                    layui.use('element', function() {
                        var element = layui.element;
                        element.init();
                    });
                });
            },

jump

//页面跳转,重新修改跳转方式
    function jump(href,name){
        vm.val =name;
        vm.main = href;
    }

SysMenuController

编写menu/user/list接口


    //目录菜单
    @GetMapping("user/list")
    public Result userList(){
        List<SysMenuEntity> list = menuService.findListByUserId(ShiroUtils.getUserId());
        return Result.success(list);
    }

SysMenuService

List<SysMenuEntity> findListByUserId(Integer userId);

SysMenuServiceImpl

 @Override
    public List<SysMenuEntity> findListByUserId(Integer userId) {
        List<SysMenuEntity> menuList = this.findByUserId(userId);
        List<SysMenuEntity> hierarchyList = getHierarchyList(menuList);
        return hierarchyList;
    }

修改一下sys_menupath数据和icon数据 在这里插入图片描述 运行效果,修改了一下角色关联的目录,也是ok的 在这里插入图片描述 在这里插入图片描述

整体结束

看似结束了,基本算是做完了,还有一个按钮控制没做,这块推翻了我很多想法,比如yml配置中设置了非强制的跳转, 但是也造成了一个坑,也就是htmlshiro控制按钮,如果不从controller跳转过去,则无法生效,因为引入的一个jar包名称就叫做thymeleaf-extras-shiro,所以yml那个配置得注释掉,还得设置各种跳转的代码 在这里插入图片描述

IndexController改为PathController

PathController全部代码


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PathController {

    @GetMapping(value = {"index","","/"})
    public String index(){
        return "index";
    }

    @GetMapping(value = {"user"})
    public String user(){
        return "sysUser";
    }

    @GetMapping(value = {"info"})
    public String info(){
        return "info";
    }

    @GetMapping(value = {"main"})
    public String main(){
        return "main";
    }

    @GetMapping(value = {"role"})
    public String role(){
        return "sysRole";
    }

    @GetMapping(value = {"menu"})
    public String menu(){
        return "sysMenu";
    }

}

数据库sys_menu 修改path 在这里插入图片描述

index.html

main直接对应controllermain接口 在这里插入图片描述 运行看一下效果,没什么问题 在这里插入图片描述

thymeleaf整合shiro标签

pom.xml增加依赖

		<!-- properties下的 -->
		<thymeleaf-shiro>2.0.0</thymeleaf-shiro>
		<!-- thymeleaf整合shiro标签 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>${thymeleaf-shiro}</version>
        </dependency>

ShiroConfig中增加shiroDialect

加入这个方法便能在html中使用shiro的按钮控制了,当前角色控制也是可以,只是使用的东西不一样

/**
     * 添加ShiroDialect 为了在thymeleaf里使用shiro的标签的bean
     * @return
     */
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

sysUser.html增删改查按钮添加控制

shiro:hasPermission="sys:user:save"控制权限的(Permission) 如果是Role就是角色了,别写错了

 <button shiro:hasPermission="sys:user:save"  type="button" class="layui-btn"  @click="add">新增</button>
                <button shiro:hasPermission="sys:user:update" type="button" class="layui-btn layui-btn-normal" @click="update">修改</button>
                <button shiro:hasPermission="sys:user:del" type="button" class="layui-btn layui-btn-danger" @click="del">删除</button>

在这里插入图片描述

LoginController修改退出

退出之后清除掉登陆人的一些缓存信息,进入时重新加载数据,保存缓存的一致

  @GetMapping("logout")
    public String logout(){
         //清理缓存,并且跳转到登陆页面
        SecurityUtils.getSubject().logout();
        return "login";
    }

测试一下,修改了角色权限,需要重新登陆才会生效,全部完成了 在这里插入图片描述 在这里插入图片描述

项目完结

项目基本结束了,权限校验只是拿出来部分做演示,没有全部去写,无论是controller注解还是html标签,都只是写了一部分,还有一些特别的点没做,比如事务,权限更新之后刷新redis内部的权限缓存,这两点都没去做,当然还有layui下次就直接用Elemenet UI不会用Layui 毕竟和vue用还是有好多不舒服的地方,比如还需要手动设置刷新,下拉控件redio使用vue无效、毕竟两个东西走的方向不太一样,所以下次直接使用element ui 或者iview ui 项目结束了,剩下就是整合knife4j生成好看的接口文档了,还有上传linux直接线上访问

整合knife4j

pim.xml添加knife4j的jar

<!-- properties -->
<knife4j-version>2.0.2</knife4j-version>
 <dependency>
   <groupId>com.github.xiaoymin</groupId>
   <artifactId>knife4j-spring-boot-starter</artifactId>
   <version>${knife4j-version}</version>
</dependency>

添加配置

config文件夹下添加Swagger2Config

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableKnife4j
public class Swagger2Config {

    @Bean(value = "indexApi")
    public Docket indexApi() {
        return  new Docket(DocumentationType.SWAGGER_2)
                .useDefaultResponseMessages(false)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.macro.controller"))
                .paths(PathSelectors.any())
                .build();

    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("后台项目管理")
                .description("无术同学")
                .version("1.0")
                .build();
    }
}

原本想在getShiroFilterFactoryBean中放行swagger直接在外部访问的,想想算了,不安全

sysUserController修改

添加如下几个注解 在这里插入图片描述

修改UserParam

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

//继承PageEntity
@Data
//swagger的注解
@ApiModel(value = "用户参数")
public class UserParam extends PageEntity {
    //昵称,添加这个注解能在文档上看到参数名称
    @ApiModelProperty(value = "昵称")
    private String niceName;
    //账号
    @ApiModelProperty(value = "账号")
    private String username;
}

修改PageEntity

@Data
public class PageEntity {
//  页数
@ApiModelProperty(value = "页数")
    private Integer page;
//每页数量
@ApiModelProperty(value = "每页数量")
    private Integer limit;
}

菜单中添加接口文档菜单 在这里插入图片描述 然后就能看到如下图片了,只改了一小部分,只能登陆之后访问,或者修改getShiroFilterFactoryBean放行doc.html等资源,能直接看到,登陆之后可以直接访问http://127.0.0.1:8082/doc.html#/home也可以看到! 在这里插入图片描述

源码

在公众号内发送后台即可获取源码数据库