SSM项目实战-云尚办公系统(上)

尚硅谷-云尚办公系统

核心技术

| 基础框架:SpringBoot
| 数据缓存:Redis |
| 数据库:MySQL |
| 权限控制:SpringSecurity (new) |
| 工作流引擎:Activiti (new) |
| 前端技术:vue-admin-template + Node.js + Npm + Vue + ElementUI + Axios |
| 微信公众号:公众号菜单 + 微信授权登录 + 消息推送 |

一、后端环境搭建

1、构建Maven聚合工程(按照下图描述过程进行搭建)

在这里插入图片描述
1、创建父工程
在这里插入图片描述
2、建立module
在这里插入图片描述
3、按照上述maven聚合工程以此创建,创建完毕如下图所示
在这里插入图片描述

最终服务器端架构模块

guigu-oa-parent:根目录,管理子模块:

​ common:公共类父模块

​ common-util:核心工具类

​ service-util:service模块工具类

​ spring-security:spring-security业务模块

​ model:实体类模块

​ service-oa:系统服务模块

2、配置项目依赖

按照下述,依次导入项目依赖
1、父工程的pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
<!--    1、指定springboot的启动-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.6.RELEASE</version>
    </parent>

    <groupId>com.atguigu</groupId>
    <artifactId>guigu-oa-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>common</module>
        <module>model</module>
        <module>service-oa</module>
    </modules>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--        2、指定特定的依赖版本-->
        <java.version>17</java.version>
        <mybatis-plus.version>3.4.1</mybatis-plus.version>
        <mysql.version>8.0.30</mysql.version>
        <knife4j.version>3.0.3</knife4j.version>
        <jwt.version>0.9.1</jwt.version>
        <fastjson.version>2.0.21</fastjson.version>
    </properties>

    <!--3、配置dependencyManagement锁定依赖的版本-->
    <dependencyManagement>
        <dependencies>
            <!--mybatis-plus 持久层-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!--mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!--knife4j-->
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-spring-boot-starter</artifactId>
                <version>${knife4j.version}</version>
            </dependency>
            <!--jjwt-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>
            <!--fastjson-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2、common-util模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common</artifactId>
        <groupId>com.atguigu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>common-util</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided </scope>
        </dependency>
        <!--用于创建和验证JSON Web Tokens (JWT),这是一种开放标准(RFC 7519),
        通常用于在客户端和服务端之间安全地传输信息。-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
        <!--它通过注解的形式帮助开发者消除大量getter/setter、构造函数、equals/hashCode等样板代码,使代码更简洁-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--Fastjson是阿里巴巴开源的一个高性能的JSON处理库,用于Java语言,
        可以实现Java对象和JSON之间的快速序列化和反序列化操作。在处理HTTP请求和响应时,经常用于JSON格式数据的转换。-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
    </dependencies>
</project>

Note:
依赖管理(Dependency Management): 子模块中如果需要使用父模块中声明的依赖,只需要声明依赖的groupId、artifactId和version(版本号可以省略,因为会自动继承父模块中设定的版本),而不需要详细指定版本号。
例如,父模块中已经声明了某个依赖的版本,在子模块中只需要声明使用这个依赖即可。
因此,上述公共父模块中已经声明了fastjson模块,在子模块common中声明一下即可。
3、service-util模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>common</artifactId>
        <groupId>com.atguigu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>service-util</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>common-util</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

</project>

4、model模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>guigu-oa-parent</artifactId>
        <groupId>com.atguigu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>model</artifactId>

    <dependencies>
        <!--lombok用来简化实体类-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <scope>provided </scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <scope>provided </scope>
        </dependency>
    </dependencies>

</project>

note:lombok可以简化实体类开发,手动安装Lombok插件。
lombok用来简化实体类:@Data注解可以替代实体类的get、set方法

在这里插入图片描述
5、service-oa模块

<dependencies>
        <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>service-util</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

3、将实体类创建在model中

在这里插入图片描述

二、Mybatis-Plus

1、入门

1、简介:MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
2、特点:
(1)无侵入:只做增强,不做改变
(2)强大的CRUD:内置通用的Mapper、Service,通过少量的配置即可实现单表大部分的CRUD操作
(3)支持Lambda形式调用(wrapper)
(4)支持主键自动生成
(5)内置代码生成器,通过逆向工程,逆向生成代码
3、依赖导入

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

2、配置文件

0、当前以“角色管理”为例讲解Mybatis-Plus的使用

(1)创捷数据库和角色表
(2)项目创建SpringBoot配置文件
(3)创建角色实体类
(4)创建mapper接口,继承mp封装结口
(5)创建springboot的启动类
配置MySql数据库的相关配置以及logs日志

1、创建数据库与角色表
CREATE DATABASE `guigu-oa` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `guigu-oa`;

CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色id',
  `role_name` varchar(20) NOT NULL DEFAULT '' COMMENT '角色名称',
  `role_code` varchar(20) DEFAULT NULL COMMENT '角色编码',
  `description` varchar(255) DEFAULT NULL COMMENT '描述',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='角色';

2、创建springboot配置文件,以及实体类

application.xml

spring:
  application:
    name: service-oa
  profiles:
    active: dev

application-dev.yaml

server:
  port: 8800
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource #数据库类型
    driver-class-name: com.mysql.cj.jdbc.Driver #驱动类名字
    url: jdbc:mysql://localhost:3306/guigu-oa?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8 #数据库地址
    username: root #用户名
    password: root #密码

实体类

@TableName:表名注解,标识实体类对应的表

@TableId:主键注解,type = IdType.AUTO(数据库 ID 自增)

@TableField:字段注解(非主键)

@TableLogic:逻辑删除

package com.atguigu.model.system;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.atguigu.model.base.BaseEntity;
import lombok.Data;

@Data
@TableName("sys_role")//表名注解
public class SysRole extends BaseEntity {
   
   private static final long serialVersionUID = 1L;

   //角色名称
   @TableField("role_name")//字段注解
   private String roleName;

   //角色编码
   @TableField("role_code")
   private String roleCode;

   //描述
   @TableField("description")
   private String description;

}
3、创建springboot启动类

在springboot启动类中添加@MapperScan注解,扫描mapper文件夹

package com.atguigu.auth;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * springboot启动类
 */
@SpringBootApplication//指示Spring Boot自动配置和组件扫描的起点,Spring Boot会从该类所在的包及其子包开始扫描Bean定义。
@MapperScan("com.atguigu.auth.mapper")
//专门用于MyBatis,告诉Spring Boot去扫描com.atguigu包及其子包下所有以".mapper"结尾的包,
//这些包中定义的Mapper接口将被自动注册为Spring Bean
public class ServiceAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceAuthApplication.class, args);
    }
}

4、创建mapper接口,继承mp(mybatis-plus)封装接口

BaseMapper里面封装好了常用的数据库CRUD语句

package com.atguigu.auth.mapper;

import com.atguigu.model.system.SysRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface SysRoleMapper extends BaseMapper<SysRole> {
}

package com.baomidou.mybatisplus.core.mapper;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;

public interface BaseMapper<T> extends Mapper<T> {
    int insert(T entity);

    int deleteById(Serializable id);

    int deleteByMap(@Param("cm") Map<String, Object> columnMap);

    int delete(@Param("ew") Wrapper<T> queryWrapper);

    int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);

    int updateById(@Param("et") T entity);

    int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);

    T selectById(Serializable id);

    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);

    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);

    T selectOne(@Param("ew") Wrapper<T> queryWrapper);

    Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);

    List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);

    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);

    List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);

    <E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);

    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
}
5、测试自带的查询语句
package com.atguigu.auth;

import com.atguigu.auth.mapper.SysRoleMapper;
import com.atguigu.model.system.SysRole;
import org.junit.jupiter.api.Test; //Test引入的依赖是这个
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class TestMpDemo1 {

    //注入,
    @Autowired
    private SysRoleMapper mapper;
//    此处mapper报错:因为SysRoleMapper是接口,不能够创建对象;但这个实现类的对象不是我们创建的,而是在实现的过程中动态的创建的
//    解决方案:(1)不管他,实现过程中会动态创建,此处报错不影响执行(2)在SysRoleMapper上面加注解@Repository即可

    /**
     * 查询所有记录
     */
    @Test
    public void getAll(){
        System.out.println("***");

        List<SysRole> list = mapper.selectList(null);
        System.out.println(list);

    }

}

Note:此处可能会报错

Caused by: java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required

原因如下:
springboot3版本与mybatis-plus的版本不匹配
改为下述版本
在这里插入图片描述
在这里插入图片描述
参考链接
注意:

IDEA在sysRoleMapper处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确的执行。

为了避免报错,可以在 mapper 层 的接口上添加 @Repository 或直接使用 @Resource 代替 @Autowired。

3、mybatis-plus中的CRUD操作

PS:
物理删除:真正删除表数据、
逻辑删除:实现删除操作,但是数据还存在,只是查不出来了;通过标志位实现逻辑删除效果,本质还是修改操作
is_deleted:0 没有删除,1 已经删除

在这里插入图片描述
在这里插入图片描述

package com.atguigu.auth;

import com.atguigu.auth.mapper.SysRoleMapper;
import com.atguigu.model.system.SysRole;
import org.junit.jupiter.api.Test; //Test引入的依赖是这个
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.List;

@SpringBootTest
public class TestMpDemo1 {

    //注入,
    @Autowired
    private SysRoleMapper mapper;
//    此处mapper报错:因为SysRoleMapper是接口,不能够创建对象;但这个实现类的对象不是我们创建的,而是在实现的过程中动态的创建的
//    解决方案:(1)不管他,实现过程中会动态创建,此处报错不影响执行(2)在SysRoleMapper上面加注解@Repository即可

    /**
     * 查询所有记录
     */
    @Test
    public void getAll(){
        System.out.println("***");

        List<SysRole> list = mapper.selectList(null);
        System.out.println(list);

    }

    /**
     * 添加操作
     */
    @Test
    public void add(){
        SysRole sysRole = new SysRole();
        sysRole.setRoleName("角色管理员");
        sysRole.setRoleCode("role");
        sysRole.setDescription("角色管理员");

        int rows = mapper.insert(sysRole);//返回的值rows表示的是添加的行数
        System.out.println(rows);

        System.out.println(sysRole);
    }
    /**
     * 修改操作
     * 1、根据id查询
     * 2、设置修改值
     * 3、调用方法实现最终修改
     */

    @Test
    public void update(){
        SysRole sysRole = mapper.selectById(9);
        sysRole.setRoleName("修改角色管理员");
        int rows = mapper.updateById(sysRole);
        System.out.println(rows);
    }
    /**
     * 删除操作
     * 1、id删除
     * 2、批量删除
     */

    @Test
    public void delete(){

        int rows = mapper.deleteById(9);//id删除
        int row2=mapper.deleteBatchIds(Arrays.asList(1,2));//批量删除,id=1,2

    }



}

1、Mybatis-Plus条件构造器

在这里插入图片描述
Wrapper : 条件构造抽象类,最顶端父类

AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件

​ QueryWrapper : Entity 对象封装操作类,不是用lambda语法

​ UpdateWrapper : Update 条件封装,用于Entity对象更新操作

AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。

​ LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper

​ LambdaUpdateWrapper : Lambda 更新封装Wrapper

@Test
    public void testQuery1(){
        //QueryWrapper
        QueryWrapper<SysRole> wrapper = new QueryWrapper<>();
        wrapper.eq("role_name","总经理");
        List<SysRole> list = mapper.selectList(wrapper);

        //LambdaQueryWrapper好处:不用与数据库中名称完全一致,会自动查找数据库对应的名称
        LambdaQueryWrapper<SysRole> wrapper1 = new LambdaQueryWrapper<>();
        wrapper1.eq(SysRole::getRoleName,"总经理");
        List<SysRole> list1 = mapper.selectList(wrapper1);


        System.out.println(list);
        System.out.println(list1);

    }
