云尚办公项目-用户与角色管理

1. 项目介绍

云尚办公系统是一套自动办公系统,系统主要包含:管理端和员工端
管理端包含:权限管理、审批管理、公众号菜单管理
员工端采用微信公众号操作,包含:办公审批、微信授权登录、消息推送等功能
项目服务器端架构:SpringBoot + MyBatisPlus + SpringSecurity + Redis + Activiti+ MySQL
前端架构:vue-admin-template + Node.js + Npm + Vue + ElementUI + Axios

1.1 开发环境说明

工具版本
后台SpringBoot 2.3.6 + MyBatisPlus 3.4.1
数据库MySQL 8.0.27
开发工具IDEA 2022.3
前端工具vsCode
前端技术Vue + ElementUI + Node.js 14.15.0

2. 搭建后台环境

2.1 项目模块图

在这里插入图片描述

2.2 开始搭建

1. 搭建父工程:guiigu-oa-parent
  1. 推荐直接使用springboot进行父工程搭建用于方便选择SpringBoot版本,注意jdk为1.8
    在这里插入图片描述
    在这里插入图片描述
2. 生成相关model
  1. 相关model结构
    在这里插入图片描述
  2. 使用最普通的model进行生成,不需要使用springboot
    在这里插入图片描述

2.3 搭建依赖关系

1. guigu-oa-parent父模块管理依赖版本
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gdhd</groupId>
    <artifactId>guigu-oa-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>guigu-oa-parent</name>
    <description>guigu-oa-parent</description>
    <modules>
        <module>common</module>
        <module>common/common-util</module>
        <module>common/service-util</module>
        <module>common/spirng-security</module>
        <module>model</module>
        <module>service-oa</module>
    </modules>
    <properties>
        <java.version>1.8</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>
    <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模块

common公共模块

<?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>
    <parent>
        <groupId>com.gdhd</groupId>
        <artifactId>guigu-oa-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>common</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>
3. common-util模块
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided </scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
    </dependencies>
4. service-util模块
<dependency>
<!-- com.gdhd 是我搭建项目时候groupld的内容,需要更换你的   -->
        <groupId>com.gdhd</groupId>
        <artifactId>common-util</artifactId>
        <version>0.0.1-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>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>net.minidev</groupId>
            <artifactId>json-smart</artifactId>
            <version>2.4.11</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>
5. model模块
<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>
6 service-oa模块
<dependencies>
        <dependency>
        <!-- com.gdhd 是我搭建项目时候groupld的内容,需要更换你的   -->
            <groupId>com.gdhd</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.gdhd</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>

2.4 搭建启动类

  • 在service-oa模块中创建启动类
  • 目录文件为:src\main\java\com\gdhd\auth
@SpringBootApplication
@ComponentScan("com.gdhd")
@MapperScan("com.gdhd.*.mapper")
public class ServiceAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceAuthApplication.class, args);
    }
}


3. 数据库与后台环境连接

3.1. 搭建数据库与实体类

1. 数据库

直接到后面资料进行获取

2. 实体类

也是直接到后面资料获取
实体类,放在工程Model模块中

3. 代码生成器

关于每一个数据库和实体类的后台环境直接使用代码生成器进行构建

package Code;


import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

public class CodeGet {

    public static void main(String[] args) {

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        //你项目的位置
        gc.setOutputDir("F:\\code\\guigu-oa-parent\\service-oa"+"/src/main/java");

        gc.setServiceName("%sService");	//去掉Service接口的首字母I
        gc.setAuthor("gdhd");
        gc.setOpen(false);
        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/guigu-oa?serverTimezone=GMT%2B8&useSSL=false");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.gdhd");
        pc.setModuleName("auth"); //模块名
        pc.setController("controller");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        //数据表
        strategy.setInclude("sys_role");

        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

        mpg.setStrategy(strategy);

        // 6、执行
        mpg.execute();
    }
}

3.2. 配置后台与数据库进行连接

