基于Springboot+MybatisPlus+Layui实现小型教育网站

该项目使用Springboot、MybatisPlus和Layui构建了一个教育网站,实现了用户认证、授权、菜单动态显示、课程管理、Excel导入导出等功能。Shiro框架用于权限控制,UserRealm自定义认证和授权,配置了拦截和过滤规则。同时,使用MybatisPlus进行数据操作,包括增删改查和分页。此外,还实现了Excel的导入导出,允许用户通过拖拽上传Excel文件批量导入课程数据。
摘要由CSDN通过智能技术生成

基于Springboot+MybatisPlus+Layui实现小型教育网站

项目简介:

该项目采用Springboot框架实现疫情下学生和教师通过一个教育平台来进行教学与交流,该项目主要涵盖的技术点是使用Echarts平台上开源图标进行数据的可视化图表呈现,网站UI整体采用Layui开源网页代码,数据库采用MySQL(后期可能会引入Redis缓存),MybatisPlus实现数据的增删改查,集成Shiro实现登陆功能,以及不同用户之间菜单栏的动态显示,用户主要分为:超级管理员,管理员,老师和学生,并实现权限的动态设置,由Maven进行依赖管理

开发环境:

**Java:**JDK1.8

**开发工具:**IDEA(2022.1.3),Maven,Sequel Pro(连接数据库)

**前端框架:**Layui,Echart图表

**后端框架:**Springboot,MybatisPlus,Shiro

**数据库:**MySQL 5.7

一、集成Shiro完成登陆和资源控制

1.1 Shiro简介

shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。相对于传统的session后台查找模式吗,Shiro是一个轻量框架,能够减轻服务端的压力,并且能够轻松完成web应用的权限管理,Shiro主要由三部分组成:

Subject**:**主体,应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject , 代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager**:**安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm**:**域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

1.2 引入pom依赖
<!--shiro-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.2</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.2</version>
</dependency>
<!--shiro和thymeleaf集成的扩展依赖,为了能在页面上使用xsln:shiro的标签-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
1.3 编写UserRealm

在主程序中创建名为realm的文件来写Shiro对认证和配置的方法

public class UserRealm extends AuthorizingRealm {

    @Autowired
    @Lazy
    private UserService userService;


    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }


    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.查询数据库
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",token.getPrincipal().toString());
        User user = userService.getOne(queryWrapper);
        if (null != user){
            //盐 时用户uuid生成的
            //ByteSource salt = ByteSource.Util.bytes(user.getSalt());
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
            return info;
        }
        return null;
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        return null;
    }


}
1.4 ShiroConfig

在主程序下的Config文件包下去配置Shiro的基本配置(拦截和过滤)

package com.example.springboot2022nocv.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.example.springboot2022nocv.realm.UserRealm;
import lombok.Data;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(value = { SecurityManager.class })
@ConfigurationProperties(prefix = "shiro")
@Data
public class ShiroConfig {

    private static final String SHIRO_DIALECT = "shiroDialect";
    private static final String SHIRO_FILTER = "shiroFilter";
    // 加密方式
    //private String hashAlgorithmName = "md5";
    // 散列次数
    //private int hashIterations = 2;
    // 默认的登陆页面
    private String loginUrl = "/china.html";

    private String[] anonUrls; // 放行的路径
    private String logOutUrl; // 登出的地址
    private String[] authcUlrs; // 拦截的路径

    /**
     * 声明凭证匹配器
     */
    /*@Bean("credentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
        credentialsMatcher.setHashIterations(hashIterations);
        return credentialsMatcher;
    }*/