2、Mybatis-Plus封装service层

com.baomidou.mybatisplus.extension.service.IService这是Mybatis-Plus提供的默认Service接口。
在这里插入图片描述
在这里插入图片描述

package com.atguigu.auth;

import com.atguigu.auth.mapper.SysRoleMapper;
import com.atguigu.auth.service.SysRoleService;
import com.atguigu.model.system.SysRole;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Arrays;
import java.util.List;

@SpringBootTest
public class TestMpDemo2 {

    //注入,
    @Autowired
    private SysRoleService service;

    /**
     * 查询所有记录
     */
    @Test
    public void getAll(){
        List<SysRole> list = service.list();
        System.out.println(list);

    }



}

三、角色管理

1、测试controller层


@RestController
@RequestMapping("admin/system/sysRole")
public class SysRoleController {
    //http://localhost:8800/admin/system/sysRole/findAll
    //注入service
    @Autowired
    private SysRoleService sysRoleService;

    //查询所有角色
    @GetMapping("findAll")
    public List<SysRole> findAll(){
        List<SysRole> list = sysRoleService.list();
        return list;

    }

测试接口:http://localhost:8800/admin/system/sysRole/findAll

2、定义统一返回结果对象

项目中。我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一,使前端对数据的操作更一致、轻松
一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及返回的具体数据就可以。但是一般会包含状态码、返回消息、数据这几部分内容
例如,我们的系统要求返回的基本数据格式如下:
列表:

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "id": 2,
      "roleName": "系统管理员"
    }
  ],
  "ok": true
}

分页

{
  "code": 200,
  "message": "成功",
  "data": {
    "records": [
      {
        "id": 2,
        "roleName": "系统管理员"
      },
      {
        "id": 3,
        "name": "普通管理员"
      }
    ],
    "total": 10,
    "size": 3,
    "current": 1,
    "orders": [],
    "hitCount": false,
    "searchCount": true,
    "pages": 2
  },
  "ok": true
}

没有返回数据

{
  "code": 200,
  "message": "成功",
  "data": null,
  "ok": true
}

失败

{
  "code": 201,
  "message": "失败",
  "data": null,
  "ok": false
}
2.1、定义统一返回结果对象

操作模块:common-util
在这里插入图片描述

package com.atguigu.common.result;

import lombok.Getter;

@Getter
public enum ResultCodeEnum {
    SUCCESS(200,"成功"),
    FAIL(201, "失败");
//    SERVICE_ERROR(2012, "服务异常"),
//    DATA_ERROR(204, "数据异常"),
//
//    LOGIN_AUTH(208, "未登陆"),
//    PERMISSION(209, "没有权限")
//    ;

    private Integer code;
    private String message;

    private ResultCodeEnum(Integer code,String message){
        this.code=code;
        this.message=message;

    }
}

package com.atguigu.common.result;

import lombok.Data;

@Data
public class Result<T> {
    private Integer code;//状态码
    private String message;//返回信息
    private T data;//数据(因为数据是不同的类型,所以使用不确定的枚举类型T)

    //私有化
    private Result(){}

    //封装返回的数据
    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = new Result<>();
        //封装数据
        if(body!=null){
            result.setData(body);
        }
        //状态码
        result.setCode(resultCodeEnum.getCode());
        //返回信息
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }

    //成功
    public static<T> Result<T> ok(){
        return build(null,ResultCodeEnum.SUCCESS);
    }
    public static<T> Result<T> ok(T data){
        return build(data,ResultCodeEnum.SUCCESS);
    }

    //失败
    public static<T> Result<T> fail(){
        return build(null,ResultCodeEnum.FAIL);
    }
    public static<T> Result<T> fail(T data){
        return build(data,ResultCodeEnum.FAIL);
    }

    public Result<T> message(String msg){
        this.setMessage(msg);
        return this;
    }

    public Result<T> code(Integer code){
        this.setCode(code);
        return this;
    }


}

2.2、改造controller方法
package com.atguigu.auth.controller;


import com.atguigu.auth.service.SysRoleService;
import com.atguigu.common.result.Result;
import com.atguigu.model.system.SysRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("admin/system/sysRole")
public class SysRoleController {
    //http://localhost:8800/admin/system/sysRole/findAll
    //注入service
    @Autowired
    private SysRoleService sysRoleService;

//    //查询所有角色
//    @GetMapping("findAll")
//    public List<SysRole> findAll(){
//        List<SysRole> list = sysRoleService.list();
//        return list;
//
//    }

    //查询所有角色
    //统一返回数据结果
    @GetMapping("findAll")
    public Result findAll(){
        List<SysRole> list = sysRoleService.list();
        return Result.ok(list);

    }

}

3、knife4j

文档地址:https://doc.xiaominfo.com/
knife4j是为Java MVC框架集成Swagger生成APi文档的增强解决方案

3.1、Swagger介绍

前后端分离开发模式中,api文档是最好的沟通方式
Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务
1、及时性:接口变更后,能够及时准确的通知相关前后端开发人员
2、规范性:保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息
3、一致性:接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧
4、可测性:直接在接口文档上进行测试,以方便理解业务

3.2、继承knife4j

knife4j属于service模块的公共资源,因此我们集成到service-util模块

3.2.1、添加依赖

操作模块:service-util

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>

说明:guigu-auth-parent已加入版本管理

3.2.2、添加knife4j配置类

```java
package com.atguigu.common.config.knife4j;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

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

/**
 * knife4j配置信息
 */
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfig {

    @Bean
    public Docket adminApiConfig(){
        List<Parameter> pars = new ArrayList<>();
        ParameterBuilder tokenPar = new ParameterBuilder();
        tokenPar.name("token")
                .description("用户token")
                .defaultValue("")
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .required(false)
                .build();
        pars.add(tokenPar.build());
        //添加head参数end

        Docket adminApi = new Docket(DocumentationType.SWAGGER_2)
                .groupName("adminApi")
                .apiInfo(adminApiInfo())
                .select()
                //只显示admin路径下的页面
                .apis(RequestHandlerSelectors.basePackage("com.atguigu"))
                .paths(PathSelectors.regex("/admin/.*"))
                .build()
                .globalOperationParameters(pars);
        return adminApi;
    }

    private ApiInfo adminApiInfo(){

        return new ApiInfoBuilder()
                .title("后台管理系统-API文档")
                .description("本文档描述了后台管理系统微服务接口定义")
                .version("1.0")
                .contact(new Contact("atguigu", "http://atguigu.com", "atguigu@qq.com"))
                .build();
    }


}

在这里插入图片描述
启动springboot启动类,访问链接:http://localhost:8800/doc.html
访问成功界面:
在这里插入图片描述
note:这边我的出现问题,knife4j文档请求异常404。
可能原因:springboot3版本与其不兼容问题,目前尚未解决
所以我这边采用postman进行测试:
在这里插入图片描述
2024.04.20更新:
今天重新把之前的文档项目重新看了下,又把knife4j重新试了一下。目前已经成功。
在这里插入图片描述
可以按照我标出来的地方两个版本号都试试(之前是3.0.3的版本,今天试了2.0.1的版本可以用,又重新换回3.0.3的版本又可以用了)PS:knife4j确实比postman好用!
在这里插入图片描述

4、条件分页查询

4.1、实现方法:

两种:
1、如果使用spring,可以采用配置文件方式
在这里插入图片描述

2、使用springboot。采用配置类方法
在这里插入图片描述
实现方法
1、配置分页插件
操作模块:service-uitl,service公共资源

说明:我们将@MapperScan(“com.atguigu.auth.mapper”)提取到该配置类上面,统一管理,启动类就不需要了。


```java
package com.atguigu.common.config.mp;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.atguigu.auth.mapper")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

2、编写controller的分页方法
(1)需要参数:分页相关参数(当前页和每页显示记录条数)
(2)需要参数:条件参数

(1)创建page对象,传递分页相关参数(page,limit)
(2)封装条件,(首先判断是否为空,不为空进行封装)
(3)调用方法

/**
     * 条件分页查询
     * page:当前页
     * limit:每页记录数
     * SysRoleQueryVo:条件对象
     */
    @ApiOperation("条件分页查询")
    @GetMapping("{page}/{limit}")
    public Result pageQueryRole(@PathVariable Long page,
                                @PathVariable Long limit,
                                SysRoleQueryVo sysRoleQueryVo){
//        调用service方法的实现
//        1、创建page对象,传递分页相关参数(必须有的步骤)
        Page<SysRole> page1=new Page<>(page,limit);
//        2、封装条件,判断是否为空,不为空进行封装
        LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
        String roleName = sysRoleQueryVo.getRoleName();
        if(!StringUtils.isEmpty(roleName)){
            //封装
            wrapper.like(SysRole::getRoleName,roleName);

        }
//        3、调用方法实现
        IPage<SysRole> pageModel = sysRoleService.page(page1,wrapper);
        return Result.ok(pageModel);
    }

3、调用service的方法实现条件分页查询
在这里插入图片描述

note:之前使用的java版本是17,项目的是1.8。最好改为和项目一样的1.8,不然后面会因为依赖的版本问题导致各种各样的问题。我从这里开始改为使用jdk1.8了。
改为jdk1.8方法,挺简单的,一台电脑可以安装多个不同的jdk版本,配置好就行。网上有相应教程,配置完之后把项目的jdk改为1.8即可。(改完之后knife4j还是不能使用,不管了,就用postman了,就当为之后开发练手了)

5、其他controller方法

  /**
     * 添加角色
     */
    @ApiOperation("添加角色")
    @PostMapping("save")//Get提交方式没有请求体RequestBody(json格式传参),需要改为Post
    public Result save(@RequestBody SysRole role){
//        调用service方法
        boolean is_success = sysRoleService.save(role);
        if(is_success){
            return Result.ok();
        }else{
            return Result.fail();
        }
    }

    @ApiOperation("根据id查询")
    @GetMapping("get/{id}")
    public Result get(@PathVariable Long id){
        SysRole sysRole = sysRoleService.getById(id);
        return Result.ok(sysRole);
    }

    @ApiOperation("修改角色")
    @PutMapping("update")//Get提交方式没有请求体RequestBody(json格式传参),需要改为Post
    public Result update(@RequestBody SysRole role){
//        调用service方法
        boolean is_success = sysRoleService.updateById(role);
        if(is_success){
            return Result.ok();
        }else{
            return Result.fail();
        }
    }

    @ApiOperation("删除操作")
    @DeleteMapping("remove/{id}")
    public Result remove(@PathVariable Long id){
        boolean is_success = sysRoleService.removeById(id);
        if(is_success){
            return Result.ok();
        }else{
            return Result.fail();
        }

    }

    @ApiOperation("批量删除操作")
    @DeleteMapping("batchRemove")
    public Result batchRemove(@RequestBody List<Long> idList){
        boolean is_success = sysRoleService.removeByIds(idList);
        if(is_success){
            return Result.ok();
        }else{
            return Result.fail();
        }

    }

postman测试:
在这里插入图片描述
配置日期时间格式

application-dev.yml添加以下内容

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

在这里插入图片描述

6、统一异常处理

制造异常
在这里插入图片描述

异常处理步骤:
1、创建类,在类上面添加注解@ControllerAdvice
2、在类添加执行的方法(全局、特定、自定义执行不同的方法),在方法上添加注解,指定哪个异常出现的时候执行(底层是AOP,面向切面编程。即在不改变源代码的情况下,添加功能)

package com.atguigu.common.config.exception;

import com.atguigu.common.result.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice//定义全局异常处理器
public class GlobalExceptionHandler {
    //全局异常处理执行的方法
    @ExceptionHandler(Exception.class)
    @ResponseBody//为了让其可以返回json数据,因为controller中加入RestController(包含ResponseBody)可以直接返回json数据,
    // 但是这个类并不在controller中
    public Result error(Exception e){
        e.printStackTrace();
        return Result.fail().message("执行全局异常处理...");
    }