配置MySQL数据库的相关配置及Mybatis-Plus日志
操作在:service-oa模块

  1. 创建application.yml文件
spring:
  application:
    name: service-oa
  profiles:
    active: dev
  1. 创建application-dev.yml文件
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&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  mvc:
#     SpringBoot2.6.x与Swagger2 3.0.0版本冲突解决
    pathmatch:
      matching-strategy: ant_path_matcher

注意:连接地址url
3. MySQL5.7版本的url:jdbc:mysql://localhost:3306/guigu-oa?characterEncoding=utf-8&useSSL=false
4. MySQL8.0版本的url:jdbc:mysql://localhost:3306/guigu-oa?
serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
5. 否则运行测试用例报告如下错误:java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä

3.3 配置mp的分页插件

  • 模块:service-util模块
  • 位置:com.gdhd.common.config.mp
package com.gdhd.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.gdhd.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);
    }
}

4. 配置前端工程

4.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

4.2 vue-admin-template

1. 简介

vue-admin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版),可作为模板进行二次开发。
GitHub地址:https://github.com/PanJiaChen/vue-admin-template
建议:你可以在 vue-admin-template 的基础上进行二次开发,把 vue-element-admin当做工具箱,想要什么功能或者组件就去 vue-element-admin 那里复制过来。

2. 安装
#修改项目名称 vue-admin-template 改为 guigu-auth-ui
# 解压压缩包
# 进入目录
cd guigu-auth-ui
# 安装依赖
npm install
# 启动。执行后,浏览器自动弹出并访问http://localhost:9528/
npm run dev
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相关配置(: 代理服务器)
4. 运行前端工程
  1. vsCode打开终端输入指令:npm run dev
    在这里插入图片描述

  2. 访问:http://localhost:9528/即可弹出登录界面
    在这里插入图片描述

  3. 登录以后的界面在这里插入图片描述

5. 修改路由

位置:src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'

/**
 * Note: sub-menu only appear when route children.length >= 1
 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
 *
 * hidden: true                   if set true, item will not show in the sidebar(default is false)
 * alwaysShow: true               if set true, will always show the root menu
 *                                if not set alwaysShow, when item has more than one children route,
 *                                it will becomes nested mode, otherwise not show the root menu
 * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 * meta : {
    roles: ['admin','editor']    control the page roles (you can set multiple roles)
    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
  }
 */

/**
 * constantRoutes
 * a base page that does not have permission requirements
 * all roles can be accessed
 */
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  },

 

  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]

const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

  • 登录以后的界面发生改变
    在这里插入图片描述
6. 关闭语法严格
  • vue.config.js文件
  lintOnSave: false,//process.env.NODE_ENV === 'development',

5. 后台补充说明

5.1 定义统一返回结果对象

项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一, 使前端(iOS Android, Web)对数据的操作更一致、轻松。

一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。但是一般会包含状态码、返回消息、数据这几部分内容
例如,我们的系统要求返回的基本数据格式如下:

列表:

{
  "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
}
1. 定义返回结果对象
  • 操作模块:common-util
package com.gdhd.common.result;

import lombok.Data;

@Data
public class Result<T> {
    //返回码
    private Integer code;

    //返回消息
    private String message;

    //返回数据
    private T data;

    public Result(){}

    // 返回数据
    protected static <T> Result<T> build(T data) {
        Result<T> result = new Result<T>();
        if (data != null)
            result.setData(data);
        return result;
    }

    public static <T> Result<T> build(T body, Integer code, String message) {
        Result<T> result = build(body);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }

    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }

    public static<T> Result<T> ok(){
        return Result.ok(null);
    }

    /**
     * 操作成功
     * @param data  baseCategory1List
     * @param <T>
     * @return
     */
    public static<T> Result<T> ok(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }

    public static<T> Result<T> fail(){
        return Result.fail(null);
    }

    /**
     * 操作失败
     * @param data
     * @param <T>
     * @return
     */
    public static<T> Result<T> fail(T data){
        Result<T> result = build(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. 统一返回结果状态信息类
package com.gdhd.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;
    }


}