    /**
     * 声明userRealm
     */
    @Bean("userRealm")
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        return userRealm;
    }

    /**
     * 配置SecurityManager
     */
    @Bean("securityManager")
    public SecurityManager securityManager(UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 注入userRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 配置shiro的过滤器
     */
    @Bean(SHIRO_FILTER)
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        factoryBean.setSecurityManager(securityManager);
        // 设置未登陆的时要跳转的页面
        factoryBean.setLoginUrl(loginUrl);
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        // 设置放行的路径
        if (anonUrls != null && anonUrls.length > 0) {
            for (String anon : anonUrls) {
                filterChainDefinitionMap.put(anon, "anon");
            }
        }
        // 设置登出的路径
        if (null != logOutUrl) {
            filterChainDefinitionMap.put(logOutUrl, "logout");
        }
        // 设置拦截的路径
        if (authcUlrs != null && authcUlrs.length > 0) {
            for (String authc : authcUlrs) {
                filterChainDefinitionMap.put(authc, "authc");
            }
        }
        Map<String, Filter> filters=new HashMap<>();
        factoryBean.setFilters(filters);
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return factoryBean;
    }

    /**
     * 注册shiro的委托过滤器,相当于之前在web.xml里面配置的
     * @return
     */
    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
        FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName(SHIRO_FILTER);
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }

    /* 加入注解的使用,不加入这个注解不生效--开始 */
    /**
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    /* 加入注解的使用,不加入这个注解不生效--结束 */

    /**
     * 这里是为了能在html页面引用shiro标签,上面两个函数必须添加,不然会报错
     *
     * @return
     */
    @Bean(name = SHIRO_DIALECT)
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}
1.5 static下的静态index.html跳转页面

写在static文件夹下的index.html文件主要用作页面跳转

<!--用来跳转-->
<script type="text/javascript">
    window.location.href="/toLogin";
</script>
1.6 编写yml配置过滤路径

在application.yml文件中去编写程序的拦截和过滤文件,即决定了我们必须先登陆认证,才能获得进入主程序的进一步权限访问

一言以概之,Shiro框架就是: 认证 => 授权