    //特定异常处理
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public Result error(ArithmeticException e){
        e.printStackTrace();
        return Result.fail().message("执行特定异常处理...");
    }

    //自定义异常处理
    @ExceptionHandler(GuiguException.class)
    @ResponseBody
    public Result error(GuiguException e){
        e.printStackTrace();
        return Result.fail().code(e.getCode()).message(e.getMsg());
    }
}

6.1、全局异常处理

在这里插入图片描述

在这里插入图片描述

6.2、特定异常处理

在这里插入图片描述

在这里插入图片描述
当发生异常时,会先寻找特定异常,没有特定异常才会寻找全局异常

6.3、自定义异常处理

1、创建异常类,继承RuntimeException
2、在异常类添加属性(状态码、描述信息)

package com.atguigu.common.config.exception;

import com.atguigu.common.result.ResultCodeEnum;
import lombok.Data;

@Data
public class GuiguException extends RuntimeException{

    private Integer code;//状态码
    private String msg;//描述信息

    public GuiguException(Integer code,String msg){
        super(msg);
        this.code=code;
        this.msg=msg;
    }

    /**
     * 接收枚举类型对象
     * @param resultCodeEnum
     */
    public GuiguException(ResultCodeEnum resultCodeEnum) {
        super(resultCodeEnum.getMessage());
        this.code = resultCodeEnum.getCode();
        this.msg = resultCodeEnum.getMessage();
    }

    @Override
    public String toString() {
        return "GuliException{" +
                "code=" + code +
                ", message=" + this.getMessage() +
                '}';
    }
}

3、在出现异常的地方,手动抛出异常

在这里插入图片描述

4、在之前创建的异常类,添加执行的方法

package com.atguigu.common.config.exception;

import com.atguigu.common.result.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {
    //全局异常处理执行的方法
    @ExceptionHandler(Exception.class)
    @ResponseBody//为了让其可以返回json数据,因为controller中加入RestController(包含ResponseBody)可以直接返回json数据,
    // 但是这个类并不在controller中
    public Result error(Exception e){
        e.printStackTrace();
        return Result.fail().message("执行全局异常处理...");
    }

    //特定异常处理
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public Result error(ArithmeticException e){
        e.printStackTrace();
        return Result.fail().message("执行特定异常处理...");
    }

    //自定义异常处理
    @ExceptionHandler(GuiguException.class)
    @ResponseBody
    public Result error(GuiguException e){
        e.printStackTrace();
        return Result.fail().code(e.getCode()).message(e.getMsg());
    }
}

在这里插入图片描述

四、前端基础

1、前端开发介绍

2、下载安装vscode

1、下载地址:https://code.visualstudio.com/
2、插件安装:为方便后续开发,建议安装如下插件
在这里插入图片描述
3、创建项目
vscode本身没有新建项目的选项,所以要先创建一个空的文件夹,如project_xxxx。
然后打开vscode,再在vscode里面选择 File -> Open Folder 打开文件夹,这样才可以创建项目。
4、保存工作区
打开文件夹后,选择“文件 -> 将工作区另存为…”,为工作区文件起一个名字,存储在刚才的文件夹下即可
5、新建文件夹和网页
6、预览网页
以文件路径方式打开网页预览

需要安装“open in browser”插件:

文件右键 -> Open In Default Browser

以服务器方式打开网页预览

需要安装“Live Server”插件:

文件右键 -> Open with Live Server

3、ES6入门

基础语法:

1、模板字符串
创建模板字符串.html

// 2、字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。
let name = "Mike"
let age = 27
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info)
// My Name is Mike,I am 28 years old next year.

2、对象拓展运算符
拓展运算符(…)用于取出参数对象所有可遍历属性然后拷贝到当前对象

// 1、拷贝对象
let person1 = {name: "Amy", age: 15}
let someone = { ...person1 }
console.log(someone)  //{name: "Amy", age: 15}

3、箭头函数
箭头函数提供了一种更加简洁的函数书写方法。基本语法是:参数=>函数体

// 传统
var f1 = function(a){
    return a
}
console.log(f1(1))
// ES6
var f2 = a => a
console.log(f2(1))
// 当箭头函数没有参数或者有多个参数,要用 () 括起来。
// 当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,
// 当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var f3 = (a,b) => {
    let result = a+b
    return result
}
console.log(f3(6,2))  // 8
// 前面代码相当于:
var f4 = (a,b) => a+b

箭头函数多用于匿名函数的定义

4、Vue基础

1、基础语法:

1、Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。

Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

官方网站:https://cn.vuejs.org

2、示例

<!-- id标识vue作用的范围 -->
<div id="app">
    <!-- {{}} 插值表达式,绑定vue中的data数据 -->
    {{ message }}
</div>
<script src="vue.min.js"></script>
<script>
    // 创建一个vue对象
    new Vue({
        el: '#app',//绑定vue作用的范围
        data: {//定义页面中显示的模型数据
            message: 'Hello Vue!'
        }
    })
</script>

这就是声明式渲染:Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统

这里的核心思想就是没有繁琐的DOM操作,例如jQuery中,我们需要先找到div节点,获取到DOM对象,然后进行一系列的节点操作
3、实例生命周期

<body>
    <div id="app">
        {{info}}
    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
               info:'hello atguigu' 
            },
            created() { //渲染前
                debugger
                console.log('created....')
            },
            mounted() { //渲染后
                debugger
                console.log('mounted....')
            }
        })
    </script>
</body>
2、Axios

Axios是独立于Vue的一个项目,基于promise用于浏览器和node.js的http客户端

  • 在浏览器中可以帮助我们完成 ajax请求的发送

  • 在node.js中可以向远程接口发送请求

    引入vue和axios的js文件

<script src="vue.min.js"></script>
<script src="axios.min.js"></script>

进行axios调用

var app = new Vue({
    el: '#app',
    data: {
        memberList: []//数组
    },
    created() {
        this.getList()
    },
    methods: {
        getList(id) {
            axios.get('data.json')
            .then(response => {
                console.log(response)
                this.memberList = response.data.data.items
            })
            .catch(error => {
                console.log(error)
            })
        }
    }
})

创建data.json文件

{
    "success":true,
    "code":20000,
    "message":"成功",
    "data":{
        "list":[
            {"name":"lucy","age":20},
            {"name":"mary","age":30},
            {"name":"jack","age":40}
        ]
    }
}

5、Node.js入门

1、简介:

简单的说 Node.js 就是运行在服务端的 JavaScript。

Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

如果你是一个前端程序员,你不懂得像PHP、Python或Ruby等动态编程语言,然后你想创建自己的服务,那么Node.js是一个非常好的选择。

Node.js 是运行在服务端的 JavaScript,如果你熟悉Javascript,那么你将会很容易的学会Node.js。

当然,如果你是后端程序员,想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择。
2、安装
官网:https://nodejs.org/en/

中文网:http://nodejs.cn/

LTS:长期支持版本

Current:最新版
查看版本:

node -v

3、简单入门
创建 01-控制台程序.js

console.log('Hello Node.js')

进入到程序所在的目录,输入

node 01-控制台程序.js

浏览器的内核包括两部分核心:

  • DOM渲染引擎;
  • js解析器(js引擎)
  • js运行在浏览器中的内核中的js引擎内部

Node.js是脱离浏览器环境运行的JavaScript程序,基于V8 引擎(Chrome 的 JavaScript的引擎)

6、NPM

1、简介:
什么是npm?
NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于前端的Maven 。

NPM工具的安装位置
我们通过npm 可以很方便地下载js库,管理前端工程。

Node.js默认安装的npm包和工具的位置:Node.js目录\node_modules

  • 在这个目录下你可以看见 npm目录,npm本身就是被NPM包管理器管理的一个工具,说明 Node.js已经集成了npm工具

2、使用npm管理项目
(1)创建文件夹npm
(2)项目初始化

#建立一个空文件夹,在命令提示符进入该文件夹  执行命令初始化
npm init
#按照提示输入相关信息,如果是用默认值则直接回车即可。
#name: 项目名称
#version: 项目版本号
#description: 项目描述
#keywords: {Array}关键词,便于用户搜索到我们的项目
#最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml
#我们之后也可以根据需要进行修改。

在这里插入图片描述

#如果想直接生成 package.json 文件,那么可以使用命令
npm init -y

3、修改npm镜像
NPM官方的管理的包都是从 http://npmjs.com下载的,但是这个网站在国内速度很慢。

这里推荐使用淘宝 NPM 镜像 http://npm.taobao.org/ ,淘宝 NPM 镜像是一个完整 npmjs.com 镜像,同步频率目前为 10分钟一次,以保证尽量与官方服务同步。
设置镜像地址

#经过下面的配置,以后所有的 npm install 都会经过淘宝的镜像地址下载
npm config set registry https://registry.npm.taobao.org 
#查看npm配置信息
npm config list

4、npm install命令的使用

#使用 npm install 安装依赖包的最新版,
#模块安装的位置:项目目录\node_modules
#安装会自动在项目目录下添加 package-lock.json文件,这个文件帮助锁定安装包的版本
#同时package.json 文件中,依赖包会被添加到dependencies节点下,类似maven中的 <dependencies>
npm install jquery
#npm管理的项目在备份和传输的时候一般不携带node_modules文件夹
npm install #根据package.json中的配置下载依赖,初始化项目
#如果安装时想指定特定的版本
npm install jquery@2.1.x

7、模块化开发

五、角色管理前端

1、前端框架

1、vue-element-admin

vue-element-admin是基于element-ui 的一套后台管理系统集成方案。

功能:https://panjiachen.github.io/vue-element-admin-site/zh/guide/#功能

GitHub地址:https://github.com/PanJiaChen/vue-element-admin

项目在线预览:https://panjiachen.gitee.io/vue-element-admin

2、vue-admin-template

2.1、简介

vue-admin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版),可作为模板进行二次开发。

GitHub地址链接

**建议:**你可以在 vue-admin-template 的基础上进行二次开发,把 vue-element-admin当做工具箱,想要什么功能或者组件就去 vue-element-admin 那里复制过来。
2.2、安装

#修改项目名称 vue-admin-template 改为 guigu-auth-ui
# 解压压缩包
# 进入目录
cd guigu-auth-ui
# 安装依赖
npm install
# 启动。执行后,浏览器自动弹出并访问http://localhost:9528/
npm run dev

2.3、源码目录结构

|-dist 生产环境打包生成的打包项目
|-mock 使用mockjs来mock接口
|-public 包含会被自动打包到项目根路径的文件夹
	|-index.html 唯一的页面
|-src
	|-api 包含接口请求函数模块
		|-table.js  表格列表mock数据接口的请求函数
		|-user.js  用户登陆相关mock数据接口的请求函数
	|-assets 组件中需要使用的公用资源
		|-404_images 404页面的图片
	|-components 非路由组件
		|-SvgIcon svg图标组件
		|-Breadcrumb 面包屑组件(头部水平方向的层级组件)
		|-Hamburger 用来点击切换左侧菜单导航的图标组件
	|-icons
		|-svg 包含一些svg图片文件
		|-index.js 全局注册SvgIcon组件,加载所有svg图片并暴露所有svg文件名的数组
	|-layout
		|-components 组成整体布局的一些子组件
		|-mixin 组件中可复用的代码
		|-index.vue 后台管理的整体界面布局组件
	|-router
		|-index.js 路由器
	|-store
		|-modules
			|-app.js 管理应用相关数据
			|-settings.js 管理设置相关数据
			|-user.js 管理后台登陆用户相关数据
		|-getters.js 提供子模块相关数据的getters计算属性
		|-index.js vuex的store
	|-styles
		|-xxx.scss 项目组件需要使用的一些样式(使用scss)
	|-utils 一些工具函数
		|-auth.js 操作登陆用户的token cookie
		|-get-page-title.js 得到要显示的网页title
		|-request.js axios二次封装的模块
		|-validate.js 检验相关工具函数
		|-index.js 日期和请求参数处理相关工具函数
	|-views 路由组件文件夹
		|-dashboard 首页
		|-login 登陆
	|-App.vue 应用根组件
	|-main.js 入口js
	|-permission.js 使用全局守卫实现路由权限控制的模块
	|-settings.js 包含应用设置信息的模块