5.2 Kinfe4j

文档地址:https://doc.xiaominfo.com/

knife4j是为Java MVC框架集成Swagger生成Api文

1. Swagger介绍

前后端分离开发模式中,api文档是最好的沟通方式。

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

1、及时性 (接口变更后,能够及时准确地通知相关前后端开发人员)

2、规范性 (并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息)

3、一致性 (接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧)

4、可测性 (直接在接口文档上进行测试,以方便理解业务)

2. 添加依赖

操作模块:service-uitl

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
3. 添加knife4j配置类
package com.gdhd.common.config.kinfe4j;
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;
@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.gdhd"))
                .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();
    }

}

6. 实现登录&退出登录

6.1 前端操作

1. 前后端跨域配置

前端中进行配置:vue.config.js
+ 注释掉mock接口配置
+ 配置代理转发请求到目标接口

// before: require('./mock/mock-server.js')
proxy: {
  '/dev-api': { // 匹配所有以 '/dev-api'开头的请求路径
    target: 'http://localhost:8800',
    changeOrigin: true, // 支持跨域
    pathRewrite: { // 重写路径: 去掉路径中开头的'/dev-api'
      '^/dev-api': ''
    }
  }
}
2. 携带登录数据
  • 请求自动携带token,且请求头名称为token
  • 前端操作在:utils/request.js中
const token = store.getters.token
if (token) {
  // let each request carry token
  // ['X-Token'] is a custom headers key
  // please modify it according to the actual situation
  // config.headers['X-Token'] = getToken()
  config.headers['token'] = token
}
//在这个文件中,修改!==200
if (res.code !== 200) {
  Message({
    message: res.message || 'Error',
    type: 'error',
    duration: 5 * 1000
  })
  return Promise.reject(new Error(res.message || 'Error'))
} else {
  return res
}
3. 修改api用于与后端交互
  • 前端操作在:api/user.js文件
import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/admin/system/index/login',
    method: 'post',
    data
  })
}

export function getInfo(token) {
  return request({
    url: '/admin/system/index/info',
    method: 'get',
    params: { token }
  })
}

export function logout() {
  return request({
    url: '/admin/system/index/logout',
    method: 'post'
  })
}

6.2 后端服务器增加接口

  • service-oa模块
  • 目录结构为:src\mian\java\com\gdhd\auth\controll\IndexController.java
package com.gdhd.auth.controller;

import com.gdhd.common.result.Result;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@Api(tags = "后台登录管理")
@RestController
@RequestMapping("/admin/system/index")
public class IndexController {


    /**
     * 登录
     * @return
     */
    @PostMapping("login")
    public Result login() {
        Map<String, Object> map = new HashMap<>();
        map.put("token","admin");
        return Result.ok(map);
    }
    /**
     * 获取用户信息
     * @return
     */
    @GetMapping("info")
    public Result info() {
        Map<String, Object> map = new HashMap<>();
        map.put("roles","[admin]");
        map.put("name","admin");
        map.put("avatar","https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
        return Result.ok(map);
    }
    /**
     * 退出
     * @return
     */
    @PostMapping("logout")
    public Result logout(){
        return Result.ok();
    }

}
  • 注意:后端只是简单实现,并没有访问数据库操作等

7. 角色功能实现

7.1 前端添加角色功能页面

1. 添加路由
  • 在src/router/index.jx文件添加路由
{
    path: '/system',
    component: Layout,
    meta: {
      title: '系统管理',
      icon: 'el-icon-s-tools'
    },
    alwaysShow: true,
    children: [
      {
        path: 'sysRole',
        component: () => import('@/views/system/sysRole/list'),
        meta: {
          title: '角色管理',
          icon: 'el-icon-s-help'
        },
      }
    ]
  },
2. 定义api
  • 创建文件:src/api/system/sysRole.js
//用于向后端发送请求
import request from '@/utils/request'

const api_name = '/admin/system/sysRole'

export default {
}
3. 添加前端页面
  • 在src/views文件夹下创建以下文件夹和文件
  • 创建文件夹:system/sysRole
  • 创建文件:list.vue
//前端页面
<template>
  <div class="app-container">
    用户列表
    <div class="app-container">
      <!--查询表单-->
      <div class="search-div">
        <el-form label-width="70px" size="small">
          <el-row>
            <el-col :span="24">
              <el-form-item label="角色名称">
                <el-input
                  style="width: 100%"
                  v-model="searchObj.roleName"
                  placeholder="角色名称"
                ></el-input>
              </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
            >
            <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>
          </el-row>
        </el-form>
      </div>
      <!-- 表格 -->
      <el-table
        v-loading="listLoading"
        :data="list"
        stripe
        border
        style="width: 100%; margin-top: 10px"
        @selection-change="handleSelectionChange"
      >
        <el-table-column type="selection" />