# shiro配置路径
shiro:
  anon-urls:
    - /toLogin*
    - /login.html*
    - /login/login
    - /login/getCode
    - /css/**
    - /echarts/**
    - /images/**
    - /layui/**
    - /layui_ext/**
    - /js/**
    - /sass/**
    - /fonts/**
    - /dummy/**
    - /video/**
  login-url: /index.html
  login-out-url: /login/logout*
  authc-ulrs:
    - /**
1.7 验证码逻辑

引入hutool工具类进行生成验证码,验证验证码的工作

<!--hutool-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.6.8</version>
</dependency>
@RequestMapping("/getCode")
public void getCode(HttpServletResponse response, HttpSession session) throws IOException {
        //HuTool定义图形验证码的长和宽,验证码的位数,干扰线的条数
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(116, 36,4,10);
        session.setAttribute("code",lineCaptcha.getCode());
        try {
            ServletOutputStream outputStream = response.getOutputStream();
            lineCaptcha.write(outputStream);
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

二、以课程为例实现后台基本的增删改查

2.1引入相关依赖

在本次项目中使用Springboot+MyBatisPlus去实现一个后台的业务逻辑,在创建项目之前只选择了四个依赖: Sping Web, Lombok, Thymeleaf, JDBC API

然后在项目中去添加MybatisPlus和Springboot所需要的依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.1.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
2.2 连接数据库

在MySQL数据库中先创建一个名为edu的数据库,再在其中创建一个名为course的数据表,配置如下

截屏2022-12-18 13.05.25

截屏2022-12-18 13.04.28

变量名变量类型变量说明
idINT主键
course_nameVARCHAR课程名称
teacherVARCHAR任课老师
student_numINT上课人数
course_numINT总课时数
start_timeDATETIME开始时间
end_timeDATETIME结束时间
contentVARCHAR课程概况

数据库创建完然后在我们的项目中进行数据库的连接,在application.yml进行数据库配置

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/edu?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver
2.3 创建数据库实体(Entity)对象

在entity包中用于存放我们的实体类,与数据库中的属性值基本保持一致。

@Data
@TableName("course")
public class Course {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String courseName;
    private String teacher;
    private Integer studentNum;
    private Integer courseNum;
    private Date startTime;
    private Date endTime;
    private String content;
}

@TableName的注解用来链接数据表

我们在MySQL中一般采用下划线命名,而在Java中我们必须严格遵循驼峰命名法,在MySQL中是不区分大小写的,这样才能完成数据库数据的导入

2.4 创建Mapper对象

在mapper包(或者dao包)中, 对数据库进行数据持久化操作,他的方法语句是直接针对数据库操作的,可以使用**@Select、@Delet、@Insert**等注解对数据库直接操作

ublic interface CourseMapper extends BaseMapper<Course> {
}
2.5 创建Service包

service包下的类是针对 controller层的 controller,也就是针对我们使用者。service的 impl 是把mapper和service进行整合的文件

先创建接口(Interface)

public interface CourseService extends IService<Course> {
}

再创建应用类(Impl)

@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {
}
2.6 分页配置MyBatisPlusConfig

与Shiro框架一样,我们在前台进行分页展示的时候,先进行MyBatisPlusConfig的配置

@Configuration
@ConditionalOnClass(value = {PaginationInterceptor.class})
public class MybatisPlusConfig {
    @Bea@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {
}
n
    public PaginationInterceptor paginationInterceptor(){
        return  new PaginationInterceptor();
    }
}
2.7 Layui返回的数据格式 DataView

在工程中再去创建名为vo的包,用作前台文件展示,会直接返回给前台

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataView {
    private Integer code = 0;
    private String msg = "";
    private Long count = 0L;
    private Object data;

    public DataView(Long count,Object data){
        this.count = count;
        this.data = data;
    }

    public DataView(Object data){
        this.data = data;
    }
}

主要存放返回错误码,返回提示信息和分页信息

2.8 编写Controller文件

本次项目中没有使用RestFul接口风格,所以采用最原始的@RequestMapping和@ResponseBody来实现最基础的功能

1.展示数据(带模糊查询)
//注入
@Autowired
private CourseService courseService;

//带有模糊查询的内容展示
@RequestMapping("/listDataByPage")
@ResponseBody
public DataView listDataByPage(CourseVo courseVo) {
    //1.创建分页对象 限制每页展示内容数量
    IPage<Course> page = new Page<>(courseVo.getPage(), courseVo.getLimit());
    //2.创建模糊查询条件
    QueryWrapper<Course> queryWrapper = new QueryWrapper<>();
    queryWrapper.like(StringUtils.isNotBlank(courseVo.getCourseName()), "courseName", courseVo.getCourseName());
    courseService.page(page, queryWrapper);
    return new DataView(page.getTotal(), page.getRecords());
}
2.删除数据
//删除课程
@RequestMapping("/deleteById")
@ResponseBody
public DataView deleteById(Integer id) {
    boolean save = courseService.removeById(id);
    DataView dataView = new DataView();
    if (save) {
        dataView.setCode(200);
        dataView.setMsg("删除成功!");
        return dataView;
    }
    dataView.setCode(100);
    dataView.setMsg("删除失败!");
    return dataView;
}
3.增加或删除数据
//新增或修改id
@RequestMapping("/addOrUpdateCourse")
@ResponseBody
public DataView addOrUpdateCourse(Course course) {
    boolean save = courseService.saveOrUpdate(course);
    DataView dataView = new DataView();
    if (save) {
        dataView.setCode(200);
        dataView.setMsg("新增课程数据成功!");
        return dataView;
    }
    dataView.setCode(100);
    dataView.setMsg("新增课程数据失败!");
    return dataView;
}

三、实现拖拽Excel导入课程信息

3.1 引入pom依赖
<!--引入poi-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.0.0</version>
</dependency>
3.2 引入layui的拖拽上传Excel组件

基本思路

  • Excel的拖拽或者点击上传
  • 1.前台页面发送一个请求,上传文件MutilpartFile HTTP
  • 2.Controller,上传文件MutilpartFile 参数
  • 3.POI 解析文件,里面的数据一行一行全部解析出来
  • 4.每一条数据插入数据库
  • 5.mybatiplus 批量saveBatch(list)

前端代码:

<div class="layui-upload-drag" id="test10">
    <i class="layui-icon"></i>
    <p>点击上传,或将文件拖拽到此处</p>
    <div class="layui-hide" id="uploadDemoView">
        <hr>
        <img src="" alt="上传成功后渲染" style="max-width: 196px">
    </div>
</div>
//js 代码
layui.use(['upload','jquery'],function(){
        var layer = layui.layer //弹层
            ,$ = layui.jquery
            ,upload = layui.upload
            
//拖拽上传
        upload.render({
            elem: '#test10'
            ,url: '/excelImport'
            ,accept: 'file' //普通文件
            ,done: function(res){
                layer.msg('上传成功');
                console.log(res);
            }
        });
3.3 编写Controller
//Excel上传文件
@RequestMapping("/excelImportCourse")
@ResponseBody
public DataView excelImportCourse(@RequestParam("file") MultipartFile file) throws Exception {
    DataView dataView = new DataView();
    //1.判断文件是否为空
    if (file.isEmpty()) {
        dataView.setMsg("文件为空,不能上传!");
    }

    //2.POI 获取Excel数据
    XSSFWorkbook wb = new XSSFWorkbook(file.getInputStream());
    XSSFSheet sheet = wb.getSheetAt(0);

    //3.定义程序集合来接收文件内容
    List<Course> list = new ArrayList<>();
    XSSFRow row = null;

    //4.接收数据 装入集合中
    for (int i = 0; i < sheet.getPhysicalNumberOfRows(); i++) {
        Course course = new Course();
        row = sheet.getRow(i);
        course.setCourseName(row.getCell(0).getStringCellValue());
        course.setTeacher(row.getCell(1).getStringCellValue());
        course.setStudentNum(Integer.valueOf(row.getCell(2).getStringCellValue()));
        course.setCourseNum(Integer.valueOf(row.getCell(3).getStringCellValue()));
        course.setStartTime(new SimpleDateFormat("yyyy-MM-dd").parse(row.getCell(4).getStringCellValue()));
        course.setEndTime(new SimpleDateFormat("yyyy-MM-dd").parse(row.getCell(5).getStringCellValue()));
        course.setContent(row.getCell(6).getStringCellValue());

        list.add(course);
    }
    boolean save = courseService.saveBatch(list);
    if (save) {
        dataView.setCode(200);
        dataView.setMsg("数据表插入成功!");
        return dataView;
    }
    dataView.setCode(100);
    dataView.setMsg("数据表插入失败!");
    return dataView;
}
3.4 数据导出的Excel功能

基本思路

1.前端发送请求

2.后端查询数据库,封装数据Excel实体

3.返回数据建立输出,写出浏览器文件

/导出Excel@RequestMapping("/excelOutPortCourse")
@ResponseBody
public void excelOutPortChina(HttpServletResponse response) {
    //1. 查询数据库 [查询所有,符合条件的数据查询出来]
    List<Course> list = courseService.list();
    //2.建立Excel对象,封装数据
    response.setCharacterEncoding("UTF-8");
    //2.1创建Excel对象
    XSSFWorkbook wb = new XSSFWorkbook();
    //2.3创建sheet对象
    XSSFSheet sheet = wb.createSheet("中国疫情数据sheet1");
    //2.3创建表头
    XSSFRow xssfRow = sheet.createRow(0);
    xssfRow.createCell(0).setCellValue("课程名称");
    xssfRow.createCell(1).setCellValue("任课老师");
    xssfRow.createCell(2).setCellValue("学生人数");
    xssfRow.createCell(3).setCellValue("总课时数");
    xssfRow.createCell(4).setCellValue("开课时间");
    xssfRow.createCell(5).setCellValue("结课时间");
    xssfRow.createCell(6).setCellValue("课程概要");
    //3.遍历数据,封装Excel工作对象
    for(Course data: list) {
        XSSFRow dataRow = sheet.createRow(sheet.getLastRowNum() + 1);
        dataRow.createCell(0).setCellValue(data.getCourseName());
        dataRow.createCell(1).setCellValue(data.getTeacher());
        dataRow.createCell(2).setCellValue(data.getStudentNum());
        dataRow.createCell(3).setCellValue(data.getCourseNum());
        dataRow.createCell(4).setCellValue(data.getStartTime());
        dataRow.createCell(5).setCellValue(data.getEndTime());
        dataRow.createCell(6).setCellValue(data.getContent());
    }
    //4.建立输出流,输出浏览器文件
    OutputStream os = null;
    //4.1 设置Excel名字,数据类型编码

    try {
        response.setContentType("application/octet-stream;chartset=utf8");
        response.setHeader("Content-Disposition", "attachment;filename=" + new String("课程统计表".getBytes(), "iso-8859-1") + ".xls");
        //4.2 输出文件
        os = response.getOutputStream();
        wb.write(os);
        os.flush();
    } catch(Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (os != null)
                os.close();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

}

四、数据表的设计

4.1 数据库设计

1.menu菜单

idpidtypetitlepermissioniconhrefopenordernumavailable
10menu课程管理&#xe653;/course/toCourse011
110menu系统管理&#xe62a;0111
1211menu菜单管理&#xe60a;/menu/toMenu0121

2.role角色

idnameremark
1超级管理员拥有所有权限
2老师查看新增修改
3学生查看

3.role_menu 关联关系表

ridmid
11
12

4.user 用户表【老师,学生,管理员】

idusernamepasswordrole_idban_ji_idxue_yuan_idteacher_id
1admin1234561110

5.ban_ji 班级表

idnamexue_yuan_id
1软件工程1班1

6.xue_yuan学院表

idname
1计算机学院
4.2 Java实体的编写

五、修改index主题样式、菜单的增删改查

整个项目最后希望能够实现一个便捷化的交互方式,能够在前台就简单实现菜单的增加和删除,在菜单下增加子菜单,并且动态给予每个角色不同的菜单角色

5.1 引入pom依赖
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
5.2 菜单的插入

创建名为utils包,里面放入菜单修改的Tree配置

TreeNode

package com.example.demo.controller;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author:
 * @Date: 2019/11/22 15:25
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TreeNode {

    private Integer id;
    @JsonProperty("parentId") //返回的json的名称 parentId ,为了确定层级关系
    private Integer pid;
    private String title;
    private String icon;
    private String href;
    private Boolean spread;
    private List<TreeNode> children = new ArrayList<TreeNode>();

    /**
     * 0为不选中  1为选中
     */
    private String checkArr="0";

    /**
     * 首页左边导航菜单的构造器
     */
    public TreeNode(Integer id, Integer pid, String title, String icon, String href, Boolean spread) {
        this.id = id;
        this.pid = pid;
        this.title = title;
        this.icon = icon;
        this.href = href;
        this.spread = spread;
    }

    /**
     * 部门 dtree的构造器
     * @param id id
     * @param pid 父亲parentId
     * @param title 名称
     * @param spread 是否展开
     */
    public TreeNode(Integer id, Integer pid, String title, Boolean spread) {
        this.id = id;
        this.pid = pid;
        this.title = title;
        this.spread = spread;
    }

    /**
     * 给角色分配权限的构造器
     */
    public TreeNode(Integer id, Integer pid, String title, Boolean spread, String checkArr) {
        this.id = id;
        this.pid = pid;
        this.title = title;
        this.spread = spread;
        this.checkArr = checkArr;
    }
}