|-.env.development 指定了开发环境的代理服务器前缀路径
|-.env.production 指定了生产环境的代理服务器前缀路径
|-.eslintignore eslint的忽略配置
|-.eslintrc.js eslint的检查配置
|-.gitignore git的忽略配置
|-.npmrc 指定npm的淘宝镜像和sass的下载地址
|-babel.config.js babel的配置
|-jsconfig.json 用于vscode引入路径提示的配置
|-package.json 当前项目包信息
|-package-lock.json 当前项目依赖的第三方包的精确信息
|-vue.config.js webpack相关配置(: 代理服务器)

2、角色管理前端开发

1、列表

在这里插入图片描述
1、添加角色管理路由
在这里插入图片描述
2、在api文件夹创建js文件,定义接口信息
在这里插入图片描述
note:这边稍微有个小坑,url:

url: `${api_name}/${current}/${limit}`,
两边符号是``并不是''(注意区分)

当下述页面正常访问但是不显示任何数据的时候,注意下是否是上述提到的问题。
在这里插入图片描述

3、在views文件夹创建页面,在页面引入定义接口js文件,调用接口通过axios实现功能
在这里插入图片描述

2、删除

在这里插入图片描述

3、角色添加

1、定义api

src/api/system/sysRole.js

save(role) {
  return request({
    url: `${api_name}/save`,
    method: 'post',
    data: role
  })
}
2、定义data
export default {
  // 定义数据模型
  data() {
    return {
      list: [], // 讲师列表
      total: 0, // 总记录数
      page: 1, // 页码
      limit: 10, // 每页记录数
      searchObj: {}, // 查询条件
      multipleSelection: [],// 批量删除选中的记录列表

      dialogVisible: false,
      sysRole: {},
      saveBtnDisabled: false
    }
  },
  ...
}
3、定义添加按钮

src/views/system/sysRole/list.vue

表格上面添加按钮

<!-- 工具条 -->
<div class="tools-div">
  <el-button type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>
</div>
4、定义弹出层

src/views/system/sysRole/list.vue

表格最下面添加弹出层

<el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" >
      <el-form ref="dataForm" :model="sysRole" label-width="150px" size="small" style="padding-right: 40px;">
        <el-form-item label="角色名称">
          <el-input v-model="sysRole.roleName"/>
        </el-form-item>
        <el-form-item label="角色编码">
          <el-input v-model="sysRole.roleCode"/>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
        <el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
      </span>
    </el-dialog>
5、实现功能
add(){
  this.dialogVisible = true
},

saveOrUpdate() {
  this.saveBtnDisabled = true // 防止表单重复提交
  if (!this.sysRole.id) {
    this.saveData()
  } else {
    this.updateData()
  }
},

// 新增
saveData() {
  api.save(this.sysRole).then(response => {
    this.$message.success(response.message || '操作成功')
    this.dialogVisible = false
    this.fetchData(this.page)
  })
}

4、角色修改与数据回显

1、定义api

src/api/system/sysRole.js

getById(id) {
  return request({
    url: `${api_name}/get/${id}`,
    method: 'get'
  })
},

updateById(role) {
  return request({
    url: `${api_name}/update`,
    method: 'put',
    data: role
  })
}
2、组件中调用api

methods中定义fetchDataById

edit(id) {
  this.dialogVisible = true
  this.fetchDataById(id)
},

fetchDataById(id) {
  api.getById(id).then(response => {
    this.sysRole = response.data
  })
}
3、修改提交
updateData() {
  api.updateById(this.sysRole).then(response => {
    this.$message.success(response.message || '操作成功')
    this.dialogVisible = false
    this.fetchData(this.page)
  })
}

5、批量删除

1、定义api

src/api/system/sysRole.js

batchRemove(idList) {
  return request({
    url: `${api_name}/batchRemove`,
    method: `delete`,
    data: idList
  })
},
2、初始化组件

src/views/system/sysRole/list.vue

在table组件上添加 批量删除 按钮

<!-- 工具条 -->
<div class="tools-div">
  <el-button type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>
  <el-button class="btn-add" size="mini" @click="batchRemove()" >批量删除</el-button>
</div>

在table组件上添加复选框

<el-table
  v-loading="listLoading"
  :data="list"
  stripe
  border
  style="width: 100%;margin-top: 10px;"
  @selection-change="handleSelectionChange">
  <el-table-column type="selection"/>
3、实现功能

data定义数据

multipleSelection: []// 批量删除选中的记录列表

完善方法

// 当多选选项发生变化的时候调用
handleSelectionChange(selection) {
  console.log(selection)
  this.multipleSelection = selection
},
// 批量删除
batchRemove() {
  if (this.multipleSelection.length === 0) {
    this.$message.warning('请选择要删除的记录!')
    return
  }
  this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    // 点击确定,远程调用ajax
    // 遍历selection,将id取出放入id列表
    var idList = []
    this.multipleSelection.forEach(item => {
      idList.push(item.id)
    })
    // 调用api
    return api.batchRemove(idList)
  }).then((response) => {
    this.fetchData()
    this.$message.success(response.message)
  })
}

六、用户管理

1、用户管理

在这里插入图片描述

1、用户管理CRUD
1、Mapper
package com.atguigu.auth.mapper;

import com.atguigu.model.system.SysUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface SysUserMapper extends BaseMapper<SysUser> {

}
2、service接口

SysUserService接口

package com.atguigu.system.service;


import com.atguigu.model.system.SysUser;
import com.atguigu.vo.system.SysUserQueryVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;

public interface SysUserService extends IService<SysUser> {

}
3、service接口实现
package com.atguigu.system.service.impl;

import com.atguigu.model.system.SysUser;
import com.atguigu.system.mapper.SysUserMapper;
import com.atguigu.system.service.SysUserService;
import com.atguigu.vo.system.SysUserQueryVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

}
4、controller

/**
 * <p>
 * 用户表 前端控制器
 * </p>
 *
 * @author atguigu
 * @since 2024-04-16
 */
@Api(tags = "用户管理接口")
@RestController
@RequestMapping("/admin/system/sysUser")
public class SysUserController {
    @Autowired
    private SysUserService service;

    /**
     * 条件分页查询
     */
    @ApiOperation("条件分页查询")
    @GetMapping("{page}/{limit}")
    public Result info(@PathVariable Long page,
                       @PathVariable Long limit,
                       SysUserQueryVo sysUserQueryVo){
//        1、创建page对象,传递分页相关参数
        Page<SysUser> page1 = new Page<>(page, limit);
//        2、封装条件,判断是否为空,不为空进行封装
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        String username=sysUserQueryVo.getKeyword();
        String createTime=sysUserQueryVo.getCreateTimeBegin();
        String endTime=sysUserQueryVo.getCreateTimeEnd();
        if (!StringUtils.isEmpty(username)){
            wrapper.like(SysUser::getName,username);
        }

        //ge 大于等于
        if(!StringUtils.isEmpty(createTime)) {
            wrapper.ge(SysUser::getCreateTime,createTime);
        }
        //le 小于等于
        if(!StringUtils.isEmpty(endTime)) {
            wrapper.le(SysUser::getCreateTime,endTime);
        }

//        3、调用方法实现(模板写法)
        IPage<SysUser> pageModel=service.page(page1,wrapper);
        pageModel.getRecords();
        return Result.ok(pageModel);

    }

    @ApiOperation(value = "获取用户")
    @GetMapping("get/{id}")
    public Result get(@PathVariable Long id){
        SysUser user = service.getById(id);
        return Result.ok(user);

    }

    @ApiOperation(value = "保存用户")
    @PostMapping("save")
    public Result save(@RequestBody SysUser user){
        service.save(user);
        return Result.ok();

    }
    @ApiOperation(value = "更新用户")
    @PutMapping("update")
    public Result updateById(@RequestBody SysUser user){
        service.updateById(user);//更新用户 updateById传入对象
        return Result.ok();
    }

    @ApiOperation(value = "删除用户")
    @DeleteMapping("remove/{id}")
    public Result remove(@PathVariable Long id){
        service.removeById(id);
        return Result.ok();
    }

}

postman进行接口测试/knife4j测试
http://localhost:8800/doc.html

2、用户管理前端实现
2.1、添加路由

修改 src/router/index.js 文件

{
  path: '/system',
  component: Layout,
  meta: {
    title: '系统管理',
    icon: 'el-icon-s-tools'
  },
  alwaysShow: true,
  children: [
    {
      name: 'sysUser',
      path: 'sysUser',
      component: () => import('@/views/system/sysUser/list'),
      meta: {
        title: '用户管理',
        icon: 'el-icon-s-custom'
      },
    },
    {
      path: 'sysRole',
      component: () => import('@/views/system/sysRole/list'),
      meta: {
        title: '角色管理',
        icon: 'el-icon-s-help'
      },
    }
  ]
},
2.2、定义基础api

创建文件 src/api/system/sysUser.js

import request from '@/utils/request'

const api_name = '/admin/system/sysUser'

export default {

  getPageList(page, limit, searchObj) {
    return request({
      url: `${api_name}/${page}/${limit}`,
      method: 'get',
      params: searchObj // url查询字符串或表单键值对
    })
  },
  getById(id) {
    return request({
      url: `${api_name}/get/${id}`,
      method: 'get'
    })
  },

  save(role) {
    return request({
      url: `${api_name}/save`,
      method: 'post',
      data: role
    })
  },

  updateById(role) {
    return request({
      url: `${api_name}/update`,
      method: 'put',
      data: role
    })
  },
  removeById(id) {
    return request({
      url: `${api_name}/remove/${id}`,
      method: 'delete'
    })
  }
}
2.3、实现页面功能

创建src/views/system/sysUser/list.vue