        <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="roleName" label="角色名称" />
        <el-table-column prop="roleCode" label="角色编码" />
        <el-table-column prop="createTime" label="创建时间" width="160" />
        <el-table-column label="操作" width="200" align="center">
          <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="showAssignAuth(scope.row)"
              title="分配权限"
            />
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页组件 -->
      <el-pagination
        :current-page="page"
        :total="total"
        :page-size="limit"
        style="padding: 30px 0; text-align: center"
        layout="total, prev, pager, next, jumper"
        @current-change="fetchData"
      />
      <el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%">
        <el-form
          ref="dataForm"
          :model="sysRole"
          label-width="150px"
          size="small"
          style="padding-right: 40px"
        >
          <input type="hidden" v-model="sysRole.id" />
          <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>
    </div>
  </div>
</template>
<script>
import api from "@/api/system/sysRole";
export default {
  // vue代码结构
  data() {
    return {
      list: [], // 列表
      total: 0, // 总记录数
      page: 1, // 页码
      limit: 10, // 每页记录数
      searchObj: {}, // 查询条件
      multipleSelection: [], // 批量删除选中的记录列表
      dialogVisible: false, //是否弹框
      sysRole: {
        id: null,
        roleName: "",
        roleCode: "",
      },
      dialogVisible: false,
      selections: [], // 批量删除选中的记录列表
    };
  },
  //在页面渲染之前
};
</script>