TreeBuilder

public class TreeNodeBuilder {
    public static List<TreeNode> build(List<TreeNode> treeNodes, Integer topPid) {
        List<TreeNode> nodes = new ArrayList<TreeNode>();
        for (TreeNode n1 : treeNodes) {
            if (n1.getPid()==topPid){
                nodes.add(n1);
            }
            for (TreeNode n2 : treeNodes) {
                if (n1.getId()==n2.getPid()){
                    n1.getChildren().add(n2);
                }
            }
        }
        return nodes;
    }
}
5.3 菜单栏的增删改查
1.菜单栏的展示(加载,刷新,回显)
//加载所有菜单
@RequestMapping("loadAllMenu")
@ResponseBody
public DataView loadAllMenu(MenuVo menuVo) {
    IPage<Menu> page = new Page<>(menuVo.getPage(), menuVo.getLimit());
    QueryWrapper<Menu> queryWrapper= new QueryWrapper<>();
    queryWrapper.like(StringUtils.isNotBlank(menuVo.getTitle()), "title", menuVo.getTitle());
    queryWrapper.orderByAsc("ordernum");
    menuService.page(page);
    return new DataView(page.getTotal(), page.getRecords());
}

//加载下拉菜单数据 初始化下拉树
@RequestMapping("loadMenuManagerLeftTreeJson")
@ResponseBody
public DataView loadMenuManagerLeftTreeJson() {
    List<Menu> list = menuService.list();
    List<TreeNode> treeNodes = new ArrayList<>();
    for (Menu menu : list) {
        boolean open = menu.getOpen() == 1 ? true : false;
        treeNodes.add(new TreeNode(menu.getId(), menu.getPid(), menu.getTitle(), open));
    }
    return new DataView(treeNodes);
}