<template>
  <div class="app-container">

    <div class="search-div">
      <el-form label-width="70px" size="small">
        <el-row>
          <el-col :span="8">
            <el-form-item label="关 键 字">
              <el-input style="width: 95%" v-model="searchObj.keyword" placeholder="用户名/姓名/手机号码"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="操作时间">
              <el-date-picker
                v-model="createTimes"
                type="datetimerange"
                range-separator="至"
                start-placeholder="开始时间"
                end-placeholder="结束时间"
                value-format="yyyy-MM-dd HH:mm:ss"
                style="margin-right: 10px;width: 100%;"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row style="display:flex">
          <el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="fetchData()">搜索</el-button>
          <el-button icon="el-icon-refresh" size="mini" @click="resetData">重置</el-button>
        </el-row>
      </el-form>
    </div>

    <!-- 工具条 -->
    <div class="tools-div">
      <el-button type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>
    </div>

    <!-- 列表 -->
    <el-table
      v-loading="listLoading"
      :data="list"
      stripe
      border
      style="width: 100%;margin-top: 10px;">

      <el-table-column
        label="序号"
        width="70"
        align="center">
        <template slot-scope="scope">
          {{ (page - 1) * limit + scope.$index + 1 }}
        </template>
      </el-table-column>

      <el-table-column prop="username" label="用户名" width="100"/>
      <el-table-column prop="name" label="姓名" width="70"/>
      <el-table-column prop="phone" label="手机" width="120"/>
      <el-table-column prop="postName" label="岗位" width="100"/>
      <el-table-column prop="deptName" label="部门" width="100"/>
      <el-table-column label="所属角色" width="130">
        <template slot-scope="scope">
          <span v-for="item in scope.row.roleList" :key="item.id" style="margin-right: 10px;">{{ item.roleName }}</span>
        </template>
      </el-table-column>
      <el-table-column label="状态" width="80">
        <template slot-scope="scope">
          <el-switch
            v-model="scope.row.status === 1"
            @change="switchStatus(scope.row)">
          </el-switch>
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="创建时间" width="160"/>

      <el-table-column label="操作" width="180" align="center" fixed="right">
        <template slot-scope="scope">
          <el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row.id)" title="修改"/>
          <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除" />
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页组件 -->
    <el-pagination
      :current-page="page"
      :total="total"
      :page-size="limit"
      :page-sizes="[5, 10, 20, 30, 40, 50, 100]"
      style="padding: 30px 0; text-align: center;"
      layout="sizes, prev, pager, next, jumper, ->, total, slot"
      @current-change="fetchData"
      @size-change="changeSize"
    />

    <el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" >
      <el-form ref="dataForm" :model="sysUser"  label-width="100px" size="small" style="padding-right: 40px;">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="sysUser.username"/>
        </el-form-item>
        <el-form-item v-if="!sysUser.id" label="密码" prop="password">
          <el-input v-model="sysUser.password" type="password"/>
        </el-form-item>
        <el-form-item label="姓名" prop="name">
          <el-input v-model="sysUser.name"/>
        </el-form-item>
        <el-form-item label="手机" prop="phone">
          <el-input v-model="sysUser.phone"/>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
        <el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import api from '@/api/system/sysUser'
const defaultForm = {
  id: '',
  username: '',
  password: '',
  name: '',
  phone: '',
  status: 1
}
export default {
  data() {
    return {
      listLoading: true, // 数据是否正在加载
      list: null, // banner列表
      total: 0, // 数据库中的总记录数
      page: 1, // 默认页码
      limit: 10, // 每页记录数
      searchObj: {}, // 查询表单对象

      createTimes: [],

      dialogVisible: false,
      sysUser: defaultForm,
      saveBtnDisabled: false,
    }
  },

  // 生命周期函数:内存准备完毕,页面尚未渲染
  created() {
    console.log('list created......')
    this.fetchData()
  },

  // 生命周期函数:内存准备完毕,页面渲染成功
  mounted() {
    console.log('list mounted......')
  },

  methods: {
    // 当页码发生改变的时候
    changeSize(size) {
      console.log(size)
      this.limit = size
      this.fetchData(1)
    },

    // 加载banner列表数据
    fetchData(page = 1) {
      debugger
      this.page = page
      console.log('翻页。。。' + this.page)

      if(this.createTimes && this.createTimes.length ==2) {
        this.searchObj.createTimeBegin = this.createTimes[0]
        this.searchObj.createTimeEnd = this.createTimes[1]
      }

      api.getPageList(this.page, this.limit, this.searchObj).then(
        response => {
          //this.list = response.data.list
          this.list = response.data.records
          this.total = response.data.total

          // 数据加载并绑定成功
          this.listLoading = false
        }
      )
    },

    // 重置查询表单
    resetData() {
      console.log('重置查询表单')
      this.searchObj = {}
      this.createTimes = []
      this.fetchData()
    },

    // 根据id删除数据
    removeDataById(id) {
      // debugger
      this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => { // promise
        // 点击确定,远程调用ajax
        return api.removeById(id)
      }).then((response) => {
        this.fetchData(this.page)
        this.$message.success(response.message || '删除成功')
      }).catch(() => {
         this.$message.info('取消删除')
      })
    },

    // -------------
    add(){
      this.dialogVisible = true
      this.sysUser = Object.assign({}, defaultForm)
    },

    edit(id) {
      this.dialogVisible = true
      this.fetchDataById(id)
    },

    fetchDataById(id) {
      api.getById(id).then(response => {
        this.sysUser = response.data
      })
    },

    saveOrUpdate() {
      this.$refs.dataForm.validate(valid => {
        if (valid) {
          this.saveBtnDisabled = true // 防止表单重复提交
          if (!this.sysUser.id) {
            this.saveData()
          } else {
            this.updateData()
          }
        }
      })
    },

    // 新增
    saveData() {
      api.save(this.sysUser).then(response => {
        this.$message.success('操作成功')
        this.dialogVisible = false
        this.fetchData(this.page)
      })
    },

    // 根据id更新记录
    updateData() {
      api.updateById(this.sysUser).then(response => {
        this.$message.success(response.message || '操作成功')
        this.dialogVisible = false
        this.fetchData(this.page)
      })
    }
  }
}
</script>

2、给用户分配角色及修改用户状态

1、给用户分配角色
1、接口分析

1、进入分配页面:获取已分配角色与全部角色,进行页面展示

2、保存分配角色:删除之前分配的角色和保存现在分配的角色

2、controller方法

操作类:SysRoleController

@ApiOperation(value = "根据用户获取角色数据")
@GetMapping("/toAssign/{userId}")
public Result toAssign(@PathVariable Long userId) {
    Map<String, Object> roleMap = sysRoleService.findRoleByAdminId(userId);
    return Result.ok(roleMap);
}

@ApiOperation(value = "根据用户分配角色")
@PostMapping("/doAssign")
public Result doAssign(@RequestBody AssginRoleVo assginRoleVo) {
    sysRoleService.doAssign(assginRoleVo);
    return Result.ok();
}
3、service接口

操作类:SysRoleService

/**
 * 根据用户获取角色数据
 * @param userId
 * @return
 */
Map<String, Object> findRoleByAdminId(Long userId);

/**
 * 分配角色
 * @param assginRoleVo
 */
void doAssign(AssginRoleVo assginRoleVo);
4、service接口实现

操作类:SysRoleServiceImpl
在这里插入图片描述


    @Autowired
    private SysUserRoleService sysUserRoleService;

    @Override
    public Map<String, Object> findRoleDataByUserId(Long userId) {

        //1、查询所有角色,返回list集合
        List<SysRole> allRoleList = baseMapper.selectList(null);

        //2、根据userid查询 角色用户关系表,查询userid对应所有角色id
        LambdaQueryWrapper<SysUserRole> wrapper=new LambdaQueryWrapper<>();
        wrapper.eq(SysUserRole::getUserId,userId);
        List<SysUserRole> existUserRoleList = sysUserRoleService.list(wrapper);

        //从查询出来的用户id对应角色list集合,获取所有角色id
        List<Long> existRoleIdList = existUserRoleList.stream().map(c->c.getRoleId()).collect(Collectors.toList());
        /**
         *existUserRoleList.stream():首先,从已存在的用户角色关系列表(existUserRoleList)创建一个Stream流。
         * .map(c->c.getRoleId()):接着,使用map操作对流中的每个元素(这里假设是用户角色关系的对象)应用给定的Lambda表达式,
         * 提取每个对象中的角色ID (c.getRoleId()), 这里c是流中每个元素的引用变量。
         * .collect(Collectors.toList()):最后,通过collect方法将流中的数据收集(汇总)到一个新的列表中,
         * 这里使用Collectors.toList()收集器来实现。
         */
        //3、根据查询所有角色id,找到对应的用户信息
        List<SysRole> assignRoleList = new ArrayList<>();
        for (SysRole sysRole:allRoleList){
            //比较
            if(existRoleIdList.contains(sysRole.getId())){
                assignRoleList.add(sysRole);
            }
        }

        Map<String, Object> roleMap = new HashMap<>();
        roleMap.put("assginRoleList", assignRoleList);
        roleMap.put("allRolesList", allRoleList);
        return roleMap;
    }

    @Override
    public void doAssign(AssginRoleVo assginRoleVo) {
        //把用户之前分配角色数据删除(用户角色关系表中,根据userid删除)
        LambdaQueryWrapper<SysUserRole> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUserRole::getUserId,assginRoleVo.getUserId());
        sysUserRoleService.remove(wrapper);
        //重新进行分配
        List<Long> roleIdList=assginRoleVo.getRoleIdList();
        for (Long roleId:roleIdList){
            if (StringUtils.isEmpty(roleId)){
                continue;
            }
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(assginRoleVo.getUserId());
            sysUserRole.setRoleId(roleId);
            sysUserRoleService.save(sysUserRole);
        }

5、添加Sys User Role Mapper类
package com.atguigu.system.mapper;

import com.atguigu.model.system.SysUserRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {

}

Note1:
Java中的Lambda表达式是一种简洁的函数式编程语法,它首次出现在Java 8版本中,允许开发人员创建匿名函数。Lambda表达式的主要特点包括:

  1. 匿名性:Lambda表达式没有名称,因此也被称为匿名函数。它代表一段可执行的代码块,可以在需要函数对象的地方直接定义。

  2. 简洁性:通过减少冗余的函数声明和实现,Lambda表达式使得代码更为精炼。在需要实现只有一个抽象方法的接口(称为函数式接口)时,可以直接用Lambda表达式替代传统的匿名内部类实现。

  3. 函数作为参数:Lambda表达式能够作为方法参数传递,支持函数式编程风格,这对于事件处理、排序、过滤、转换等操作特别有用,尤其是在处理集合数据时结合Stream API能极大提高代码的可读性和效率。

  4. 语法结构:一个基本的Java Lambda表达式的语法格式是 (parameters) -> expressionOrBlock,其中:

    • parameters 是输入参数列表,可以省略类型(如果可以从上下文推断出);
    • -> 是分隔符,表示“去向”,指向右边的函数体;
    • expressionOrBlock 是Lambda表达式的核心,它可以是一个表达式(如果表达式的结果就是函数的返回值)或一个代码块(当需要多条语句时)。
  5. 闭包:尽管Lambda表达式与数学上的闭包概念不尽相同,但它确实实现了类似的效果,即Lambda表达式可以捕获并引用其所在作用域中的变量。

  6. 函数式接口:Lambda表达式与Java中的函数式接口紧密关联,函数式接口就是一个接口,里面仅有一个抽象方法。Java 8提供了许多预定义的函数式接口,如 Runnable, Callable, Consumer, Supplier, Predicate, Function 等,这些接口常用于Lambda表达式的应用场景中。

例如,对于一个接受两个整数并返回它们相加结果的函数式接口 IntBinaryOperator,可以用Lambda表达式这样实现:

IntBinaryOperator adder = (int a, int b) -> a + b;

在这个例子中,(int a, int b) -> a + b 就是一个Lambda表达式,它代表了一个可以接收两个整数参数并返回它们和的函数对象。

Note2:
Java 8 引入了Stream API,这是一个用于处理数据集的强大工具,它可以以一种声明式、高效且易于并行化的风格来处理集合。Stream API并非数据结构,而是一种对集合进行操作的高级方式,它允许我们以链式调用的方式对数据进行过滤、映射、排序、聚合等各种操作。

以下是对Java Stream API的一些核心概念和操作的详解:

  1. 创建Stream:可以通过Collection系列集合的stream()parallelStream()方法创建Stream,也可以使用Arrays类的stream()方法对数组创建Stream,或者直接通过Stream类的静态工厂方法创建。

    List<String> list = Arrays.asList("a", "b", "c");
    Stream<String> stream = list.stream(); // 创建串行流
    Stream<String> parallelStream = list.parallelStream(); // 创建并行流
    
    String[] array = {"d", "e", "f"};
    Stream<String> arrayStream = Arrays.stream(array);
    
  2. 中间操作:Stream的中间操作不会立即执行任何处理,而是构建一个新的Stream,直到遇到终止操作。常见的中间操作有:

    • filter(Predicate predicate):根据给定的条件过滤元素。
    • map(Function mapper):将每个元素应用给定的函数,并生成新的Stream。
    • sorted(Comparator<? super T> comparator):对Stream中的元素进行排序。
    • limit(long maxSize):限制Stream中元素的最大数量。
    • skip(long n):跳过前n个元素。
  3. 终端操作:终端操作会触发中间操作的执行,并得出最终结果。常见的终端操作有:

    • forEach(Consumer action):对Stream中的每个元素执行指定的操作。
    • collect(Collectors collector):收集Stream中的元素到某些形式的结果,如List、Set、Map等,或者进行汇总计算如求和、最大值等。
    • reduce(BinaryOperator accumulator):对Stream中的元素应用一个累积函数,如求和、求积、求最大值/最小值等。
    • anyMatch(Predicate predicate)allMatch(Predicate predicate)noneMatch(Predicate predicate):检查Stream中的元素是否满足特定条件。
    • findFirst()findAny():找到Stream中的第一个或任意一个元素。
  4. 并行流:Java Stream API支持并行处理,可以充分利用多核处理器的优势。通过调用parallelStream()方法创建并行流后,Java会自动将其转换为并行执行的任务,但在一些情况下,需要注意数据依赖和线程安全问题。