  • 添加以后,页面如下
    在这里插入图片描述

7.2 分页显示角色列表

1. 前端发送请求
  1. 增加api
/*
  获取角色分页列表(带搜索)
  */
  //page: 页码,limit:每页记录数,searchObj:搜索条件
  getPageList(page, limit, searchObj) {
    return request({
      url: `${api_name}/${page}/${limit}`,
      method: 'get',
      params: searchObj
    })
  }
  1. 页面发送请求
// 页面渲染成功后获取数据
  created() {
    this.fetchData()
  },
  // 定义方法
  methods: {
  //如果没有参数,则代表页码为1
    fetchData(current=1) {
      this.page = current
      // 调用api
      api.getPageList(this.page, this.limit, this.searchObj).then(response => {
      //拿到数据后,渲染页面
        this.list = response.data.records
        this.total = response.data.total
      })
    },
  }
2. 后端代码
  • 模块:service-oa模块
  • controller层
@Api("角色功能")
@RestController
@RequestMapping("/admin/system/sysRole")
public class SysRoleController {
    @ApiOperation("条件分页查询")
    @GetMapping("{page}/{limit}")
    //@PathVariable Long page, 当前页
    //@PathVariable Long limit,每页x显示记录数
    //SysRoleQueryVo 每页条件对象
    public Result pageQueryRole(@PathVariable Long page, @PathVariable Long limit, SysRoleQueryVo sysRoleQueryVo ){
        //调用Service方法实现
        //1. 创建一个Page对象,传递分页相关参数
        Page<SysRole> pageParam=new Page<>(page,limit);
        //2. 封装条件 ,判断条件是否为空,不为空进行封装
        LambdaQueryWrapper<SysRole> wrapper=new LambdaQueryWrapper<>();
        String roleName = sysRoleQueryVo.getRoleName();
        //如果条件不等于空
        if (!StringUtils.isEmpty(roleName)){
            //封装
            wrapper.like(SysRole::getRoleName,roleName);
        }
        Page<SysRole> page1 = sysRoleService.page(pageParam, wrapper);
        return Result.ok(page1);
    }
 }

7.3 实现角色添加功能

1. 分析
  • 当点击页面添加,弹出添加修改窗口
2. 前端操作
  1. 添加对应方法
   add(){
      //弹出添加修改窗口
      this.dialogVisible=true
    },
    //当点击弹出修改窗口的确定按钮,判断有没有id
    saveOrUpdate(){
      if(!this.sysRole.id){
        //如果没有id则代表是添加功能
        this.saveData()
        
      }else{
      //如果有id则代表是修改功能
        this.updateData()
      }
    },
    saveData(){
   //调动api 
      api.save(this.sysRole).then(response=>{
          this.$message.success(response.message || '操作成功')
          //关闭窗口
          this.dialogVisible=false
          this.sysRole={}
          //重新显示列表
          this.fetchData(this.page)
        })
    },
  1. 添加api
  save(role) {
    return request({
      url: `${api_name}/save`,
      method: 'post',
      data: role
    })
  },
3. 后台代码
 @ApiOperation("添加角色")
    @PostMapping("save")
    public Result save(@RequestBody  SysRole role){
        boolean is_success = sysRoleService.save(role);
        if (is_success){
            return Result.ok();
        }else {
            return Result.fail();
        }
    }

7.4 实现角色修改功能

1. 分析
  • 当点击某一条数据修改功能后,将该条的数据id给后台进行查询,查询成功以后把数据回显到修改页面中
  • 确定修改以后,将数据传递给后端进行修改。然后关闭修改窗口,重新加载数据
2. 前端操作
  1. 相关方法
 //数据回显
    edit(id){
      //当点击修改的时候,那就根据id进行查询进行数据回显
      api.getId(id).then(response=>{
        this.dialogVisible=true
        this.sysRole=response.data
        
      })

    },
    //修改
    updateData(){
      console.log(this.sysRole)
      api.update(this.sysRole).then(response=>{
        this.$message.success(response.message || '操作成功')
          //关闭窗口
          this.dialogVisible=false
          //重新显示列表
          this.fetchData(this.page)
      })
    },
  1. 增加api
//根据id查询
 getId(id){
    return request({
      url:`${api_name}/getId/${id}`,
      method:'get',
    })
  },
  //修改
  update(role){
    return request({
      url:`${api_name}/update`,
      method:'put',
      data:role
    })
  },
3. 后端代码
@ApiOperation("根据id查询角色")
    @GetMapping("getId/{id}")
    public Result getId(@PathVariable Long id){
        //根据id进行查询
        SysRole role = sysRoleService.getById(id);
        return Result.ok(role);
    }
    @ApiOperation("修改角色")
    @PutMapping("update")
    //put需要从请求头中获取
    public Result update(@RequestBody SysRole role){
        System.out.println(role);
        sysRoleService.updateById(role);
        return Result.ok();
    }

7.5 实现删除操作

1. 分析

当点击某条数据的删除功能时,根据该条数据的id进行删除

2. 前端操作
  1. 相关方法
 //根据id删除
      removeDataById(id){
      if(confirm("你确定要删除吗?")){
        api.removeById(id).then(response=>{
          alert(this.list)
          this.fetchData(this.page)
          alert(this.list)
        })
      }
    },
  1. 增加api
  removeById(id){
    return request({
      url:`${api_name}/removeById/${id}`,
      method:'delete'
    })
  },
3. 后端代码
   @ApiOperation("根据Id进行删除")
    @DeleteMapping("removeById/{id}")
    public Result removeById(@PathVariable Long id){
        sysRoleService.removeById(id);
        return Result.ok();
    }

7.6 实现批量删除

1. 分析

当选择多条数据,然后点击批量删除按钮,对选中的数据进行删除。