//赋值最大的排序码加一
//条件查询:倒叙排序,取一条数据  +1
@RequestMapping("/loadMenuMaxOrderNum")
@ResponseBody
public Map<String, Object> loadMenuMaxOrderNum() {
    Map<String, Object> map = new HashMap<>();
    QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
    queryWrapper.orderByDesc("ordernum");
    IPage<Menu> page = new Page<>(1, 1);
    List<Menu> list = menuService.page(page, queryWrapper).getRecords();
    map.put("value", list.get(0).getOrdernum() + 1);
    return map;
}
2.新增菜单
//新增菜单
    @RequestMapping("/addMenu")
    @ResponseBody
    public DataView addMenu(Menu menu) {
        DataView dataView = new DataView();
        menu.setType("menu");
        boolean save = menuService.save(menu);
        if (!save) {
            dataView.setCode(100);
            dataView.setMsg("数据插入失败!");
            return dataView;
        }
        dataView.setMsg("菜单插入成功!");
        dataView.setCode(200);
        return dataView;
    }
3.更新菜单
//更新菜单
@RequestMapping("/updateMenu")
@ResponseBody
public DataView updateMenu(Menu menu) {
    menuService.updateById(menu);
    DataView dataView = new DataView();
    dataView.setCode(200);
    dataView.setMsg("更新菜单成功!");
    return dataView;
}
4.删除菜单(判断 删除)