示例代码:

// 对列表中的字符串长度大于2的元素进行筛选,并转为大写,然后排序,最后打印
list.stream()
    .filter(s -> s.length() > 2)
    .map(String::toUpperCase)
    .sorted()
    .forEach(System.out::println);

总的来说,Java Stream API极大地提升了处理集合数据的效率和代码可读性,尤其是配合Lambda表达式和其他函数式编程特性,更能让开发者专注于描述“做什么”而非“怎么做”。

2、更改用户状态
1、需求分析

用户状态:状态(1:正常 0:停用),当用户状态为正常时,可以访问后台系统,当用户状态停用后,不可以登录后台系统

2、controller方法

操作类:SysUserController

@ApiOperation(value = "更新状态")
@GetMapping("updateStatus/{id}/{status}")
public Result updateStatus(@PathVariable Long id, @PathVariable Integer status) {
    sysUserService.updateStatus(id, status);
    return Result.ok();
}
3、service接口

操作类:SysUserService

void updateStatus(Long id, Integer status);
4、service接口实现

操作类:SysUserServiceImpl

@Transactional
// 是一个用于Spring框架中的注解,它用于指定一个方法或一个类的事务性行为。
//这个注解主要用于服务层(Service Layer)的方法上,
//以确保数据库操作的原子性、一致性、隔离性和持久性(ACID特性),
//即保证一系列数据库操作要么全部成功,要么全部失败。
@Override
public void updateStatus(Long id, Integer status) {
   SysUser sysUser = this.getById(id);
   if(status.intValue() == 1) {
      sysUser.setStatus(status);
   } else {
      sysUser.setStatus(0);
   }
   this.updateById(sysUser);
}

在这@Transactional
@Override
public void updateStatus(Long id, Integer status) {
SysUser sysUser = this.getById(id);
if(status.intValue() == 1) {
sysUser.setStatus(status);
} else {
sysUser.setStatus(0);
}
this.updateById(sysUser);
}里插入图片描述

3、前端实现
3.1、添加api

src/api/system/sysUser.js

updateStatus(id, status) {
  return request({
    url: `${api_name}/updateStatus/${id}/${status}`,
    method: 'get'
  })
}

src/api/system/sysRole.js

getRoles(adminId) {
  return request({
    url: `${api_name}/toAssign/${adminId}`,
    method: 'get'
  })
},

assignRoles(assginRoleVo) {
  return request({
    url: `${api_name}/doAssign`,
    method: 'post',
    data: assginRoleVo
  })
}
3.2、修改页面

更改src/views/system/sysUser/list.vue

<template>
  <div class="app-container">

    <div class="search-div">
      <el-form label-width="70px" size="small">
        <el-row>
          <el-col :span="8">
            <el-form-item label="关 键 字">
              <el-input style="width: 95%" v-model="searchObj.keyword" placeholder="用户名/姓名/手机号码"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="操作时间">
              <el-date-picker
                v-model="createTimes"
                type="datetimerange"
                range-separator="至"
                start-placeholder="开始时间"
                end-placeholder="结束时间"
                value-format="yyyy-MM-dd HH:mm:ss"
                style="margin-right: 10px;width: 100%;"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row style="display:flex">
          <el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="fetchData()">搜索</el-button>
          <el-button icon="el-icon-refresh" size="mini" @click="resetData">重置</el-button>
        </el-row>
      </el-form>
    </div>

    <!-- 工具条 -->
    <div class="tools-div">
      <el-button type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>
    </div>

    <!-- 列表 -->
    <el-table
      v-loading="listLoading"
      :data="list"
      stripe
      border
      style="width: 100%;margin-top: 10px;">

      <el-table-column
        label="序号"
        width="70"
        align="center">
        <template slot-scope="scope">
          {{ (page - 1) * limit + scope.$index + 1 }}
        </template>
      </el-table-column>

      <el-table-column prop="username" label="用户名" width="100"/>
      <el-table-column prop="name" label="姓名" width="70"/>
      <el-table-column prop="phone" label="手机" width="120"/>
      <el-table-column prop="postName" label="岗位" width="100"/>
      <el-table-column prop="deptName" label="部门" width="100"/>
      <el-table-column label="所属角色" width="130">
        <template slot-scope="scope">
          <span v-for="item in scope.row.roleList" :key="item.id" style="margin-right: 10px;">{{ item.roleName }}</span>
        </template>
      </el-table-column>
      <el-table-column label="状态" width="80">
        <template slot-scope="scope">
          <el-switch
            v-model="scope.row.status === 1"
            @change="switchStatus(scope.row)">
          </el-switch>
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="创建时间" width="160"/>

      <el-table-column label="操作" width="180" align="center" fixed="right">
        <template slot-scope="scope">
          <el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row.id)" title="修改"/>
          <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除" />
          <el-button type="warning" icon="el-icon-baseball" size="mini" @click="showAssignRole(scope.row)" title="分配角色"/>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页组件 -->
    <el-pagination
      :current-page="page"
      :total="total"
      :page-size="limit"
      :page-sizes="[5, 10, 20, 30, 40, 50, 100]"
      style="padding: 30px 0; text-align: center;"
      layout="sizes, prev, pager, next, jumper, ->, total, slot"
      @current-change="fetchData"
      @size-change="changeSize"
    />

    <el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" >
      <el-form ref="dataForm" :model="sysUser"  label-width="100px" size="small" style="padding-right: 40px;">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="sysUser.username"/>
        </el-form-item>
        <el-form-item v-if="!sysUser.id" label="密码" prop="password">
          <el-input v-model="sysUser.password" type="password"/>
        </el-form-item>
        <el-form-item label="姓名" prop="name">
          <el-input v-model="sysUser.name"/>
        </el-form-item>
        <el-form-item label="手机" prop="phone">
          <el-input v-model="sysUser.phone"/>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
        <el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
      </span>
    </el-dialog>

    <el-dialog title="分配角色" :visible.sync="dialogRoleVisible">
      <el-form label-width="80px">
        <el-form-item label="用户名">
          <el-input disabled :value="sysUser.username"></el-input>
        </el-form-item>

        <el-form-item label="角色列表">
          <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
          <div style="margin: 15px 0;"></div>
          <el-checkbox-group v-model="userRoleIds" @change="handleCheckedChange">
            <el-checkbox v-for="role in allRoles" :key="role.id" :label="role.id">{{role.roleName}}</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
      </el-form>
      <div slot="footer">
        <el-button type="primary" @click="assignRole" size="small">保存</el-button>
        <el-button @click="dialogRoleVisible = false" size="small">取消</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import api from '@/api/system/sysUser'
import roleApi from '@/api/system/sysRole'
const defaultForm = {
  id: '',
  username: '',
  password: '',
  name: '',
  phone: '',
  status: 1
}
export default {
  data() {
    return {
      listLoading: true, // 数据是否正在加载
      list: null, // banner列表
      total: 0, // 数据库中的总记录数
      page: 1, // 默认页码
      limit: 10, // 每页记录数
      searchObj: {}, // 查询表单对象

      createTimes: [],

      dialogVisible: false,
      sysUser: defaultForm,
      saveBtnDisabled: false,

      dialogRoleVisible: false,
      allRoles: [], // 所有角色列表
      userRoleIds: [], // 用户的角色ID的列表
      isIndeterminate: false, // 是否是不确定的
      checkAll: false // 是否全选
    }
  },

  // 生命周期函数:内存准备完毕,页面尚未渲染
  created() {
    console.log('list created......')
    this.fetchData()

    roleApi.findAll().then(response => {
      this.roleList = response.data;
    })
  },

  // 生命周期函数:内存准备完毕,页面渲染成功
  mounted() {
    console.log('list mounted......')
  },

  methods: {
    // 当页码发生改变的时候
    changeSize(size) {
      console.log(size)
      this.limit = size
      this.fetchData(1)
    },

    // 加载banner列表数据
    fetchData(page = 1) {
      debugger
      this.page = page
      console.log('翻页。。。' + this.page)

      if(this.createTimes && this.createTimes.length ==2) {
        this.searchObj.createTimeBegin = this.createTimes[0]
        this.searchObj.createTimeEnd = this.createTimes[1]
      }

      api.getPageList(this.page, this.limit, this.searchObj).then(
        response => {
          //this.list = response.data.list
          this.list = response.data.records
          this.total = response.data.total

          // 数据加载并绑定成功
          this.listLoading = false
        }
      )
    },

    // 重置查询表单
    resetData() {
      console.log('重置查询表单')
      this.searchObj = {}
      this.createTimes = []
      this.fetchData()
    },

    // 根据id删除数据
    removeDataById(id) {
      // debugger
      this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => { // promise
        // 点击确定,远程调用ajax
        return api.removeById(id)
      }).then((response) => {
        this.fetchData(this.page)
        this.$message.success(response.message || '删除成功')
      }).catch(() => {
         this.$message.info('取消删除')
      })
    },

    // -------------
    add(){
      this.dialogVisible = true
      this.sysUser = Object.assign({}, defaultForm)
    },

    edit(id) {
      this.dialogVisible = true
      this.fetchDataById(id)
    },

    fetchDataById(id) {
      api.getById(id).then(response => {
        this.sysUser = response.data
      })
    },

    saveOrUpdate() {
      this.$refs.dataForm.validate(valid => {
        if (valid) {
          this.saveBtnDisabled = true // 防止表单重复提交
          if (!this.sysUser.id) {
            this.saveData()
          } else {
            this.updateData()
          }
        }
      })
    },

    // 新增
    saveData() {
      api.save(this.sysUser).then(response => {
        this.$message.success('操作成功')
        this.dialogVisible = false
        this.fetchData(this.page)
      })
    },

    // 根据id更新记录
    updateData() {
      api.updateById(this.sysUser).then(response => {
        this.$message.success(response.message || '操作成功')
        this.dialogVisible = false
        this.fetchData(this.page)
      })
    },

    showAssignRole (row) {
      this.sysUser = row
      this.dialogRoleVisible = true
      this.getRoles()
    },

    getRoles () {
      roleApi.getRoles(this.sysUser.id).then(response => {
        const {allRolesList, assginRoleList} = response.data
        this.allRoles = allRolesList
        this.userRoleIds = assginRoleList.map(item => item.id)
        this.checkAll = allRolesList.length===assginRoleList.length
        this.isIndeterminate = assginRoleList.length>0 && assginRoleList.length<allRolesList.length
      })
    },

    /*
    全选勾选状态发生改变的监听
    */
    handleCheckAllChange (value) {// value 当前勾选状态true/false
      // 如果当前全选, userRoleIds就是所有角色id的数组, 否则是空数组
      this.userRoleIds = value ? this.allRoles.map(item => item.id) : []
      // 如果当前不是全选也不全不选时, 指定为false
      this.isIndeterminate = false
    },

    /*
    角色列表选中项发生改变的监听
    */
    handleCheckedChange (value) {
      const {userRoleIds, allRoles} = this
      this.checkAll = userRoleIds.length === allRoles.length && allRoles.length>0
      this.isIndeterminate = userRoleIds.length>0 && userRoleIds.length<allRoles.length
    },

    assignRole () {
      let assginRoleVo = {
        userId: this.sysUser.id,
        roleIdList: this.userRoleIds
      }
      roleApi.assignRoles(assginRoleVo).then(response => {
        this.$message.success(response.message || '分配角色成功')
        this.dialogRoleVisible = false
        this.fetchData(this.page)
      })
    },

    switchStatus(row) {
      row.status = row.status === 1 ? 0 : 1
      api.updateStatus(row.id, row.status).then(response => {
        if (response.code) {
          this.$message.success(response.message || '操作成功')
          this.dialogVisible = false
          this.fetchData()
        }
      })
    }
  }
}
</script>