  • 点击批量删除按钮,需要判断选择数据列表是否有数据,当没有数据提示需要选择数据
  • 当选择数据的时候,选择数据列表需要进行更新操作
2. 前端代码实现
  1. 相关方法
 // 当多选选项发生变化的时候调用
    handleSelectionChange(selection) {
      this.selections = selection
    },
    batchRemove(){
      if(this.selections.length===0){
        alert("请选择要删除的记录")
        return
      }
      if(confirm("确定要删除记录吗?")){
        //用于存放数组
        var idList=[];
        this.selections.forEach(item=>{
          idList.push(item.id);
        })
        api.batchRemove(idList).then(response=>{
          this.fetchData()
          this.$message.success(response.message)
        })
      }
    },
    
  // 重置表单
    resetData() {
    console.log('重置查询表单')
    this.searchObj = {}
    this.fetchData()
},
  1. 增加api
  batchRemove(idList) {
    return request({
      url: `${api_name}/batchRemove`,
      method: `delete`,
      data: idList
    })
  },
3. 后端代码
 @ApiOperation("批量删除")
    //数组[1,2,3]
    @DeleteMapping("batchRemove")
    public Result BatchRemove(@RequestBody List<Long> ids){
        boolean is_success = sysRoleService.removeByIds(ids);
        if (is_success){
            return Result.ok();
        }else {
            return Result.fail();
        }
    }

8. 用户功能实现

  • 数据表:sys_user
  • 后台环境搭建:代码生成器

8.1 前端添加用户页面

1. 添加路由

+修改 src/router/index.js 文件

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. 定义api

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

import request from '@/utils/request'

const api_name = '/admin/system/sysUser'

export default {
}
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: false, // 数据是否正在加载
        list: null, // banner列表
        total: 0, // 数据库中的总记录数
        page: 1, // 默认页码
        limit: 10, // 每页记录数
        searchObj: {}, // 查询表单对象
  
        createTimes: [],
  
        dialogVisible: false,
        sysUser: defaultForm,
        saveBtnDisabled: false,
      }
    }
   
  }
  </script>

在这里插入图片描述

8.2 分页显示用户数据

1. 分析

在页面渲染前获取数据,然后再渲染在页面上,需要把搜索条件,还有页码,每页记录数进行传递

2. 前端代码实现
  1. 添加相关方法
   created(){
        this.fetchData()
    },
    methods:{
     // 当页码发生改变的时候
    changeSize(size) {
      console.log(size)
      this.limit = size
      this.fetchData(1)
    },
    fetchData(page=1){
        this.page=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.records
            this.total=response.data.total
             // 数据加载并绑定成功
          this.listLoading = false
        })
        }
    },
    
    // 重置查询表单
    resetData() {
      console.log('重置查询表单')
      this.searchObj = {}
      this.createTimes = []
      this.fetchData()
    },
  1. 增加 api
  getPageList(page, limit, searchObj) {
    return request({
      url: `${api_name}/${page}/${limit}`,
      method: 'get',
      params: searchObj // url查询字符串或表单键值对
    })
  },
3. 后端代码实现
@RestController
@RequestMapping("/admin/system/sysUser")
public class SysUserController {
    @Autowired
    private SysUserService service;
    //用户条件分页查询
    @ApiOperation("用户条件分页查询")
    @GetMapping("{page}/{limit}")
    public Result index(@PathVariable Long page,
                        @PathVariable Long limit,
                        SysUserQueryVo sysUserQueryVo) {
        //创建page对象
        Page<SysUser> pageParam = new Page<>(page,limit);

        //封装条件,判断条件值不为空
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        //获取条件值
        //获取关键字
        String username = sysUserQueryVo.getKeyword();
        //获取相关时间
        String createTimeBegin = sysUserQueryVo.getCreateTimeBegin();
        String createTimeEnd = sysUserQueryVo.getCreateTimeEnd();
        //判断条件值不为空
        //like 模糊查询
        if(!StringUtils.isEmpty(username)) {
            wrapper.like(SysUser::getUsername,username);
        }
        //ge 大于等于
        if(!StringUtils.isEmpty(createTimeBegin)) {
            wrapper.ge(SysUser::getCreateTime,createTimeBegin);
        }
        //le 小于等于
        if(!StringUtils.isEmpty(createTimeEnd)) {
            wrapper.le(SysUser::getCreateTime,createTimeEnd);
        }

        //调用mp的方法实现条件分页查询
        IPage<SysUser> pageModel = service.page(pageParam, wrapper);
        return Result.ok(pageModel);
    }
}