删除菜单分两种情况:1.有子类ID 不能删除

2.没有子类ID 直接删除

//判断是否有父级ID
//没有子类ID,可以删除 【】
@RequestMapping("/checkMenuHasChildrenNode")
@ResponseBody
public Map<String, Object> checkChildrenNode (Menu menu) {
    Map<String, Object> map = new HashMap<>();
    QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("pid", menu.getId());
    List<Menu> list = menuService.list(queryWrapper);
    if (list.size() > 0) {
        map.put("value", true);
    } else {
        map.put("value", false);
    }

    return map;
}

//删除
@RequestMapping("/deleteMenu")
@ResponseBody
public DataView deleteMenu(Menu menu) {
    menuService.removeById(menu.getId());
    DataView dataView = new DataView();
    dataView.setCode(200);
    dataView.setMsg("删除菜单成功!");
    return dataView;
}
5.4 修改index主菜单为动态查库

1.修改样式 引入 js css

2.配置yml放行js包

3.原项目修改index.html 为 china.html 删除 commonmenu.html 引入 静态资源包里面的 index.html

4.去掉其它页面 的 引入,添加

去除所有的

模块

5.修改 indexcontroller 的请求/路径,添加一个/self/index

6.修改数据库/self/index

7.编写Controller

/**
 * 加载最外层index菜单
 */
@RequestMapping("loadIndexLeftMenuJson")
@ResponseBody
public DataView loadIndexLeftMenuJson(Menu permissionVo){
    //查询所有菜单
    QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
    List<Menu> list = menuService.list();

    List<TreeNode> treeNodes = new ArrayList<>();
    for (Menu p : list) {
        Integer id =p.getId();
        Integer pid = p.getPid();
        String title = p.getTitle();
        String icon = p.getIcon();
        String href = p.getHref();
        Boolean spread = p.getOpen().equals(1)?true:false;
        treeNodes.add(new TreeNode(id,pid,title,icon,href,spread));
    }

    //构造层级关系
    List<TreeNode> list2 = TreeNodeBuilder.build(treeNodes,0);
    return new DataView(list2);

}

六、角色【管理员、学生、老师】CRUD,分配菜单权限

6.1 角色的增删改查【条件查询带有分页】

1.引入role的静态页面

页面进行菜单的增加

6.2 为角色分配菜单权限

1.分配权限 menu【菜单的操作资源】id

2.分配角色 role【用户 管理员 学生 教师】id

3.关联表role_menu:全都可以为空,不能有主键,都是外键属性

rid mid

1 1

1 2

select mid from role_menu where rid = ?

List 所具有的菜单栏权限

/**
 * 1.初始化下拉列表的权限
 */
@Autowired
private MenuService menuService;