七、菜单管理

在这里插入图片描述

1、菜单管理需求

1、需求描述

不同角色的用户登录后台管理系统拥有不同的菜单权限与功能权限,我们前端是基于:vue-admin-template这个模块开发的,因此我们菜单表设计也必须基于前端模板进行设计。

前端框架vue-admin-template菜单其实就是我们配置的路由:

{
  path: '/system',
  component: Layout,
  meta: {
    title: '系统管理',
    icon: 'el-icon-s-tools'
  },
  alwaysShow: true,
  children: [
    {
      name: 'sysUser',
      path: 'sysUser',
      component: () => import('@/views/system/sysUser/list'),
      meta: {
        title: '用户管理',
        icon: 'el-icon-s-custom'
      },
    },
    {
      path: 'sysRole',
      component: () => import('@/views/system/sysRole/list'),
      meta: {
        title: '角色管理',
        icon: 'el-icon-s-help'
      },
    },
    {
      name: 'sysMenu',
      path: 'sysMenu',
      component: () => import('@/views/system/sysMenu/list'),
      meta: {
        title: '菜单管理',
        icon: 'el-icon-s-unfold'
      },
    },
    {
      path: 'assignAuth',
      component: () => import('@/views/system/sysRole/assignAuth'),
      meta: {
        activeMenu: '/system/sysRole',
        title: '角色授权'
      },
      hidden: true,
    }
  ]
}

因此,菜单表的设计必须满足路由配置的必要信息

2、菜单表设计
1、表结构

在这里插入图片描述

2、示例数据

在这里插入图片描述

3、页面效果

在这里插入图片描述

2、菜单管理

1、菜单管理的CRUD
1、Mapper
package com.atguigu.system.mapper;


import com.atguigu.model.system.SysMenu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {

}

2、service接口及实现

SysUserService接口

package com.atguigu.system.service;


import com.atguigu.model.system.SysMenu;
import com.baomidou.mybatisplus.extension.service.IService;

import java.util.List;

public interface SysMenuService extends IService<SysMenu> {

    /**
     * 菜单树形数据
     * @return
     */
    List<SysMenu> findNodes();

}

SysUserServiceImpl实现

package com.atguigu.system.service.impl;

import com.atguigu.common.execption.GuiguException;
import com.atguigu.common.result.ResultCodeEnum;
import com.atguigu.model.system.SysMenu;
import com.atguigu.system.helper.MenuHelper;
import com.atguigu.system.mapper.SysMenuMapper;
import com.atguigu.system.service.SysMenuService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.List;

@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {

    @Autowired
    private SysMenuMapper sysMenuMapper;

    @Override
    public List<SysMenu> findNodes() {
        //全部权限列表
        List<SysMenu> sysMenuList = this.list();
        if (CollectionUtils.isEmpty(sysMenuList)) return null;

        //构建树形数据
        List<SysMenu> result = MenuHelper.buildTree(sysMenuList);
        return result;
    }

    @Override
    public boolean removeById(Serializable id) {
        int count = this.count(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, id));
        if (count > 0) {
            throw new GuiguException(201,"菜单不能删除");
        }
        sysMenuMapper.deleteById(id);
        return false;
    }

}

添加帮助类
新建:com.atguigu.system.helper.MenuHelper

package com.atguigu.system.helper;


import com.atguigu.model.system.SysMenu;

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

/**
 * <p>
 * 根据菜单数据构建菜单数据
 * </p>
 *
 */
public class MenuHelper {

    /**
     * 使用递归方法建菜单
     * @param sysMenuList
     * @return
     */
    public static List<SysMenu> buildTree(List<SysMenu> sysMenuList) {
        List<SysMenu> trees = new ArrayList<>();
        for (SysMenu sysMenu : sysMenuList) {
            if (sysMenu.getParentId().longValue() == 0) {
                trees.add(findChildren(sysMenu,sysMenuList));
            }
        }
        return trees;
    }

    /**
     * 递归查找子节点
     * @param treeNodes
     * @return
     * 递归方法,用于为给定的菜单项查找并添加子菜单项。
     */
    public static SysMenu findChildren(SysMenu sysMenu, List<SysMenu> treeNodes) {
        sysMenu.setChildren(new ArrayList<SysMenu>());

        for (SysMenu it : treeNodes) {
            if(sysMenu.getId().longValue() == it.getParentId().longValue()) {
                if (sysMenu.getChildren() == null) {
                    sysMenu.setChildren(new ArrayList<>());
                }
                sysMenu.getChildren().add(findChildren(it,treeNodes));
            }
        }
        return sysMenu;
    }
}
3、controller
package com.atguigu.system.controller;

import com.atguigu.common.result.Result;
import com.atguigu.model.system.SysMenu;
import com.atguigu.system.service.SysMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Api(tags = "菜单管理")
@RestController
@RequestMapping("/admin/system/sysMenu")
public class SysMenuController {

    @Autowired
    private SysMenuService sysMenuService;

    @ApiOperation(value = "获取菜单")
    @GetMapping("findNodes")
    public Result findNodes() {
        List<SysMenu> list = sysMenuService.findNodes();
        return Result.ok(list);
    }

    @ApiOperation(value = "新增菜单")
    @PostMapping("save")
    public Result save(@RequestBody SysMenu permission) {
        sysMenuService.save(permission);
        return Result.ok();
    }

    @ApiOperation(value = "修改菜单")
    @PutMapping("update")
    public Result updateById(@RequestBody SysMenu permission) {
        sysMenuService.updateById(permission);
        return Result.ok();
    }

    @ApiOperation(value = "删除菜单")
    @DeleteMapping("remove/{id}")
    public Result remove(@PathVariable Long id) {
        sysMenuService.removeById(id);
        return Result.ok();
    }

}
4、knife4j测试

knife4j测试

2、用户管理前端实现
1、添加路由

修改 src/router/index.js 文件

{
  name: 'sysMenu',
  path: 'sysMenu',
  component: () => import('@/views/system/sysMenu/list'),
  meta: {
    title: '菜单管理',
    icon: 'el-icon-s-unfold'
  },
}
2、定义基础api

创建文件 src/api/system/sysMenu.js

import request from '@/utils/request'

/*
菜单管理相关的API请求函数
*/
const api_name = '/admin/system/sysMenu'

export default {

  /*
  获取权限(菜单/功能)列表
  */
  findNodes() {
    return request({
      url: `${api_name}/findNodes`,
      method: 'get'
    })
  },

  /*
  删除一个权限项
  */
  removeById(id) {
    return request({
      url: `${api_name}/remove/${id}`,
      method: "delete"
    })
  },

  /*
  保存一个权限项
  */
  save(sysMenu) {
    return request({
      url: `${api_name}/save`,
      method: "post",
      data: sysMenu
    })
  },

  /*
  更新一个权限项
  */
  updateById(sysMenu) {
    return request({
      url: `${api_name}/update`,
      method: "put",
      data: sysMenu
    })
  }
}
3、实现页面功能

创建src/views/system/sysMenu/list.vue

<template>
  <div class="app-container">

    <!-- 工具条 -->
    <div class="tools-div">
      <el-button type="success" icon="el-icon-plus" size="mini" @click="add()">添 加</el-button>
    </div>
    <el-table
      :data="sysMenuList"
      style="width: 100%;margin-bottom: 20px;margin-top: 10px;"
      row-key="id"
      border
      :default-expand-all="false"
      :tree-props="{children: 'children'}">

      <el-table-column prop="name" label="菜单名称" width="160"/>
      <el-table-column label="图标">
        <template slot-scope="scope">
          <i :class="scope.row.icon"></i>
        </template>
      </el-table-column>
      <el-table-column prop="perms" label="权限标识" width="160"/>
      <el-table-column prop="path" label="路由地址" width="120"/>
      <el-table-column prop="component" label="组件路径" width="160"/>
      <el-table-column prop="sortValue" label="排序" width="60"/>
      <el-table-column label="状态" width="80">
        <template slot-scope="scope">
          <el-switch
            v-model="scope.row.status === 1" disabled="true">
          </el-switch>
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="创建时间" width="160"/>
      <el-table-column label="操作" width="180" align="center" fixed="right">
        <template slot-scope="scope">
          <el-button type="success" v-if="scope.row.type !== 2" icon="el-icon-plus" size="mini" @click="add(scope.row)" title="添加下级节点"/>
          <el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row)" title="修改"/>
          <el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除" :disabled="scope.row.children.length > 0"/>
        </template>
      </el-table-column>
    </el-table>

    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="40%" >
      <el-form ref="dataForm" :model="sysMenu" label-width="150px" size="small" style="padding-right: 40px;">
        <el-form-item label="上级部门" v-if="sysMenu.id === ''">
          <el-input v-model="sysMenu.parentName" disabled="true"/>
        </el-form-item>
        <el-form-item label="菜单类型" prop="type">
          <el-radio-group v-model="sysMenu.type" :disabled="typeDisabled">
            <el-radio :label="0" :disabled="type0Disabled">目录</el-radio>
            <el-radio :label="1" :disabled="type1Disabled">菜单</el-radio>
            <el-radio :label="2" :disabled="type2Disabled">按钮</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="菜单名称" prop="name">
          <el-input v-model="sysMenu.name"/>
        </el-form-item>
        <el-form-item label="图标" prop="icon" v-if="sysMenu.type !== 2">
          <el-select v-model="sysMenu.icon" clearable>
            <el-option v-for="item in iconList" :key="item.class" :label="item.class" :value="item.class">
            <span style="float: left;">
             <i :class="item.class"></i>  <!-- 如果动态显示图标,这里添加判断 -->
            </span>
              <span style="padding-left: 6px;">{{ item.class }}</span>
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="排序">
          <el-input-number v-model="sysMenu.sortValue" controls-position="right" :min="0" />
        </el-form-item>
        <el-form-item prop="path">
              <span slot="label">
                <el-tooltip content="访问的路由地址,如:`sysUser`" placement="top">
                <i class="el-icon-question"></i>
                </el-tooltip>
                路由地址
              </span>
          <el-input v-model="sysMenu.path" placeholder="请输入路由地址" />
        </el-form-item>
        <el-form-item prop="component" v-if="sysMenu.type !== 0">
              <span slot="label">
                <el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
                <i class="el-icon-question"></i>
                </el-tooltip>
                组件路径
              </span>
          <el-input v-model="sysMenu.component" placeholder="请输入组件路径" />
        </el-form-item>
        <el-form-item v-if="sysMenu.type === 2">
          <el-input v-model="sysMenu.perms" placeholder="请输入权限标识" maxlength="100" />
          <span slot="label">
                <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(hasAuthority('bnt.sysRole.list'))" placement="top">
                <i class="el-icon-question"></i>
                </el-tooltip>
                权限字符
              </span>
        </el-form-item>
        <el-form-item label="状态" prop="type">
          <el-radio-group v-model="sysMenu.status">
            <el-radio :label="1">正常</el-radio>
            <el-radio :label="0">停用</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
        <el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>