+ 后端其它环境,直接使用代码生成器生成

8.3 实现添加功能

1. 前端代码
  1. 相关方法
  add() {
      this.dialogVisible = true
       this.sysUser = Object.assign({}, defaultForm)
    },
    //当添加修改窗口弹出
    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)
      })
    },
  1. 增加api
save(role) {
    return request({
      url: `${api_name}/save`,
      method: 'post',
      data: role
    })
  },
2. 后端代码
   @ApiOperation(value = "保存用户")
    @PostMapping("save")
    public Result save(@RequestBody SysUser user) {
        service.save(user);
        return Result.ok();
    }

8.4 修改功能

1. 前端操作
  1. 相关方法
 //修改
    //数据回显
    edit(id){
        api.findById(id).then(response=>{
            this.sysUser=response.data
            this.dialogVisible=true
        })
    },
     // 根据id更新记录
     updateData() {
      api.updateById(this.sysUser).then(response => {
        this.$message.success(response.message || '操作成功')
        this.dialogVisible = false
        this.fetchData(this.page)
      })
    }

  }
  1. 增加api
  findById(id){
        return request({
            url:`${api_name}/findById/${id}`,
            method:'get'
        })
    },
    updateById(role) {
        return request({
          url: `${api_name}/update`,
          method: 'put',
          data: role
        })
      },
2. 后台代码
  @ApiOperation("根据id进行查询")
    @GetMapping("findById/{id}")
    public Result findById(@PathVariable Long id){
        SysUser user = service.getById(id);
        System.out.println(user);
        return Result.ok(user);
    }
    @ApiOperation(value = "更新用户")
    @PutMapping("update")
    public Result updateById(@RequestBody SysUser user) {
        service.updateById(user);
        return Result.ok();
    }

8.5 删除功能

1. 前端操作
  1. 相关方法
  // 根据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('取消删除')
      })
    },
  1. 增加api
removeById(id) {
    return request({
      url: `${api_name}/remove/${id}`,
      method: 'delete'
    })
  }
2.后端操作
@ApiOperation(value = "删除用户")
    @DeleteMapping("remove/{id}")
    public Result remove(@PathVariable Long id) {
        service.removeById(id);
        return Result.ok();
    }

9. 给用户分配角色及更改状态

9.1 给用户分配角色

1. 接口分析

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

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

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 // 是否全选    }  },
3. 前端操作
  1. 相关方法
//当点击分配角色
 showAssignRole(user){
 //当前数据赋值给页面
      this.sysUser = user
      //弹出分配角色窗口
      this.dialogRoleVisible = true
      this.getRoles()
    },
    getRoles () {
      //根据Userid进行查询当前用户的角色,和所有角色
      roleApi.getRoles(this.sysUser.id).then(response => {
        //allRolesList:所有角色
        //assginRoleLiest当前用户所属角色
        
        const {allRolesList, assginRoleList} = response.data
        this.allRoles = allRolesList
        //用户的角色ID的列表
        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)
      })
    },
  1. 增加api
getRoles(userId){
    return request({
      url:`${api_name}/getRoles/${userId}`,
      method:'get'
    })
  },
  assignRoles(assginRoleVo) {
    return request({
      url: `${api_name}/doAssign`,
      method: 'post',
      data: assginRoleVo
    })
  }