@RequestMapping("/initPermissionByRoleId")
@ResponseBody
public DataView initPermissionByRoleId(Integer roleId){
    //查询所有菜单和权限
    QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
    List<Menu> allPermissions = menuService.list();
    //1.首先根据角色id查询出当前角色所拥有的所有菜单的ID和权限的ID
    List<Integer> currentRolePermissions = roleService.queryRolePermissionIdsByRid(roleId);
    //2.根据查询出来的菜单ID和权限ID,再查询出菜单的数据和权限的数据
    List<Menu> currentPermissions = null;

    //如果根据角色id查询出来了菜单ID或权限ID,就去查询
    if (currentRolePermissions.size()>0){
        queryWrapper.in("id",currentRolePermissions);
        currentPermissions = menuService.list(queryWrapper);
    }else {
        currentPermissions = new ArrayList<>();
    }
    //3.构造List<TreeNode>
    List<TreeNode> nodes = new ArrayList<>();
    for (Menu allPermission : allPermissions) {
        String checkArr = "0";
        for (Menu currentPermission : currentPermissions) {
            if (allPermission.getId().equals(currentPermission.getId())){
                checkArr = "1";
                break;
            }
        }
        Boolean spread = (allPermission.getOpen()==null||allPermission.getOpen()==1)?true:false;
        nodes.add(new TreeNode(allPermission.getId(),allPermission.getPid(),allPermission.getTitle(),spread,checkArr));
    }
    return new DataView(nodes);
}
@Select("select mid from role_menu where rid = #{roleId}")
List<Integer> queryRolePermissionIdsByRid(Integer roleId);
//分配菜单权限【角色与菜单之间的关系】

// 1.分配菜单栏之前删除所有的rid数据
@Delete("delete from role_menu where rid = #{rid}")
void deleteRoleByRid(Integer rid);

// 2.保存分配 角色 与 菜单 的关系
@Insert("insert into role_menu(rid,mid) values (#{rid},#{mid})")
void saveRoleMenu(Integer rid, Integer mid);
6.3 不同角色登陆看到不同的菜单栏
// 查询的所有菜单栏 按照条件查询【管理员,学生 老师【条件查询】】
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
List<Menu> list = null;

// 1.取出session中的用户ID
User user = (User) session.getAttribute("user");
Integer userId = user.getId();
// 2.根据用户ID查询角色ID
List<Integer> currentUserRoleIds = roleService.queryUserRoleById(userId);
// 3.去重
Set<Integer> mids = new HashSet<>();
for (Integer rid : currentUserRoleIds){
    // 3.1.根据角色ID查询菜单ID
    List<Integer> permissionIds = roleService.queryAllPermissionByRid(rid);
    // 3.2.菜单栏ID和角色ID去重
    mids.addAll(permissionIds);
}
// 4.根据角色ID查询菜单ID
if (mids.size()>0){
    queryWrapper.in("id",mids);
    list = menuService.list(queryWrapper);
}
  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
图书管理系统是一个常见的项目,下面是使用 SpringBoot + MyBatisPlus + Restful + Vue + Jquery + Axios 的图书管理系统的简单介绍。 1. 项目概述 该项目是一个图书管理系统,主要功能包括: - 图书的增删改查 - 图书分类的增删改查 - 图书借阅的管理 2. 技术栈 - 后端SpringBoot + MyBatisPlus + Restful - 前端:Vue + Jquery + Axios 3. 功能模块 - 登录模块:用户登录、退出登录 - 图书管理模块:图书查询、添加、修改、删除 - 图书分类模块:图书分类查询、添加、修改、删除 - 借阅管理模块:借阅记录查询、添加、修改、删除 4. 项目结构 - backend:后端代码 - src/main/java/com/example/demo:Java 代码 - src/main/resources:配置文件和静态资源 - frontend:前端代码 - src:Vue 代码 5. 实现步骤 - 使用 Spring Initializr 创建一个 SpringBoot 项目 - 引入 MyBatisPlus、Druid 数据库连接池、Lombok 等依赖 - 创建数据库表,使用 MyBatisPlus 自动生成实体类和 Mapper 接口 - 创建 Restful API,提供图书、图书分类、借阅记录的增删改查接口 - 使用 Vue、Jquery、Axios 等前端技术实现前端界面,调用后端提供的接口实现相应功能 6. 总结 该项目基于 SpringBoot + MyBatisPlus + Restful + Vue + Jquery + Axios 技术栈,实现了一个简单的图书管理系统。通过该项目,可以学习到如何使用 SpringBoot 进行开发,如何使用 MyBatisPlus 简化数据库操作,以及如何使用 Vue、Jquery、Axios 等前端技术实现前端界面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ice Programmer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值