<script>
import api from '@/api/system/sysMenu'
const defaultForm = {
  id: '',
  parentId: '',
  name: '',
  type: 0,
  path: '',
  component: '',
  perms: '',
  icon: '',
  sortValue: 1,
  status: 1
}
export default {
  // 定义数据
  data() {
    return {
      sysMenuList: [],
      expandKeys: [], // 需要自动展开的项

      typeDisabled: false,
      type0Disabled: false,
      type1Disabled: false,
      type2Disabled: false,
      dialogTitle: '',

      dialogVisible: false,
      sysMenu: defaultForm,
      saveBtnDisabled: false,

      iconList: [
        {
          class: "el-icon-s-tools",
        },
        {
          class: "el-icon-s-custom",
        },
        {
          class: "el-icon-setting",
        },
        {
          class: "el-icon-user-solid",
        },
        {
          class: "el-icon-s-help",
        },
        {
          class: "el-icon-phone",
        },
        {
          class: "el-icon-s-unfold",
        },
        {
          class: "el-icon-s-operation",
        },
        {
          class: "el-icon-more-outline",
        },
        {
          class: "el-icon-s-check",
        },
        {
          class: "el-icon-tickets",
        },
        {
          class: "el-icon-s-goods",
        },
        {
          class: "el-icon-document-remove",
        },
        {
          class: "el-icon-warning",
        },
        {
          class: "el-icon-warning-outline",
        },
        {
          class: "el-icon-question",
        },
        {
          class: "el-icon-info",
        }
      ]
    }
  },

  // 当页面加载时获取数据
  created() {
    this.fetchData()
  },

  methods: {
    // 调用api层获取数据库中的数据
    fetchData() {
      console.log('加载列表')
      api.findNodes().then(response => {
        this.sysMenuList = response.data
        console.log(this.sysMenuList)
      })
    },

    // 根据id删除数据
    removeDataById(id) {
      // debugger
      this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => { // promise
        // 点击确定,远程调用ajax
        return api.removeById(id)
      }).then((response) => {
        this.fetchData()
        this.$message({
          type: 'success',
          message: '删除成功!'
        })
      }).catch(() => {
         this.$message.info('取消删除')
      })
    },

    // -------------
    add(row){
      debugger
      this.typeDisabled = false
      this.dialogTitle = '添加下级节点'
      this.dialogVisible = true

      this.sysMenu = Object.assign({}, defaultForm)
      this.sysMenu.id = ''
      if(row) {
        this.sysMenu.parentId = row.id
        this.sysMenu.parentName = row.name
        //this.sysMenu.component = 'ParentView'
        if(row.type === 0) {
          this.sysMenu.type = 1
          this.typeDisabled = false
          this.type0Disabled = false
          this.type1Disabled = false
          this.type2Disabled = true
        } else if(row.type === 1) {
          this.sysMenu.type = 2
          this.typeDisabled = true
        }
      } else {
        this.dialogTitle = '添加目录节点'
        this.sysMenu.type = 0
        this.sysMenu.component = 'Layout'
        this.sysMenu.parentId = 0
        this.typeDisabled = true
      }
    },

    edit(row) {
      debugger
      this.dialogTitle = '修改节点'
      this.dialogVisible = true

      this.sysMenu = Object.assign({}, row)
      this.typeDisabled = true
    },

    saveOrUpdate() {
      if(this.sysMenu.type === 0 && this.sysMenu.parentId !== 0) {
        this.sysMenu.component = 'ParentView'
      }
      this.$refs.dataForm.validate(valid => {
        if (valid) {
          this.saveBtnDisabled = true // 防止表单重复提交
          if (!this.sysMenu.id) {
            this.saveData()
          } else {
            this.updateData()
          }
        }
      })
    },

    // 新增
    saveData() {
      api.save(this.sysMenu).then(response => {
        this.$message.success(response.message || '操作成功')
        this.dialogVisible = false
        this.fetchData(this.page)
      })
    },

    // 根据id更新记录
    updateData() {
      api.updateById(this.sysMenu).then(response => {
        this.$message.success(response.message || '操作成功')
        this.dialogVisible = false
        this.fetchData()
      })
    }
  }
}
</script>

3、给角色分配权限

1、给角色分配权限
1、接口分析

1、进入分配页面:获取全部菜单及按钮,选中已选复选框,进行页面展示
2、保存分配权限:删除之前分配的权限和保存现在分配的权限

2、controller方法

操作类:SysMenuController

@ApiOperation(value = "根据角色获取菜单")
@GetMapping("toAssign/{roleId}")
public Result toAssign(@PathVariable Long roleId) {
    List<SysMenu> list = sysMenuService.findSysMenuByRoleId(roleId);
    return Result.ok(list);
}

@ApiOperation(value = "给角色分配权限")
@PostMapping("/doAssign")
public Result doAssign(@RequestBody AssignMenuVo assignMenuVo) {
    sysMenuService.doAssign(assignMenuVo);
    return Result.ok();
}
3、service接口

操作类:SysMenuService

/**
 * 根据角色获取授权权限数据
 * @return
 */
List<SysMenu> findSysMenuByRoleId(Long roleId);

/**
 * 保存角色权限
 * @param  assginMenuVo
 */
void doAssign(AssginMenuVo assginMenuVo);
4、service接口实现

1、操作类:SysMenuServiceImpl

@Override
public List<SysMenu> findSysMenuByRoleId(Long roleId) {
    //全部权限列表
    List<SysMenu> allSysMenuList = this.list(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getStatus, 1));

    //根据角色id获取角色权限
    List<SysRoleMenu> sysRoleMenuList = sysRoleMenuMapper.selectList(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId));
    //转换给角色id与角色权限对应Map对象
    List<Long> menuIdList = sysRoleMenuList.stream().map(e -> e.getMenuId()).collect(Collectors.toList());

    allSysMenuList.forEach(permission -> {
        if (menuIdList.contains(permission.getId())) {
            permission.setSelect(true);
        } else {
            permission.setSelect(false);
        }
    });

    List<SysMenu> sysMenuList = MenuHelper.buildTree(allSysMenuList);
    return sysMenuList;
}

@Transactional
@Override
public void doAssign(AssginMenuVo assginMenuVo) {
    sysRoleMenuMapper.delete(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, assginMenuVo.getRoleId()));

    for (Long menuId : assginMenuVo.getMenuIdList()) {
        if (StringUtils.isEmpty(menuId)) continue;
        SysRoleMenu rolePermission = new SysRoleMenu();
        rolePermission.setRoleId(assginMenuVo.getRoleId());
        rolePermission.setMenuId(menuId);
        sysRoleMenuMapper.insert(rolePermission);
    }
}
2、前端实现
2.1、添加路由

修改 src/router/index.js 文件

{
  path: 'assignAuth',
  component: () => import('@/views/system/sysRole/assignAuth'),
  meta: {
    activeMenu: '/system/sysRole',
    title: '角色授权'
  },
  hidden: true,
}
2.2、角色列表添加按钮及方法

在这里插入图片描述

<el-button type="warning" icon="el-icon-baseball" size="mini" @click="showAssignAuth(scope.row)" title="分配权限"/>

添加方法

showAssignAuth(row) {
  this.$router.push('/system/assignAuth?id='+row.id+'&roleName='+row.roleName);
}
2.3、添加api

创建文件 src/api/system/sysMenu.js

/*
查看某个角色的权限列表
*/
toAssign(roleId) {
  return request({
    url: `${api_name}/toAssign/${roleId}`,
    method: 'get'
  })
},

/*
给某个角色授权
*/
doAssign(assginMenuVo) {
  return request({
    url: `${api_name}/doAssign`,
    method: "post",
    data: assginMenuVo
  })
}
2.4、实现页面功能

创建src/views/system/sysMenu/assignAuth.vue

<template>
  <div class="app-container">
    <div style="padding: 20px 20px 0 20px;">
      授权角色:{{ $route.query.roleName }}
    </div>
    <el-tree
      style="margin: 20px 0"
      ref="tree"
      :data="sysMenuList"
      node-key="id"
      show-checkbox
      default-expand-all
      :props="defaultProps"
    />
    <div style="padding: 20px 20px;">
      <el-button :loading="loading" type="primary" icon="el-icon-check" size="mini" @click="save">保存</el-button>
      <el-button @click="$router.push('/system/sysRole')" size="mini" icon="el-icon-refresh-right">返回</el-button>
    </div>
  </div>
</template>
<script>
  import api from '@/api/system/sysMenu'
  export default {
    name: 'roleAuth',

    data() {
      return {
        loading: false, // 用来标识是否正在保存请求中的标识, 防止重复提交
        sysMenuList: [], // 所有
        defaultProps: {
          children: 'children',
          label: 'name'
        },
      };
    },

    created() {
      this.fetchData()
    },

    methods: {
      /*
      初始化
      */
      fetchData() {
        const roleId = this.$route.query.id
        api.toAssign(roleId).then(result => {
          const sysMenuList = result.data
          this.sysMenuList = sysMenuList
          const checkedIds = this.getCheckedIds(sysMenuList)
          console.log('getPermissions() checkedIds', checkedIds)
          this.$refs.tree.setCheckedKeys(checkedIds)
        })
      },

      /*
      得到所有选中的id列表
      */
      getCheckedIds (auths, initArr = []) {
        return auths.reduce((pre, item) => {
          if (item.select && item.children.length === 0) {
            pre.push(item.id)
          } else if (item.children) {
            this.getCheckedIds(item.children, initArr)
          }
          return pre
        }, initArr)
      },

      /*
      保存权限列表
      */
      save() {
        debugger
        //获取到当前子节点
        //const checkedNodes = this.$refs.tree.getCheckedNodes()
        //获取到当前子节点及父节点
        const allCheckedNodes = this.$refs.tree.getCheckedNodes(false, true);
        let idList = allCheckedNodes.map(node => node.id);
        console.log(idList)
        let assginMenuVo = {
          roleId: this.$route.query.id,
          menuIdList: idList
        }
        this.loading = true
        api.doAssign(assginMenuVo).then(result => {
          this.loading = false
          this.$message.success(result.$message || '分配权限成功')
          this.$router.push('/system/sysRole');
        })
      }
    }
  };
</script>

至此,云尚办公系统前期结束。开启下面内容,要求把前面的基础知识掌握牢固!

  • 47
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
随着信息化的发展,电子人力资源管理系统(e-hr)成为了企业信息化建设的重要组成部分。本文针对ssm框架(Spring + SpringMVC + MyBatis)下的e-hr管理系统进行了设计与实现。 系统采用了B/S(浏览器/服务器)模式,前端采用HTML、CSS、JavaScript等技术,后端采用Spring框架作为控制反转的容器和AOP(面向切面编程)的框架,SpringMVC框架作为请求的分发器,MyBatis框架作为持久化框架,实现了基于Web的电子人力资源管理。 系统主要功能包括: 1.用户管理:实现新建、删除、修改、查询用户信息的功能。 2.部门管理:实现部门的管理和查询功能。 3.员工管理:实现员工的管理和查询功能。 4.考勤管理:实现考勤记录的管理和查询功能。 5.薪酬管理:实现薪酬计算和统计功能。 6.培训管理:实现培训计划制定和实施情况的查询。 7.绩效管理:实现绩效考核的制定和绩效报告的查询。 在具体实现过程中,我采用了Maven作为项目管理工具,并使用Git作为版本控制工具,保证了项目组合作的高效性。此外,我还在代码编写阶段,注重使用了面向对象的编程思想,提高了代码的可扩展性和可维护性。 综上所述,该e-hr管理系统基于ssm框架,以其良好的组合、普遍的适用性和效率较高的特点,实现了基于Web的电子人力资源管理,为企业管理和人力资源管理提供了便利,同时提高了信息化运营的效率和质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值