4. 后端操作
  1. controller层:sysRoleController
  @ApiOperation("根据用户id查询所属角色,和所有角色")
    @GetMapping("getRoles/{userId}")
    public Result getRoles(@PathVariable Long userId){
        Map<String, Object> roleMap = sysRoleService.findRoleByAdminId(userId);
        System.out.println(roleMap);
        return Result.ok(roleMap);
    }
    @ApiOperation(value = "根据用户分配角色")
    @PostMapping("/doAssign")
    public Result doAssign(@RequestBody AssginRoleVo assginRoleVo) {
        sysRoleService.doAssign(assginRoleVo);
        return Result.ok();
    }
  1. service层
@Service
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {
//需要先搭建sys_user_role数据表环境
    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Override
    public Map<String, Object> findRoleByAdminId(Long userId) {
        //查询所有角色
        List<SysRole> allRolesList = this.list();
        //拥有的角色id
        List<Long> existRoleIdList=sysUserRoleMapper.getRoleIds(userId);

//        List<SysUserRole> existUserRoleList = sysUserRoleMapper.selectList(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getUserId, userId).select(SysUserRole::getRoleId));
//        List<Long> existRoleIdList = existUserRoleList.stream().map(c->c.getRoleId()).collect(Collectors.toList());

        //对角色进行分类
        List<SysRole> assginRoleList = new ArrayList<>();
        for (SysRole role : allRolesList) {
            //已分配
            if(existRoleIdList.contains(role.getId())) {
                assginRoleList.add(role);
            }
        }

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

    }
    @Transactional
    @Override
    public void doAssign(AssginRoleVo assginRoleVo) {
        sysUserRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getUserId, assginRoleVo.getUserId()));

        for(Long roleId : assginRoleVo.getRoleIdList()) {
            if(StringUtils.isEmpty(roleId)) continue;
            SysUserRole userRole = new SysUserRole();
            userRole.setUserId(assginRoleVo.getUserId());
            userRole.setRoleId(roleId);
            sysUserRoleMapper.insert(userRole);
        }
    }
}

  1. mapper层
@Mapper
@Repository
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
    @Select("select role_id from sys_user_role where user_id=#{userId}")

    List<Long> getRoleIds(@Param("userId") Long userId);
}

9.2 更改用户转态

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

1. 前端操作
  1. 相关方法:src/views/system/sysUser/list.vue
 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()
        }
      })
    }
  1. 增加api:src/api/system/sysUser.
updateStatus(id, status) {
  return request({
    url: `${api_name}/updateStatus/${id}/${status}`,
    method: 'get'
  })
}
2. 后端代码

操作类:SysUserController

  1. controller层
@ApiOperation(value = "更新状态")
@GetMapping("updateStatus/{id}/{status}")
public Result updateStatus(@PathVariable Long id, @PathVariable Integer status) {
    sysUserService.updateStatus(id, status);
    return Result.ok();
}
  1. service接口
void updateStatus(Long id, Integer status);
  1. service接口实现
@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);
}
根据引用的解释,云尚办公集成knife4j出现空指针异常的原因可能是无法找到knife4j的任何版本。解决方案是通过project structure配置libraries,并通过Maven从正确的依赖中安装knife4j。此外,根据引用,knife4j是一个为Java MVC框架集成Swagger生成Api文档的增强解决方案。因此,集成knife4j可以帮助你生成和管理Api文档。 另外,引用提到,作者在练习云尚办公项目时遇到了一些问题,并对项目提出了一些其他的见解。然而,具体关于云尚办公集成knife4j出现空指针异常的详细问题没有在提供的引用中找到。如果你能提供更多关于空指针异常的细节,我将能够更好地帮助你解决问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [尚硅谷-云尚办公-项目复盘](https://blog.csdn.net/qq_47168235/article/details/130468136)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [云尚办公系统学习笔记(1.基础设置)](https://blog.csdn.net/Kiritoasu/article/details/130726289)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值