云上办公系统项目
创始人
2024-05-30 03:48:01
0

云上办公系统项目

  • 1、云上办公系统
    • 1.1、介绍
    • 1.2、核心技术
    • 1.3、开发环境说明
    • 1.4、产品展示
      • 后台
      • 前台
    • 1.5、 个人总结
  • 2、后端环境搭建
    • 2.1、建库建表
    • 2.2、创建Maven项目
      • pom文件
        • guigu-oa-parent
        • common
        • common-util
        • service-util
        • model
        • service-oa
      • 配置数据源、服务器端口号
        • application.yml
        • application-dev.yml
      • 导入实体类
    • 2.3、编写代码
      • 启动类
  • 3、后端角色管理
    • 3.1、查询所有角色
      • SysRoleMapper
      • SysRoleService
      • SysRoleServiceImpl
      • 编写测试类
      • 编写统一结果返回类
        • ResultCodeEnum
        • Result
      • SysRoleController
      • 测试
    • 3.2、集成knife4j
        • Swagger介绍
        • 目的
        • 使用步骤
          • 添加依赖
          • 添加knife4j配置类
          • Controller层添加注解
          • 测试
    • 3.3、分页查询所有角色
        • MybatisPlusConfig
        • 主启动类上添加包扫描
        • SysRoleController
        • 测试
    • 3.4、添加/修改/删除角色
      • 测试
  • 4、统一异常处理
    • 4.1、全局异常处理
    • 4.2、特定异常处理
    • 4.3、自定义异常处理
      • GlobalExceptionHandler
      • GuiguException
  • 5、前端环境搭建
    • 安装脚手架工程
    • 前后联调的流程
      • 修改前端的IP地址
      • 编写后台登录/登出的请求
      • 修改前端的跳转地址
      • 修改响应状态码
      • 测试
  • 6、前端角色管理
    • 6.1、角色列表
      • 修改路由
      • 创建角色页面
      • 定义角色管理相关的API请求函数
      • 测试
    • 6.2、角色删除
      • sysRole.js
      • list.vue
    • 6.3、角色添加
    • 6.4、角色修改与数据回显
    • 6.5、批量删除
      • sysRole.js
      • list.vue
      • 页面展示
  • 7、用户管理
    • 7.1、用户管理CRUD
      • 需求分析
      • 代码生成器
      • 编写代码
      • 测试
      • 整合前端
        • 前端页面 list.vue
        • 添加路由
        • 定义API接口
      • 页面展示
    • 7.2、用户管理分配角色
      • 需求分析
      • 接口分析
      • 编写代码
      • 前端展示
    • 7.3、修改用户状态
      • 需求分析
      • 编写代码
      • 整合前端
        • 定义前端路由
        • 修改前端页面
      • 页面展示
  • 8、菜单管理
    • 8.1、菜单管理CRUD
      • 需求分析
      • 编写代码
      • 接口测试
      • 整合前端
        • sysMenu.js
        • list.vue
      • 页面展示
    • 8.2、角色分配菜单功能
      • 需求分析
      • 编写代码
      • 整合前端
        • router/index.js
        • sysRole/list.vue
        • sysMenu.js
        • assignAuth.vue
      • 页面展示
  • 9、权限管理(重难点)
    • 9.1、用户登录权限管理
      • 需求分析
      • 引入JWT
      • 修改用户登录
        • 先引入MD5工具类
        • 修改SysUserControler保存用户的方法
        • 修改IndexController的登录方法
        • SysMenuService
        • SysMenuServiceImpl
      • 接口测试
        • 登录接口测试
        • info接口测试
      • 整合前端
      • 页面展示
    • 9.2、用户认证
      • 整合SpringSecurity
        • 引入依赖
        • 添加配置类
        • 测试
      • 用户认证
        • 流程分析
        • 自定义组件的编写
          • 自定义加密器PasswordEncoder
          • 自定义用户对象UserDetails
          • UserDetailsService
          • UserDetailsServiceImpl
          • 自定义用户认证接口
          • 认证解析token
          • 配置用户认证
      • 测试
    • 9.3、用户权限控制
      • 流程分析
      • 修改代码
        • spring-security模块配置redis
        • 修改TokenLoginFilter
        • 修改TokenAuthenticationFilter
        • 修改WebSecurityConfig类
        • service-oa模块添加redis配置
        • 控制controller层接口权限
        • 异常处理
      • 测试
  • 10、Activiti
    • 10.1、Activiti流程操作
      • 配置Activiti
        • 引入Activiti依赖
        • 添加配置
        • 重启项目
      • 使用activiti插件
        • 下载activiti-explorer
        • 解压部署
        • 访问activiti-explorer
    • 10.2、流程控制
      • 绘制流程
        • 新建
        • 绘制
        • 导出
        • 下载文件
      • 部署流程
      • 流程实例
      • 任务分配
      • 任务组
    • 10.3、网关
        • 排他网关
        • 并行网关
        • 包含网关
  • 11、审批管理
    • 11.1、审批设置--CRUD
    • 11.2、模板审批--CRUD
    • 11.3、添加审批模板
    • 11.4、查看审批模板
    • 11.5、审批列表
      • 分页查询
      • 页面展示
      • 部署流程定义
  • 12、前端审批
    • 12.1、OA审批
  • 13、代码托管
    • Git
    • Gitee
    • GitHub
    • 网盘资料

申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计13077字,阅读大概需要30分钟
更多学习内容, 欢迎关注我
个人公众号:不懂开发的程序猿
个人网站:https://jerry-jy.co/

【警告】本篇博客较长,若引起阅读不适,建议收藏,稍后再读

1、云上办公系统

1.1、介绍

云上办公系统是一套自动办公系统,系统主要包含:管理端和员工端

管理端包含:权限管理、审批管理、公众号菜单管理

员工端采用微信公众号操作,包含:办公审批、微信授权登录、消息推送等功能

项目服务器端架构:SpringBoot + MyBatisPlus + SpringSecurity + Redis + Activiti+ MySQL

前端架构:vue-admin-template + Node.js + Npm + Vue + ElementUI + Axios

1.2、核心技术

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

1.3、开发环境说明

工具版本
后台SpringBoot 2.3.6 + MyBatisPlus 3.4.1
服务器Tomcat 8.5.73
数据库MySQL 8.0.27
Build ToolsMaven 3.8.5
前端Vue + ElementUI + Node.js 14.15.0
开发工具IDEA 2022.3
版本管理工具Git

1.4、产品展示

后台

登录页

在这里插入图片描述

【系统管理】–【用户管理】

在这里插入图片描述

【系统管理】–【角色管理】

在这里插入图片描述

【系统管理】–【菜单管理】

在这里插入图片描述

【审批设置】–【审批类型】

在这里插入图片描述

【审批设置】–【审批模板】

在这里插入图片描述

【审批管理】–【审批列表】

在这里插入图片描述

【公众号菜单】–【菜单列表】

在这里插入图片描述

前台

正常的前台页面是在微信公众号上,我这里没有整合

http://localhost:9090/#/

在这里插入图片描述

审批页面

在这里插入图片描述

测试号的页面展示

在这里插入图片描述

1.5、 个人总结

我认为该项目对我来说主要的帮助有:

1、项目是前后端分离的,符合目前主流业务开发逻辑,作为后端程序员,复习前端Vue + ElementUI框架, 巩固练习使用前端的脚手架工程,学习使用前后端联调开发过程

2、项目中引入JWT加密token,用作用户登录身份校验,用 SpringSecurity 来做权限控制,涉及多表查询,是项目的重难点学习对象,也是对前面学习SpringSecurity的一个巩固

3、前端使用微信公众号来作为前端接入口,以前没有开发过,也是亮点。

4、引入 工作流引擎:Activiti 作为组件,第一次用,学习下

5、集成Swagger,方便进行接口API的统一测试

2、后端环境搭建

2.1、建库建表

db.sql

sql语句太多了,见文末的资料

2.2、创建Maven项目

本项目采用Maven聚合模块来管理工程

在这里插入图片描述

pom文件

guigu-oa-parent

4.0.0org.springframework.bootspring-boot-starter-parent2.3.6.RELEASEcom.jerryguigu-oa-parent1.0pomcommonmodelservice-oa1.83.4.18.0.273.0.30.9.12.0.21com.baomidoumybatis-plus-boot-starter${mybatis-plus.version}mysqlmysql-connector-java${mysql.version}com.github.xiaoyminknife4j-spring-boot-starter${knife4j.version}io.jsonwebtokenjjwt${jwt.version}com.alibabafastjson${fastjson.version}org.apache.maven.pluginsmaven-compiler-plugin3.11.81.8

common

4.0.0com.jerryguigu-oa-parent1.0commonpomcommon-utilservice-util

common-util

4.0.0com.jerrycommon1.0common-utiljarorg.springframework.bootspring-boot-starter-webprovided io.jsonwebtokenjjwtorg.projectlomboklombokcom.alibabafastjson

service-util

4.0.0com.jerrycommon1.0service-utilcom.jerrycommon-util1.0org.springframework.bootspring-boot-starter-webcom.baomidoumybatis-plus-boot-startermysqlmysql-connector-java

model

4.0.0com.jerryguigu-oa-parent1.0modelorg.projectlomboklombokcom.github.xiaoyminknife4j-spring-boot-starterprovided com.baomidoumybatis-plus-boot-starterprovided 

service-oa

4.0.0com.jerryguigu-oa-parent1.0service-oajarcom.jerrymodel1.0com.jerryservice-util1.0org.springframework.bootspring-boot-starter-testtest${project.artifactId}org.springframework.bootspring-boot-maven-plugin

配置数据源、服务器端口号

application.yml

在这里插入图片描述

spring:application:name: service-oaprofiles:active: dev

application-dev.yml

server:port: 8800
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/guigu-oa?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8username: rootpassword: root

导入实体类

在这里插入图片描述

2.3、编写代码

在这里插入图片描述

启动类

package com.jerry.auth;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** ClassName: ServiceAuthApplication* Package: com.jerry.auth* Description:** @Author jerry_jy* @Create 2023-02-28 22:03* @Version 1.0*/@SpringBootApplication
public class ServiceAuthApplication {public static void main(String[] args) {SpringApplication.run(ServiceAuthApplication.class, args);}
}

3、后端角色管理

3.1、查询所有角色

SysRoleMapper

package com.jerry.auth.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jerry.model.system.SysRole;
import org.apache.ibatis.annotations.Mapper;/*** ClassName: SysRoleMapper* Package: com.jerry.auth.mapper* Description:** @Author jerry_jy* @Create 2023-02-28 22:05* @Version 1.0*/@Mapper
public interface SysRoleMapper extends BaseMapper {
}

SysRoleService

package com.jerry.auth.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.jerry.model.system.SysRole;/*** ClassName: SysRoleService* Package: com.jerry.auth.service* Description:** @Author jerry_jy* @Create 2023-03-01 9:12* @Version 1.0*/public interface SysRoleService extends IService {
}

SysRoleServiceImpl

package com.jerry.auth.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jerry.auth.mapper.SysRoleMapper;
import com.jerry.auth.service.SysRoleService;
import com.jerry.model.system.SysRole;
import org.springframework.stereotype.Service;/*** ClassName: SysRoleServiceImpl* Package: com.jerry.auth.service.impl* Description:** @Author jerry_jy* @Create 2023-03-01 9:13* @Version 1.0*/@Service
public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService {
}

编写测试类

目的是:

  • 测试数据源连接
  • 复习下MyBatisPlus对数据库的CRUD
package com.jerry.auth;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jerry.auth.mapper.SysRoleMapper;
import com.jerry.auth.service.SysRoleService;
import com.jerry.model.system.SysRole;
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;/*** ClassName: TestMpDemo1* Package: com.jerry.auth* Description:** @Author jerry_jy* @Create 2023-02-28 22:07* @Version 1.0*/@SpringBootTest
public class TestMpDemo1 {// MyBatisPlus 对 service 层和 dao 层都做了很好的封装,直接调对应的CRUD方法就行@Autowiredprivate SysRoleMapper sysRoleMapper;@Autowiredprivate SysRoleService sysRoleService;// 使用MP 封装的 service 来操作数据库,查询所有记录@Testpublic void getAllByService(){List list = sysRoleService.list();list.forEach(System.out::println);}// 使用MP 封装的 mapper查询所有记录@Testpublic void getAllByMapper(){List sysRoles = sysRoleMapper.selectList(null);sysRoles.forEach(System.out::println);}// 添加操作@Testpublic void insert(){SysRole sysRole = new SysRole();sysRole.setRoleName("角色管理员");sysRole.setRoleCode("role");sysRole.setDescription("角色管理员");int result = sysRoleMapper.insert(sysRole);System.out.println(result); //影响的行数System.out.println(sysRole.getId()); //id自动回填}// 修改操作@Testpublic void updateById(){// 根据id查询SysRole sysRole = sysRoleMapper.selectById(9);// 设置修改值sysRole.setRoleName("角色管理员1");// 调用方法实现最终修改int update = sysRoleMapper.updateById(sysRole);System.out.println("update = " + update); // 受影响的行数}// 根据id删除@Testpublic void deleteById(){int delete = sysRoleMapper.deleteById(9);System.out.println("delete = " + delete); // 受影响的行数}// 批量删除@Testpublic void deleteBatchByIds(){//        int delete = sysRoleMapper.delete(null);// 或者下面这种写法int delete = sysRoleMapper.deleteBatchIds(Arrays.asList(1, 2));System.out.println("ids = " + delete); // 受影响的行数}// 条件查询@Testpublic void testQueryWrapper(){//        QueryWrapper queryWrapper = new QueryWrapper<>();
//        queryWrapper.eq("role_name", "系统管理员");// 或者下面这种写法LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(SysRole::getRoleName,"系统管理员");List list = sysRoleMapper.selectList(queryWrapper);list.forEach(System.out::println);}
}

编写统一结果返回类

目的:定义好统一的返回状态码和返回结果信息

ResultCodeEnum

package com.jerry.common.result;import lombok.Getter;/*** ClassName: ResultCodeEnum* Package: com.jerry.common.result* Description:** @Author jerry_jy* @Create 2023-03-01 9:50* @Version 1.0*/@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;}
}

Result

package com.jerry.common.result;import lombok.Data;/*** ClassName: Result* Package: com.jerry.common.result* Description:** @Author jerry_jy* @Create 2023-03-01 9:52* @Version 1.0*/@Data
public class Result {private Integer code; // 状态码private String message; // 返回信息private T data; // 统一返回的结果数据/*** 封装返回数据* @param body* @param resultCodeEnum* @return* @param */public static  Result build(T body, ResultCodeEnum resultCodeEnum) {Result result = new Result<>();// 封装数据if (body!=null){result.setData(body);}// 状态码result.setCode(resultCodeEnum.getCode());//返回信息result.setMessage(resultCodeEnum.getMessage());return result;}// 构造私有化 外部不能newprivate Result(){}// 成功 空结果public static Result ok(){return build(null,ResultCodeEnum.SUCCESS);}/*** 成功 返回有数据的结果* @param data* @return* @param */public static Result ok(T data){return build(data,ResultCodeEnum.SUCCESS);}// 失败public static Result fail(){return build(null,ResultCodeEnum.FAIL);}/*** 失败  返回有数据的结果* @param data* @return* @param */public static Result fail(T data){return build(data,ResultCodeEnum.FAIL);}public Result message(String msg){this.setMessage(msg);return this;}public Result code(Integer code){this.setCode(code);return this;}
}

SysRoleController

package com.jerry.auth.controller;import com.jerry.auth.service.SysRoleService;
import com.jerry.common.result.Result;
import com.jerry.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;/*** ClassName: SysRoleController* Package: com.jerry.auth.controller* Description:** @Author jerry_jy* @Create 2023-03-01 9:38* @Version 1.0*/@RestController
@RequestMapping("/admin/system/sysRole")
public class SysRoleController {@Autowiredprivate SysRoleService sysRoleService;// http://localhost:8800/admin/system/sysRole/getAll// 测试查询所有的角色
//    @GetMapping("/getAll")
//    private List getAll(){
//        List list = sysRoleService.list();
//        return list;
//    }/*** 统一返回数据结果* @return*/@GetMapping("/getAll")private Result getAll(){List list = sysRoleService.list();return Result.ok(list);}}

测试

http://localhost:8800/admin/system/sysRole/getAll

在这里插入图片描述

3.2、集成knife4j

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

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。

Swagger介绍

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

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

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

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

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

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

目的

  • 用来生成接口的API文档

  • 方便后端Java程序员进行接口测试

使用步骤

添加依赖

service-uitl.pom

com.github.xiaoyminknife4j-spring-boot-starter

添加knife4j配置类
package com.jerry.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;/*** ClassName: knife4j* Package: com.jerry.common.config* Description:** @Author jerry_jy* @Create 2023-03-01 10:53* @Version 1.0*//*** knife4j配置信息*/
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfig {@Beanpublic Docket adminApiConfig(){List 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参数endDocket adminApi = new Docket(DocumentationType.SWAGGER_2).groupName("adminApi").apiInfo(adminApiInfo()).select()//只显示admin路径下的页面.apis(RequestHandlerSelectors.basePackage("com.jerry")).paths(PathSelectors.regex("/admin/.*")).build().globalOperationParameters(pars);return adminApi;}private ApiInfo adminApiInfo(){return new ApiInfoBuilder().title("后台管理系统-API文档").description("本文档描述了后台管理系统微服务接口定义").version("1.0").contact(new Contact("jerry", "https://jerry-jy.co", "jinyang9248@163.com")).build();}}
Controller层添加注解
  • 类上加@Api(tags = "角色管理接口")
  • 方法上加@ApiOperation("查询所有角色")

在这里插入图片描述

测试

http://localhost:8800/doc.html

在这里插入图片描述

3.3、分页查询所有角色

service-util模块下创建 MybatisPlusConfig

MybatisPlusConfig

package com.jerry.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;/*** ClassName: MybatisPlusConfig* Package: com.jerry.common.config.mp* Description:** @Author jerry_jy* @Create 2023-03-01 11:17* @Version 1.0*/@Configuration
@MapperScan("com.jerry.auth.mapper")
public class MybatisPlusConfig {/*** 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}@Beanpublic ConfigurationCustomizer configurationCustomizer() {return configuration -> configuration.setUseDeprecatedExecutor(false);}
}

主启动类上添加包扫描

在这里插入图片描述

SysRoleController

    /*** 条件分页查询** @param page           当前页* @param pageSize       分页大小* @param sysRoleQueryVo 条件查询对象* @return*/@ApiOperation("条件分页查询")@GetMapping("{page}/{pageSize}")private Result page(@PathVariable int page, @PathVariable int pageSize, SysRoleQueryVo sysRoleQueryVo) {// 1、创建 page 对象, 传递分页查询的参数Page sysRolePage = new Page<>(page, pageSize);// 2、构造分页查询条件, 判断条件是否为空,不为空进行封装LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();String roleName = sysRoleQueryVo.getRoleName();if (!StringUtils.isEmpty(roleName)) {// 封装lambdaQueryWrapper.like(SysRole::getRoleName,roleName);}// 3、调用方法实现分页查询sysRoleService.page(sysRolePage, lambdaQueryWrapper);return Result.ok(sysRolePage);}

测试

在这里插入图片描述

3.4、添加/修改/删除角色

    /*** 添加角色* @param sysRole* @return*/@ApiOperation("添加角色")@PostMapping("/save")public Result save(@RequestBody SysRole sysRole) {// 调用 service 方法boolean is_success = sysRoleService.save(sysRole);if (is_success) {return Result.ok();} else {return Result.fail();}}/*** 根据 id 修改角色* @param id* @return*/@ApiOperation("根据 id 查询角色")@GetMapping("/get/{id}")public Result get(@PathVariable long id){SysRole sysRole = sysRoleService.getById(id);return Result.ok(sysRole);}/*** 修改角色* @param sysRole* @return*/@ApiOperation("修改角色")@PutMapping("/update")public Result update(@RequestBody SysRole sysRole) {// 调用 service 方法boolean is_success = sysRoleService.updateById(sysRole);if (is_success) {return Result.ok();} else {return Result.fail();}}/*** 根据 id 删除* @param id* @return*/@ApiOperation("根据 id 删除")@DeleteMapping("delete/{id}")public Result deleteById(@PathVariable long id){boolean is_success = sysRoleService.removeById(id);if (is_success) {return Result.ok();} else {return Result.fail();}}/*** 批量删除* 说明:*      Java 中的对象会转化为Json对象*      Java 中的List集合会转化为数组* @param ids* @return*/@ApiOperation("批量删除")@DeleteMapping("/ids")public Result deleteByIds(@RequestBody List ids){boolean is_success = sysRoleService.removeByIds(ids);if (is_success) {return Result.ok();} else {return Result.fail();}}

测试

在这里插入图片描述

配置日期时间格式

application-dev.yml添加以下内容

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

4、统一异常处理

异常处理的思路流程

在这里插入图片描述

4.1、全局异常处理

4.2、特定异常处理

4.3、自定义异常处理

service-util 模块下

GlobalExceptionHandler

package com.jerry.common.config.exception;import com.jerry.common.result.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;/*** ClassName: GlobalExceptionHandler* Package: com.jerry.common.config.exception* Description:** @Author jerry_jy* @Create 2023-03-01 15:48* @Version 1.0*/
@ControllerAdvice
public class GlobalExceptionHandler {/*** 全局异常处理 执行的方法* @return*/@ExceptionHandler(Exception.class)@ResponseBodypublic Result error(Exception e){e.printStackTrace();return Result.fail().message("执行全局处理异常...");}/*** 特定异常处理* @param e* @return*/@ExceptionHandler(ArithmeticException.class)@ResponseBodypublic Result error(ArithmeticException e){e.printStackTrace();return Result.fail().message("执行特定处理异常...");}/*** 自定义异常处理* @param e* @return*/@ExceptionHandler(GuiguException.class)@ResponseBodypublic Result error(GuiguException e){e.printStackTrace();return Result.fail().code(e.getCode()).message(e.getMsg());}
}

GuiguException

package com.jerry.common.config.exception;import com.jerry.common.result.ResultCodeEnum;
import lombok.Data;/*** ClassName: GuiguException* Package: com.jerry.common.config.exception* Description:** @Author jerry_jy* @Create 2023-03-01 15:59* @Version 1.0*/
@Data
public class GuiguException extends RuntimeException {private Integer code;private String msg;/*** 通过状态码和错误消息创建异常对象* @param code* @param 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();}@Overridepublic String toString() {return "GuiguException{" +"code=" + code +", msg='" + msg + '\'' +'}';}
}

5、前端环境搭建

安装脚手架工程

前端用的脚手架工程是:vue-element-admin

https://panjiachen.github.io/vue-element-admin-site/#/

# clone the project
git clone https://github.com/PanJiaChen/vue-element-admin.git# install dependency
npm install# develop
npm run dev

http://localhost:9528/#/dashboard

在这里插入图片描述

前后联调的流程

在这里插入图片描述

在这里插入图片描述

修改前端的IP地址

    // before: require('./mock/mock-server.js')proxy: {'/dev-api': { // 匹配所有以 '/dev-api'开头的请求路径target: 'http://localhost:8800',changeOrigin: true, // 支持跨域pathRewrite: { // 重写路径: 去掉路径中开头的'/dev-api''^/dev-api': ''}}}

在这里插入图片描述

编写后台登录/登出的请求

IndexController

package com.jerry.auth.controller;import com.jerry.common.result.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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;
import java.util.Objects;/*** ClassName: IndexController* Package: com.jerry.auth.controller* Description:** @Author jerry_jy* @Create 2023-03-01 18:15* @Version 1.0*/
@Api(tags = "后台登录管理")
@RestController
@RequestMapping("/admin/system/index")
public class IndexController {/*** login* @return*/@ApiOperation("登录")@PostMapping("/login")public Result login(){// {"code":200,"data":{"token":"admin-token"}}HashMap map = new HashMap<>();map.put("token","admin-token");return Result.ok(map);}/*** info* @return*/@GetMapping("/info")public Result info(){Map 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);}/*** logout* @return*/@ApiOperation("登出")@PostMapping("/logout")public Result logout(){return Result.ok();}
}

修改前端的跳转地址

在这里插入图片描述

修改响应状态码

在这里插入图片描述

测试

重启前端、后端项目,可以发现请求头信息已经做了跳转、转发

在这里插入图片描述

6、前端角色管理

6.1、角色列表

修改路由

重新定义constantRoutes

在这里插入图片描述

  {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'},}]},

创建角色页面

在这里插入图片描述


定义角色管理相关的API请求函数

在这里插入图片描述

/*
角色管理相关的API请求函数
*/
import request from '@/utils/request'const api_name = '/admin/system/sysRole'export default {/*获取角色分页列表(带搜索)*/getPageList(page, limit, searchObj) {return request({url: `${api_name}/${page}/${limit}`,method: 'get',// 如果是普通对象参数写法,params:对象参数名// 如果是使用json格式传递,data:对象参数名params: searchObj})}
}

测试

重新启动前端工程

http://localhost:9528/?#/system/sysRole

在这里插入图片描述

6.2、角色删除

sysRole.js

  /*** 角色删除* @param {*} id * @returns */removeById(id) {return request({url: `${api_name}/delete/${id}`,method: 'delete'})}

list.vue

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

6.3、角色添加

6.4、角色修改与数据回显

6.5、批量删除

前端CRUD完整代码

注意点:

前端中的url请求路径要和后端的@DeleteMapping@PutMapping@PostMapping@GetMapping路径一致

在这里插入图片描述

sysRole.js

/*
角色管理相关的API请求函数
*/
import request from '@/utils/request'const api_name = '/admin/system/sysRole'export default {/*** 获取角色分页列表(带搜索)* @param {*} page * @param {*} limit * @param {*} searchObj * @returns */getPageList(page, limit, searchObj) {return request({url: `${api_name}/${page}/${limit}`,method: 'get',// 如果是普通对象参数写法,params:对象参数名// 如果是使用json格式传递,data:对象参数名params: searchObj})},/*** 角色删除* @param {*} id * @returns */removeById(id) {return request({url: `${api_name}/delete/${id}`,method: 'delete'})},/*** 角色添加* @param {*} role * @returns */save(role) {return request({url: `${api_name}/save`,method: 'post',data: role})},// 回显要修改的id信息getById(id) {return request({url: `${api_name}/get/${id}`,method: 'get'})},// 修改updateById(role) {return request({url: `${api_name}/update`,method: 'put',data: role})},// 批量删除batchRemove(idList) {return request({url: `${api_name}/ids`,method: `delete`,data: idList})}}

list.vue


页面展示

在这里插入图片描述

7、用户管理

7.1、用户管理CRUD

需求分析

在这里插入图片描述

代码生成器

  • 可以采用MyBatisPlus提供的代码生成器直接生成 mapperserviceimplcontroller
  • 手动创建的话,也行

service-oa

        com.baomidoumybatis-plus-generator3.4.1org.apache.velocityvelocity-engine-core2.0

CodeGet.java

在这里插入图片描述

package com.jerry.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("E:\\CodeLife\\IdeaProject\\guigu-oa\\guigu-oa-parent\\service-oa"+"/src/main/java");gc.setServiceName("%sService");	//去掉Service接口的首字母Igc.setAuthor("jerry");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.jerry");pc.setModuleName("auth"); //模块名pc.setController("controller");pc.setService("service");pc.setMapper("mapper");mpg.setPackageInfo(pc);// 5、策略配置StrategyConfig strategy = new StrategyConfig();strategy.setInclude("sys_user");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();}
}

编写代码

代码是写在service-oa类中的

SysUserMapper

public interface SysUserMapper extends BaseMapper {
}

SysUserService

public interface SysUserService extends IService {
}

SysUserServiceImpl

@Service
public class SysUserServiceImpl extends ServiceImpl implements SysUserService {
}

SysUserController

package com.jerry.auth.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jerry.auth.service.SysUserService;
import com.jerry.common.result.Result;
import com.jerry.model.system.SysUser;
import com.jerry.vo.system.SysUserQueryVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;/*** 

* 用户表 前端控制器*

** @author jerry* @since 2023-03-01*/ @Api(tags = "用户管理接口") @RestController @RequestMapping("/admin/system/sysUser") public class SysUserController {@Autowiredprivate SysUserService sysUserService;/*** 用户条件分页查询** @param page* @param pageSize* @param sysUserQueryVo* @return*/@ApiOperation("用户条件分页查询")@GetMapping("/{page}/{pageSize}")public Result page(@PathVariable int page, @PathVariable int pageSize, SysUserQueryVo sysUserQueryVo) {Page sysUserPage = new Page<>(page, pageSize);LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();// 获取条件String userName = sysUserQueryVo.getKeyword();String createTimeBegin = sysUserQueryVo.getCreateTimeBegin();String createTimeEnd = sysUserQueryVo.getCreateTimeEnd();// 判断条件值不为空if (!StringUtils.isEmpty(userName)){lambdaQueryWrapper.like(SysUser::getUsername,userName);}if (!StringUtils.isEmpty(createTimeBegin)){lambdaQueryWrapper.ge(SysUser::getCreateTime,createTimeBegin);}if (!StringUtils.isEmpty(createTimeEnd)){lambdaQueryWrapper.le(SysUser::getCreateTime,createTimeEnd);}sysUserService.page(sysUserPage,lambdaQueryWrapper);return Result.ok(sysUserPage);}/*** 获取用户* @param id* @return*/@ApiOperation("获取用户")@GetMapping("/get/{id}")public Result get(@PathVariable long id){SysUser user = sysUserService.getById(id);return Result.ok(user);}/*** 更新用户* @param sysUser* @return*/@ApiOperation("更新用户")@PutMapping("/update")public Result update(@RequestBody SysUser sysUser){boolean is_success = sysUserService.updateById(sysUser);if (is_success) {return Result.ok();} else {return Result.fail();}}/*** 保存用户* @param sysUser* @return*/@ApiOperation("保存用户")@PostMapping("/save")public Result save(@RequestBody SysUser sysUser){boolean is_success = sysUserService.save(sysUser);if (is_success) {return Result.ok();} else {return Result.fail();}}/*** 删除用户* @param id* @return*/@ApiOperation("删除用户")@DeleteMapping("/remove/{id}")public Result remove(@PathVariable long id){boolean is_success = sysUserService.removeById(id);if (is_success) {return Result.ok();} else {return Result.fail();}} }

测试

全部测试通过

在这里插入图片描述

整合前端

前端页面 list.vue

在这里插入图片描述


添加路由

在这里插入图片描述

      {name: 'sysUser',path: 'sysUser',component: () => import('@/views/system/sysUser/list'),meta: {title: '用户管理',icon: 'el-icon-s-custom'},},

定义API接口

在这里插入图片描述

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'})},updateStatus(id, status) {return request({url: `${api_name}/updateStatus/${id}/${status}`,method: 'get'})}
}

页面展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiKS5qR9-1678180003061)(E:/typora/image-20230302122542413.png)]

7.2、用户管理分配角色

需求分析

  • 一个用户对应多个角色

  • 一个角色可以有多个用户

多对多的关系

在这里插入图片描述

接口分析

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

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

编写代码

代码是写在service-oa类中的

SysUserRoleMapper

public interface SysUserRoleMapper extends BaseMapper {
}

SysUserRoleService

public interface SysUserRoleService extends IService {
}

SysUserRoleServiceImpl

@Service
public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService {
}

SysRoleController

    // 1、查询所有角色 和 当前用户所属角色@ApiOperation("根据用户获取角色数据")@GetMapping("/toAssign/{userId}")public Result toAssign(@PathVariable Long userId) {Map map = sysRoleService.findRoleDataByUserId(userId);return Result.ok(map);}// 2、为用户分配角色@ApiOperation("为用户分配角色")@PostMapping("/doAssign")public Result doAssign(@RequestBody AssginRoleVo assginRoleVo) {sysRoleService.doAssign(assginRoleVo);return Result.ok();}

SysRoleServiceImpl

/*** ClassName: SysRoleServiceImpl* Package: com.jerry.auth.service.impl* Description:** @Author jerry_jy* @Create 2023-03-01 9:13* @Version 1.0*/@Service
public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService {@Autowiredprivate SysUserRoleService sysUserRoleService;//1 查询所有角色 和 当前用户所属角色@Overridepublic Map findRoleDataByUserId(Long userId) {//1 查询所有角色,返回list集合,返回List allRoleList =baseMapper.selectList(null);//2 根据userid查询 角色用户关系表,查询userid对应所有角色idLambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysUserRole::getUserId,userId);List existUserRoleList = sysUserRoleService.list(wrapper);//从查询出来的用户id对应角色list集合,获取所有角色id
//        List list = new ArrayList<>();
//        for (SysUserRole sysUserRole:existUserRoleList) {
//            Long roleId = sysUserRole.getRoleId();
//            list.add(roleId);
//        }List existRoleIdList =existUserRoleList.stream().map(c -> c.getRoleId()).collect(Collectors.toList());//3 根据查询所有角色id,找到对应角色信息//根据角色id到所有的角色的list集合进行比较List assignRoleList = new ArrayList<>();for(SysRole sysRole : allRoleList) {//比较if(existRoleIdList.contains(sysRole.getId())) {assignRoleList.add(sysRole);}}//4 把得到两部分数据封装map集合,返回Map roleMap = new HashMap<>();roleMap.put("assginRoleList", assignRoleList);roleMap.put("allRolesList", allRoleList);return roleMap;}//2 为用户分配角色@Overridepublic void doAssign(AssginRoleVo assginRoleVo) {//把用户之前分配角色数据删除,用户角色关系表里面,根据userid删除LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysUserRole::getUserId,assginRoleVo.getUserId());sysUserRoleService.remove(wrapper);//重新进行分配List 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);}}
}

前端展示

在这里插入图片描述

7.3、修改用户状态

需求分析

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

编写代码

SysRoleController

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

SysUserService

public interface SysUserService extends IService {// 更新状态void updateStatus(Long id, Integer status);
}

SysUserServiceImpl

@Service
@Slf4j
public class SysUserServiceImpl extends ServiceImpl implements SysUserService {// 更新状态@Override@Transactionalpublic void updateStatus(Long id, Integer status) {// 根据用户 userid 查询用户对象SysUser sysUser = baseMapper.selectById(id);// 设置修改状态if (status == 0 || status == 1) {sysUser.setStatus(status);} else {log.info("数值不合法");}// 调用方法进行修改baseMapper.updateById(sysUser);}
}

整合前端

定义前端路由

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})
}

修改前端页面

list.vue


页面展示

在这里插入图片描述

8、菜单管理

8.1、菜单管理CRUD

需求分析

在这里插入图片描述

编写代码

SysMenuMapper

public interface SysMenuMapper extends BaseMapper {
}

SysRoleMenuMapper

public interface SysRoleMenuMapper extends BaseMapper {}

SysMenuService

public interface SysMenuService extends IService {List findNodes();// 删除菜单void removeMenuById(Long id);
}

SysRoleMenuService

public interface SysRoleMenuService extends IService {
}

SysMenuServiceImpl

@Service
public class SysMenuServiceImpl extends ServiceImpl implements SysMenuService {@Overridepublic List findNodes() {// 1、查询所有 的数据List sysMenuList = baseMapper.selectList(null);// 2、构建树形结构List list = MenuHelper.buildTree(sysMenuList);return list;}// 删除菜单@Overridepublic void removeMenuById(Long id) {// 判断当前菜单是否有下一层菜单LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(SysMenu::getParentId,id);Integer count = baseMapper.selectCount(lambdaQueryWrapper);if (count>0){throw new GuiguException(201,"菜单不能删除");}baseMapper.deleteById(id);}
}

MenuHelper

package com.jerry.auth.util;import com.jerry.model.system.SysMenu;import java.util.ArrayList;
import java.util.List;/*** ClassName: MenuHelper* Package: com.jerry.auth.util* Description:** @Author jerry_jy* @Create 2023-03-02 17:14* @Version 1.0*/
public class MenuHelper {/*** 使用递归方法建菜单* @param sysMenuList* @return*/public static List buildTree(List sysMenuList) {// 存放最终数据List trees = new ArrayList<>();// 把所有的菜单数据进行遍历for (SysMenu sysMenu : sysMenuList) {// 递归入口 parentId = 0if (sysMenu.getParentId().longValue()==0){trees.add(getChildren(sysMenu,sysMenuList));}}return trees;}/*** 递归查找子节点* @param sysMenu* @param sysMenuList* @return*/public static SysMenu getChildren(SysMenu sysMenu,List sysMenuList){sysMenu.setChildren(new ArrayList());// 遍历所有的菜单数据,判断id和parent_id的对应关系for (SysMenu menu : sysMenuList) {if (sysMenu.getId().longValue() == menu.getParentId().longValue()){if (sysMenu.getChildren() == null) {sysMenu.setChildren(new ArrayList<>());}sysMenu.getChildren().add(getChildren(menu,sysMenuList));}}return sysMenu;}
}

SysRoleMenuServiceImpl

@Service
public class SysRoleMenuServiceImpl extends ServiceImpl implements SysRoleMenuService {
}

SysMenuController

package com.jerry.auth.controller;import com.jerry.auth.service.SysMenuService;
import com.jerry.common.result.Result;
import com.jerry.model.system.SysMenu;
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;/*** 

* 菜单表 前端控制器*

** @author jerry* @since 2023-03-02*/ @Api(tags = "菜单管理接口") @RestController @RequestMapping("/admin/system/sysMenu") public class SysMenuController {@Autowiredprivate SysMenuService sysMenuService;@ApiOperation(value = "菜单列表")@GetMapping("/findNodes")public Result findNodes() {List list = sysMenuService.findNodes();return Result.ok(list);}@ApiOperation(value = "新增菜单")@PostMapping("save")public Result save(@RequestBody SysMenu sysMenu) {sysMenuService.save(sysMenu);return Result.ok();}@ApiOperation(value = "修改菜单")@PutMapping("update")public Result updateById(@RequestBody SysMenu sysMenu) {sysMenuService.updateById(sysMenu);return Result.ok();}@ApiOperation(value = "删除菜单")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {sysMenuService.removeMenuById(id);return Result.ok();} }

接口测试

在这里插入图片描述

整合前端

在这里插入图片描述

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

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})}
}

list.vue

在这里插入图片描述


页面展示

在这里插入图片描述

8.2、角色分配菜单功能

需求分析

在这里插入图片描述

编写代码

整合前端

router/index.js

在这里插入图片描述

      {path: 'assignAuth',component: () => import('@/views/system/sysRole/assignAuth'),meta: {activeMenu: '/system/sysRole',title: '角色授权'},hidden: true,}

sysRole/list.vue

在这里插入图片描述

添加一个分配权限的button按钮

          
    // 跳转到分配菜单的页面showAssignAuth(row) {this.$router.push('/system/assignAuth?id='+row.id+'&roleName='+row.roleName);},

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})}

assignAuth.vue

在这里插入图片描述


关闭Vue语法校验,避免报错

在这里插入图片描述

页面展示

在这里插入图片描述

9、权限管理(重难点)

9.1、用户登录权限管理

需求分析

在这里插入图片描述

引入JWT

  • JWT是JSON Web Token的缩写

  • 一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上

  • 官网:https://jwt.io/

  • 最重要的作用就是对 token信息的防伪作用。

  • 由三个部分组成:JWT头、有效载荷、签名哈希

  • base64url算法编码得到JWT

common-util

        io.jsonwebtokenjjwt

JwtHwlper

package com.jerry.common.jwt;import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;import java.util.Date;/*** ClassName: JwtHwlper* Package: com.jerry.common* Description:** @Author jerry_jy* @Create 2023-03-02 20:39* @Version 1.0*/
public class JwtHelper {private static long tokenExpiration = 365 * 24 * 60 * 60 * 1000;private static String tokenSignKey = "123456";// 根据用户 id 和用户名称, 生成token的字符串public static String createToken(Long userId, String username) {String token = Jwts.builder().setSubject("AUTH-USER").setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).claim("userId", userId).claim("username", username).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}public static Long getUserId(String token) {try {if (StringUtils.isEmpty(token)) return null;Jws claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();Integer userId = (Integer) claims.get("userId");return userId.longValue();} catch (Exception e) {e.printStackTrace();return null;}}public static String getUsername(String token) {try {if (StringUtils.isEmpty(token)) return "";Jws claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return (String) claims.get("username");} catch (Exception e) {e.printStackTrace();return null;}}public static void main(String[] args) {String token = JwtHelper.createToken(1L, "admin");System.out.println(token);String username = JwtHelper.getUsername(token);Long userId = JwtHelper.getUserId(token);System.out.println("username = " + username);System.out.println("userId = " + userId);}}

修改用户登录

先引入MD5工具类

package com.jerry.common.utils;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}public static void main(String[] args) {System.out.println(MD5.encrypt("111111"));}
}

修改SysUserControler保存用户的方法

在这里插入图片描述

修改IndexController的登录方法

package com.jerry.auth.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jerry.auth.service.SysMenuService;
import com.jerry.auth.service.SysUserService;
import com.jerry.common.config.exception.GuiguException;
import com.jerry.common.jwt.JwtHelper;
import com.jerry.common.result.Result;
import com.jerry.common.utils.MD5;
import com.jerry.model.system.SysUser;
import com.jerry.vo.system.LoginVo;
import com.jerry.vo.system.RouterVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;/*** ClassName: IndexController* Package: com.jerry.auth.controller* Description:** @Author jerry_jy* @Create 2023-03-01 18:15* @Version 1.0*/
@Api(tags = "后台登录管理")
@RestController
@RequestMapping("/admin/system/index")
public class IndexController {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysMenuService sysMenuService;/*** login** @return*/@ApiOperation("登录")@PostMapping("/login")public Result login(@RequestBody LoginVo loginVo) {// {"code":200,"data":{"token":"admin-token"}}
//        HashMap map = new HashMap<>();
//        map.put("token","admin-token");
//        return Result.ok(map);// 1、获取用户名和密码// 2、根据用户名查询数据库String username = loginVo.getUsername();LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(SysUser::getUsername, username);SysUser sysUser = sysUserService.getOne(queryWrapper);// 3、用户信息是否存在if (sysUser == null) {throw new GuiguException(201, "用户不存在...");}// 4、判断密码// 取出数据库中的密文密码(MD5)String password_dB = sysUser.getPassword();String password_input = MD5.encrypt(loginVo.getPassword());if (!password_dB.equals(password_input)) {throw new GuiguException(201, "密码错误...");}// 5、判断用户是否被禁用  1  可用    0   禁用if (sysUser.getStatus().intValue() == 0) {throw new GuiguException(201, "用户被禁用...");}// 6、使用jwt根据用户id和用户名称生成token的字符串String token = JwtHelper.createToken(sysUser.getId(), sysUser.getUsername());// 7、返回Map map = new HashMap<>();map.put("token", token);return Result.ok(map);}/*** info** @return*/@GetMapping("/info")public Result info(HttpServletRequest request) {// 1、从请求头获取用户信息(获取请求头的 token 字符串)String token = request.getHeader("token");// 2、从 token 字符串中获取 用户id 或者 用户名称Long userId = JwtHelper.getUserId(token); //1L;// 3、根据 用户id 查询数据库, 获取用户信息SysUser sysUser = sysUserService.getById(userId);// 4、根据 用户id 获取用户可以操作的菜单列表// 查询数据库动态构建路由结构,进行显示List routerList = sysMenuService.findUserMenuListByUserId(userId);// 5、根据 用户id 获取用户可以操作的按钮列表List permsList = sysMenuService.findUserPermsByUserId(userId);// 6、返回相应的数据Map map = new HashMap<>();map.put("roles", "[admin]");map.put("name", sysUser.getName());map.put("avatar", "https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");// 返回用户可以操作的菜单map.put("routers", routerList);// 返回用户可以操作的按钮map.put("buttons", permsList);return Result.ok(map);}/*** logout** @return*/@ApiOperation("登出")@PostMapping("/logout")public Result logout() {return Result.ok();}
}

SysMenuService

    // 根据 用户id 获取用户可以操作的菜单列表List findUserMenuListByUserId(Long userId);// 根据 用户id 获取用户可以操作的按钮列表List findUserPermsByUserId(Long userId);

SysMenuServiceImpl

    // 根据 用户id 获取用户可以操作的菜单列表@Overridepublic List findUserMenuListByUserId(Long userId) {List sysMenusList = null;// 1、判断当前用户是否是管理员       userId=1 是管理员// 1.1、 如果是管理员,查询所有菜单列表if (userId.longValue() == 1) {// 查询所有菜单列表LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(SysMenu::getStatus, 1);queryWrapper.orderByAsc(SysMenu::getSortValue);sysMenusList = baseMapper.selectList(queryWrapper);} else {// 1.2、如果不是管理员,根据 userId 查询可以操作菜单列表// 多表关联查询:sys_role、sys_role_mexnu、sys_menusysMenusList = baseMapper.findMenuListByUserId(userId);}// 2、把查询出来的数据列表, 构建成框架要求的路由结构// 先构建树形结构List sysMenuTreeList = MenuHelper.buildTree(sysMenusList);// 构建框架要求的路由结构List routerList = this.buildRouter(sysMenuTreeList);return routerList;}// 构建框架要求的路由结构private List buildRouter(List menus) {// 创建 list 集合,存值最终数据List routers = new ArrayList<>();// menus 遍历for (SysMenu menu : menus) {RouterVo router = new RouterVo();router.setHidden(false);router.setAlwaysShow(false);router.setPath(getRouterPath(menu));router.setComponent(menu.getComponent());router.setMeta(new MetaVo(menu.getName(), menu.getIcon()));// 下一层数据List children = menu.getChildren();if (menu.getType().intValue() == 1) {// 加载隐藏路由List hiddenMenuList = children.stream().filter(item -> !StringUtils.isEmpty(item.getComponent())).collect(Collectors.toList());for (SysMenu hiddenMenu : hiddenMenuList) {RouterVo hiddenRouter = new RouterVo();hiddenRouter.setHidden(true);hiddenRouter.setAlwaysShow(false);hiddenRouter.setPath(getRouterPath(hiddenMenu));hiddenRouter.setComponent(hiddenMenu.getComponent());hiddenRouter.setMeta(new MetaVo(hiddenMenu.getName(), hiddenMenu.getIcon()));routers.add(hiddenRouter);}}else {if (!CollectionUtils.isEmpty(children)) {if(children.size() > 0) {router.setAlwaysShow(true);}// 递归router.setChildren(buildRouter(children));}}routers.add(router);}return routers;}/*** 获取路由地址** @param menu 菜单信息* @return 路由地址*/public String getRouterPath(SysMenu menu) {String routerPath = "/" + menu.getPath();if (menu.getParentId().intValue() != 0) {routerPath = menu.getPath();}return routerPath;}// 根据 用户id 获取用户可以操作的按钮列表@Overridepublic List findUserPermsByUserId(Long userId) {// 1、判断是否是管理员,如果是管理员,查询所有按钮列表List sysMenusList = null;if (userId.longValue() == 1) {// 查询所有菜单列表LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(SysMenu::getStatus, 1);sysMenusList = baseMapper.selectList(queryWrapper);}else {// 2、如果不是管理员,根据userId查询可以操作按钮列表// 多表关联查询:sys_role、sys_role_menu、sys_menusysMenusList = baseMapper.findMenuListByUserId(userId);}// 3、从查询出来的数据里面,获取可以操作按钮值的List集合,返回List permsList = sysMenusList.stream().filter(item -> item.getType() == 2).map(item -> item.getPerms()).collect(Collectors.toList());return permsList;}

接口测试

登录接口测试

在这里插入图片描述

info接口测试

在这里插入图片描述

我这里没有报错,如果出现以下的报错信息

在这里插入图片描述

解决思路是:

在这里插入图片描述

1、在pom.xml添加

    org.springframework.bootspring-boot-maven-pluginsrc/main/java**/*.yml**/*.properties**/*.xmlfalsesrc/main/resources **/*.yml**/*.properties**/*.xmlfalse

2、application-dev.yml添加

mybatis-plus:mapper-locations: classpath:com/atguigu/auth/mapper/xml/*.xml

整合前端

从这部分开始,整合前端不在写了,比较麻烦,直接复用现有的

页面展示

给李四分配没有添加的权限

在这里插入图片描述

9.2、用户认证

整合SpringSecurity

本项目采用 Spring-Security 来做用户认证和权限控制,也可以采用 Shiro

新建一个spring-security的module

在这里插入图片描述

引入依赖

4.0.0com.jerrycommon1.0spring-securitycom.jerrycommon-util1.0org.springframework.bootspring-boot-starter-securityorg.springframework.bootspring-boot-starter-webprovided 

添加配置类

package com.jerry.security.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;/*** ClassName: WebSecurityConfig* Package: com.jerry.security.config* Description:** @Author jerry_jy* @Create 2023-03-03 13:44* @Version 1.0*/@Configuration
@EnableWebSecurity  //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig {
}

在 service-oa 中引入spring-security的module

在这里插入图片描述

测试

在浏览器访问:http://localhost:8800/admin/system/sysRole/getAll

在这里插入图片描述

这时候想绕过登录页是不能的,后台服务经过会spring-security做了用户认证,提示用户需要先登录

默认的登录名是:user

密码是IDEA中生成的一串随机字符,每次都不一样

在这里插入图片描述

用户认证

流程分析

在这里插入图片描述

自定义组件的编写

在这里插入图片描述

操作spring-securitymodule

自定义加密器PasswordEncoder
@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {public String encode(CharSequence rawPassword) {return MD5.encrypt(rawPassword.toString());}public boolean matches(CharSequence rawPassword, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));}
}
自定义用户对象UserDetails
public class CustomUser extends User {/*** 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)*/private SysUser sysUser;public CustomUser(SysUser sysUser, Collection authorities) {super(sysUser.getUsername(), sysUser.getPassword(), authorities);this.sysUser = sysUser;}public SysUser getSysUser() {return sysUser;}public void setSysUser(SysUser sysUser) {this.sysUser = sysUser;}
}
UserDetailsService
public interface UserDetailsService {/*** 根据用户名获取用户对象(获取不到直接抛异常)*/UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

操作service-oamodule

UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 根据用户名查询SysUser sysUser = sysUserService.getUserByUserName(username);if(null == sysUser) {throw new UsernameNotFoundException("用户名不存在!");}if(sysUser.getStatus().intValue() == 0) {throw new RuntimeException("账号已停用");}return new CustomUser(sysUser, Collections.emptyList());}
}

SysUserService

    SysUser getUserByUserName(String username);

SysUserServiceImpl

    // 根据用户名查询@Overridepublic SysUser getUserByUserName(String username) {LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(SysUser::getUsername,username);SysUser sysUser = baseMapper.selectOne(queryWrapper);return sysUser;}
自定义用户认证接口

TokenLoginFilter

package com.jerry.security.filter;import com.fasterxml.jackson.databind.ObjectMapper;
import com.jerry.common.jwt.JwtHelper;
import com.jerry.common.result.ResponseUtil;
import com.jerry.common.result.Result;
import com.jerry.common.result.ResultCodeEnum;
import com.jerry.security.custom.CustomUser;
import com.jerry.vo.system.LoginVo;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/*** ClassName: TokenLoginFilter 
* Package: com.jerry.security.filter
* Description: 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验** @Author: jerry_jy* @Create: 2023-03-03 15:29* @Version: 1.0*/public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {// 构造方法public TokenLoginFilter(AuthenticationManager authenticationManager){this.setAuthenticationManager(authenticationManager);this.setPostOnly(false);//指定登录接口及提交方式,可以指定任意路径this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));}// 登录认证过程// 获取输入的用户名和密码,调用方法认证@Overridepublic Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)throws AuthenticationException {try {// 获取用户信息LoginVo loginVo = new ObjectMapper().readValue(req.getInputStream(), LoginVo.class);//封装对象Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());//调用方法return this.getAuthenticationManager().authenticate(authenticationToken);} catch (IOException e) {throw new RuntimeException(e);}}// 认证成功调用的方法@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication auth) throws IOException, ServletException {// 获取当前用户CustomUser customUser = (CustomUser) auth.getPrincipal();// 生成tokenString token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());// 返回Map map = new HashMap<>();map.put("token", token);ResponseUtil.out(response, Result.ok(map));}// 认证失败调用的方法@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException e) throws IOException, ServletException {if(e.getCause() instanceof RuntimeException) {ResponseUtil.out(response, Result.build(null, ResultCodeEnum.DATA_ERROR));} else {ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_AUTH));}}}

common-util下的ResponseUtil

package com.jerry.common.result;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** ClassName: ResponseUtil 
* Package: com.jerry.common.result
* Description:** @Author: jerry_jy* @Create: 2023-03-03 15:55* @Version: 1.0*/ public class ResponseUtil {public static void out(HttpServletResponse response, Result r) {ObjectMapper mapper = new ObjectMapper();response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);try {mapper.writeValue(response.getWriter(), r);} catch (IOException e) {e.printStackTrace();}} }
认证解析token

因为用户登录状态在token中存储在客户端,所以每次请求接口请求头携带token, 后台通过自定义token过滤器拦截解析token完成认证并填充用户信息实体

package com.jerry.security.filter;import com.jerry.common.jwt.JwtHelper;
import com.jerry.common.result.ResponseUtil;
import com.jerry.common.result.Result;
import com.jerry.common.result.ResultCodeEnum;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;/*** ClassName: TokenAuthenticationFilter 
* Package: com.jerry.security.filter
* Description: 认证解析token过滤器** @Author: jerry_jy* @Create: 2023-03-03 16:01* @Version: 1.0*/public class TokenAuthenticationFilter extends OncePerRequestFilter {public TokenAuthenticationFilter() {}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {logger.info("uri:"+request.getRequestURI());//如果是登录接口,直接放行if("/admin/system/index/login".equals(request.getRequestURI())) {chain.doFilter(request, response);return;}UsernamePasswordAuthenticationToken authentication = getAuthentication(request);if(null != authentication) {SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(request, response);} else {ResponseUtil.out(response, Result.build(null, ResultCodeEnum.PERMISSION));}}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {// token置于header里String token = request.getHeader("token");logger.info("token:"+token);if (!StringUtils.isEmpty(token)) {String username = JwtHelper.getUsername(token);logger.info("username:"+username);if (!StringUtils.isEmpty(username)) {return new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());}}return null;} }
配置用户认证
package com.jerry.security.config;import com.jerry.security.custom.CustomMd5PasswordEncoder;
import com.jerry.security.filter.TokenAuthenticationFilter;
import com.jerry.security.filter.TokenLoginFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;/*** ClassName: WebSecurityConfig* Package: com.jerry.security.config* Description:** @Author jerry_jy* @Create 2023-03-03 13:44* @Version 1.0*/@Configuration
@EnableWebSecurity  //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService; // 装载的是 org.springframework.security.core.userdetails.UserDetailsService;@Autowiredprivate CustomMd5PasswordEncoder customMd5PasswordEncoder;@Bean@Overrideprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护http//关闭csrf跨站请求伪造.csrf().disable()// 开启跨域以便前端调用接口.cors().and().authorizeRequests()// 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的.antMatchers("/admin/system/index/login").permitAll()// 这里意思是其它所有接口需要认证才能访问.anyRequest().authenticated().and()//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。.addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).addFilter(new TokenLoginFilter(authenticationManager()));//禁用sessionhttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 指定UserDetailService和加密器auth.userDetailsService(userDetailsService).passwordEncoder(customMd5PasswordEncoder);}/*** 配置哪些请求不拦截* 排除swagger相关请求** @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/favicon.ico", "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");}
}

测试

说明:

1、我们是前后端分离项目,使用jwt生成token ,即用户状态保存在客户端中,前后端交互通过api接口 无session生成,所以我们不需要配置formLogin,session禁用

2、在浏览器访问:http://localhost:8800/admin/system/sysRole/getAll

在这里插入图片描述

9.3、用户权限控制

流程分析

在这里插入图片描述

修改代码

修改UserDetailsServiceImpl中的loadUserByUsername

在这里插入图片描述

// 根据 user_id 查询用户操作权限数据List userPermsList = sysMenuService.findUserPermsByUserId(sysUser.getId());// 创建list集合,封装最终权限数据List authList =  new ArrayList<>();// 遍历 authListfor (String perms : userPermsList) {authList.add(new SimpleGrantedAuthority(perms.trim()));}return new CustomUser(sysUser, authList);

spring-security模块配置redis

添加依赖

        org.springframework.bootspring-boot-starter-data-redis

修改TokenLoginFilter

在这里插入图片描述

在这里插入图片描述

修改TokenAuthenticationFilter

在这里插入图片描述

在这里插入图片描述

修改WebSecurityConfig类

配置类添加注解:

开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认

在这里插入图片描述

在这里插入图片描述

service-oa模块添加redis配置

application-dev.yml配文件

spring:redis:host: localhostport: 6379database: 0timeout: 1800000password:jedis:pool:max-active: 20 #最大连接数max-wait: -1    #最大阻塞等待时间(负数表示没限制)max-idle: 5    #最大空闲min-idle: 0     #最小空闲

控制controller层接口权限

Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,来判断用户对某个控制层的方法是否具有访问权限

    @PreAuthorize("hasAuthority('bnt.sysRole.list')")

在这里插入图片描述

    @PreAuthorize("hasAuthority('bnt.sysRole.add')")

在这里插入图片描述

    @PreAuthorize("hasAuthority('bnt.sysRole.list')")

在这里插入图片描述

    @PreAuthorize("hasAuthority('bnt.sysRole.update')")

在这里插入图片描述

    @PreAuthorize("hasAuthority('bnt.sysRole.remove')")

在这里插入图片描述

异常处理

在service-util模块引入依赖

        org.springframework.bootspring-boot-starter-securityprovided

AccessDeniedException需要引入依赖,Spring Security对应的异常

在这里插入图片描述

    /*** spring security异常* @param e* @return*/@ExceptionHandler(AccessDeniedException.class)@ResponseBodypublic Result error(AccessDeniedException e) throws AccessDeniedException {return Result.build(null, ResultCodeEnum.PERMISSION);}

测试

在这里插入图片描述

10、Activiti

10.1、Activiti流程操作

配置Activiti

引入Activiti依赖

service-oa


org.activitiactiviti-spring-boot-starter7.1.0.M6mybatisorg.mybatis

添加配置

application-dev.yml中添加如下配置

spring:    activiti:#    false:默认,数据库表不变,但是如果版本不对或者缺失表会抛出异常(生产使用)#    true:表不存在,自动创建(开发使用)#    create_drop: 启动时创建,关闭时删除表(测试使用)#    drop_create: 启动时删除表,在创建表 (不需要手动关闭引擎)database-schema-update: true#监测历史表是否存在,activities7默认不开启历史表db-history-used: true#none:不保存任何历史数据,流程中这是最高效的#activity:只保存流程实例和流程行为#audit:除了activity,还保存全部的流程任务以及其属性,audit为history默认值#full:除了audit、还保存其他全部流程相关的细节数据,包括一些流程参数history-level: full#校验流程文件,默认校验resources下的process 文件夹的流程文件check-process-definitions: true

重启项目

会自己创建数据表

在这里插入图片描述

使用activiti插件

下载activiti-explorer

官网下载:https://www.activiti.org/get-started

在这里插入图片描述

解压部署

把解压出来的activiti-explorer.war放在Tomcat的webapps

在这里插入图片描述

在这里插入图片描述

启动Tomcat服务器

访问activiti-explorer

http://localhost:8080/activiti-explorer

默认登录账号: kermit kermit

在这里插入图片描述

10.2、流程控制

绘制流程

请假流程审批绘制

新建

在这里插入图片描述

绘制

在这里插入图片描述

导出

在这里插入图片描述

下载文件

qingjia.bpmn20.xml




下载流程定义图片

单击右键上图图片,图片另存为:qingjia.png

在这里插入图片描述

将资源文件放入项目

在service-oa模块resources下新建process资源文件夹

将qingjia.bpmn20.xml与qingjia.png放入process目录

部署流程

package com.jerry.auth.activiti;import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;/*** ClassName: ProcessTest 
* Package: com.jerry.activiti
* Description:** @Author: jerry_jy* @Create: 2023-03-05 10:51* @Version: 1.0*/@SpringBootTest public class ProcessTest {@Autowiredprivate RepositoryService repositoryService;// 单个文件的部署@Testpublic void deployProcess() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/qingjia.bpmn20.xml").addClasspathResource("process/qingjia.png").name("请假申请流程").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());} }

在这里插入图片描述

流程实例

package com.jerry.auth.activiti;import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;/*** ClassName: ProcessTest 
* Package: com.jerry.activiti
* Description:** @Author: jerry_jy* @Create: 2023-03-05 10:51* @Version: 1.0*/@SpringBootTest public class ProcessTest1 {@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate TaskService taskService;@Autowiredprivate HistoryService historyService;// 单个流程实例挂起@Testpublic void SingleSuspendProcessInstance() {String processInstanceId = "71f6803b-bb19-11ed-a845-005056c00001";ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();//获取到当前流程定义是否为暂停状态 suspended方法为true代表为暂停 false就是运行的boolean suspended = processInstance.isSuspended();if (suspended) {runtimeService.activateProcessInstanceById(processInstanceId);System.out.println("流程实例:" + processInstanceId + "激活");} else {runtimeService.suspendProcessInstanceById(processInstanceId);System.out.println("流程实例:" + processInstanceId + "挂起");}}// 全部流程实例挂起@Testpublic void suspendProcessInstance() {// 1、获取流程定义对象ProcessDefinition qingjia = repositoryService.createProcessDefinitionQuery().processDefinitionKey("qingjia").singleResult();// 2、调用流程定义对象的方法判断当前状态:挂起 激活boolean suspended = qingjia.isSuspended();if (suspended) {// 暂定,那就可以激活// 参数1:流程定义的id 参数2:是否激活 参数3:时间点repositoryService.activateProcessDefinitionById(qingjia.getId(), true, null);System.out.println("流程定义:" + qingjia.getId() + "激活");} else {repositoryService.suspendProcessDefinitionById(qingjia.getId(), true, null);System.out.println("流程定义:" + qingjia.getId() + "挂起");}}/*** 启动流程实例,添加businessKey*/@Testpublic void startUpProcessAddBusinessKey(){// 启动流程实例,指定业务标识businessKey,也就是请假申请单idProcessInstance processInstance = runtimeService.startProcessInstanceByKey("qingjia","1001");// 输出System.out.println("业务id:"+processInstance.getBusinessKey()); //1001System.out.println("processInstance.getId() = " + processInstance.getId()); // 71f6803b-bb19-11ed-a845-005056c00001}/*** 查询流程定义*/@Testpublic void findProcessDefinitionList(){List definitionList = repositoryService.createProcessDefinitionQuery().orderByProcessDefinitionVersion().desc().list();//输出流程定义信息for (ProcessDefinition processDefinition : definitionList) {System.out.println("流程定义 id="+processDefinition.getId());System.out.println("流程定义 name="+processDefinition.getName());System.out.println("流程定义 key="+processDefinition.getKey());System.out.println("流程定义 Version="+processDefinition.getVersion());System.out.println("流程部署ID ="+processDefinition.getDeploymentId());}}/*** 删除流程定义*/@Testpublic void deleteDeployment() {//部署idString deploymentId = "qingjia:1:c493c327-bb02-11ed-8360-005056c00001"; // //删除流程定义,如果该流程定义已有流程实例启动则删除时出错 // repositoryService.deleteDeployment(deploymentId);//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式repositoryService.deleteDeployment(deploymentId, true);}// 查询已经处理的任务@Testpublic void findCompleteTaskList(){List list = historyService.createHistoricTaskInstanceQuery().taskAssignee("zhangsan").finished().list();for (HistoricTaskInstance historicTaskInstance : list) {System.out.println("流程实例id:" + historicTaskInstance.getProcessInstanceId());System.out.println("任务id:" + historicTaskInstance.getId());System.out.println("任务负责人:" + historicTaskInstance.getAssignee());System.out.println("任务名称:" + historicTaskInstance.getName());}}// 处理当前任务@Testpublic void completeTask(){// 查询负责人需要处理的任务,返回一条Task task = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();// 完成任务taskService.complete(task.getId());}// 查询个人的代办任务--zhangsan@Testpublic void findTaskList(){String assign = "zhangsan";List list = taskService.createTaskQuery().taskAssignee(assign).list();for (Task task : list) {System.out.println("task.getProcessInstanceId() = " + task.getProcessInstanceId());System.out.println("任务id:" + task.getId());System.out.println("任务负责人:" + task.getAssignee());System.out.println("任务名称:" + task.getName());}}// 启动流程实例@Testpublic void startProcess(){ProcessInstance processInstance = runtimeService.startProcessInstanceById("qingjia");System.out.println("processInstance.getProcessDefinitionId() = " + processInstance.getProcessDefinitionId());System.out.println("processInstance.getId() = " + processInstance.getId());System.out.println("processInstance.getActivityId() = " + processInstance.getActivityId());}// 单个文件的部署@Testpublic void deployProcess() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/qingjia.bpmn20.xml").addClasspathResource("process/qingjia.png").name("请假申请流程").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());} }

任务分配

package com.jerry.auth.activiti;import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** ClassName: ProcessTest2 
* Package: com.jerry.auth.activiti
* Description:** @Author: jerry_jy* @Create: 2023-03-05 14:05* @Version: 1.0*/@SpringBootTest public class ProcessTest2 {@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate TaskService taskService;@Autowiredprivate HistoryService historyService;///// 监听器分配任务// 部署流程定义@Testpublic void deployProcess02() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/jiaban02.bpmn20.xml").name("加班申请流程02").deploy();System.out.println("deploy.getId() = " + deploy.getId()); // ed080f00-bb41-11ed-a6f2-005056c00001System.out.println("deploy.getName() = " + deploy.getName()); // 加班申请流程02}@Testpublic void startProcessInstance02(){ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban02");System.out.println("processInstance.getProcessDefinitionId() = " + processInstance.getProcessDefinitionId()); // jiaban02:1:ed150752-bb41-11ed-a6f2-005056c00001System.out.println("processInstance.getId() = " + processInstance.getId()); // 06eca124-bb42-11ed-9bbc-005056c00001}// 查询个人的代办任务--Tim@Testpublic void findTaskList02(){String assign = "Tim";List list = taskService.createTaskQuery().taskAssignee(assign).list();for (Task task : list) {System.out.println("task.getProcessInstanceId() = " + task.getProcessInstanceId()); // 06eca124-bb42-11ed-9bbc-005056c00001System.out.println("任务id:" + task.getId()); // 06f071b8-bb42-11ed-9bbc-005056c00001System.out.println("任务负责人:" + task.getAssignee()); // TimSystem.out.println("任务名称:" + task.getName()); // 经理审批}}///// uel-method// 部署流程定义@Testpublic void deployProcess01() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/jiaban01.bpmn20.xml").name("加班申请流程01").deploy();System.out.println("deploy.getId() = " + deploy.getId()); // 8c4ac05e-bb20-11ed-8d65-005056c00001System.out.println("deploy.getName() = " + deploy.getName()); // 加班申请流程01}@Testpublic void startProcessInstance01(){ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban01");System.out.println("processInstance.getProcessDefinitionId() = " + processInstance.getProcessDefinitionId()); // jiaban01:1:8c56a740-bb20-11ed-8d65-005056c00001System.out.println("processInstance.getId() = " + processInstance.getId()); // abb9c7c4-bb20-11ed-b608-005056c00001}// 查询个人的代办任务--LiLei@Testpublic void findTaskList01(){String assign = "LiLei";List list = taskService.createTaskQuery().taskAssignee(assign).list();for (Task task : list) {System.out.println("task.getProcessInstanceId() = " + task.getProcessInstanceId()); // abb9c7c4-bb20-11ed-b608-005056c00001System.out.println("任务id:" + task.getId()); // abbd4a38-bb20-11ed-b608-005056c00001System.out.println("任务负责人:" + task.getAssignee()); // LiLeiSystem.out.println("任务名称:" + task.getName()); // 经理审批}}///// uel-value// 部署流程定义@Testpublic void deployProcess() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/jiaban.bpmn20.xml").name("加班申请流程").deploy();System.out.println("deploy.getId() = " + deploy.getId()); // 5c5519ad-bb1d-11ed-b5c8-005056c00001System.out.println("deploy.getName() = " + deploy.getName()); // 加班申请流程}// 启动流程实例@Testpublic void startProcessInstance() {Map map = new HashMap<>();// 设置任务人map.put("assignee1","tom");map.put("assignee2","jerry");ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban", map);System.out.println("processInstance.getProcessDefinitionId() = " + processInstance.getProcessDefinitionId()); // jiaban:1:5c60d97f-bb1d-11ed-b5c8-005056c00001System.out.println("processInstance.getId() = " + processInstance.getId()); // 7f720dd9-bb1d-11ed-b6e9-005056c00001}// 查询个人的代办任务--tom@Testpublic void findTaskList(){String assign = "tom";List list = taskService.createTaskQuery().taskAssignee(assign).list();for (Task task : list) {System.out.println("task.getProcessInstanceId() = " + task.getProcessInstanceId()); //7f720dd9-bb1d-11ed-b6e9-005056c00001System.out.println("任务id:" + task.getId()); // 7f759051-bb1d-11ed-b6e9-005056c00001System.out.println("任务负责人:" + task.getAssignee()); // tomSystem.out.println("任务名称:" + task.getName()); // 经理审批}}}

配置监听器

package com.jerry.auth.activiti;import org.springframework.stereotype.Component;/*** ClassName: UserBean 
* Package: com.jerry.auth.activiti
* Description:** @Author: jerry_jy* @Create: 2023-03-05 14:25* @Version: 1.0*/@Component public class UserBean {public String getUsername(int id) {if (id == 1) {return "LiLei";}if (id == 2) {return "HanMeiMei";}return "admin";} }

任务组

package com.jerry.auth.activiti;import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** ClassName: ProcessTest3 
* Package: com.jerry.auth.activiti
* Description:** @Author: jerry_jy* @Create: 2023-03-05 19:18* @Version: 1.0*/@SpringBootTest public class ProcessTest3 {@Autowiredprivate RepositoryService repositoryService;@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate TaskService taskService;// 1、部署流程定义@Testpublic void deployProcess() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/jiaban04.bpmn20.xml").name("加班申请流程04").deploy();System.out.println("deploy.getId() = " + deploy.getId()); // f204be8a-bb48-11ed-950e-005056c00001System.out.println("deploy.getName() = " + deploy.getName()); // 加班申请流程04}// 1.5、启动流程实例@Testpublic void startProcessInstance() { // Map map = new HashMap<>();// 设置任务人 // map.put("assignee1","tom"); // map.put("assignee2","jerry");ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban04");System.out.println("processInstance.getProcessDefinitionId() = " + processInstance.getProcessDefinitionId()); // jiaban04:1:f210f38c-bb48-11ed-950e-005056c00001System.out.println("processInstance.getId() = " + processInstance.getId()); // 428d0c0f-bb49-11ed-83a5-005056c00001}// 2、查询组任务@Testpublic void findGroupTaskList(){List list = taskService.createTaskQuery().taskCandidateUser("tom").list();for (Task task : list) {System.out.println("----------------------------");System.out.println("流程实例id:" + task.getProcessInstanceId());System.out.println("任务id:" + task.getId());System.out.println("任务负责人:" + task.getAssignee());System.out.println("任务名称:" + task.getName());}}// 3、分配组任务@Testpublic void claimTask(){Task task = taskService.createTaskQuery().taskCandidateUser("tom").singleResult();if (task!=null){taskService.claim(task.getId(),"tom");System.out.println("分配任务完成");}}// 4、查询个人的代办任务--tom@Testpublic void findTaskList(){String assign = "tom";List list = taskService.createTaskQuery().taskAssignee(assign).list();for (Task task : list) {System.out.println("task.getProcessInstanceId() = " + task.getProcessInstanceId()); //7f720dd9-bb1d-11ed-b6e9-005056c00001System.out.println("任务id:" + task.getId()); // 7f759051-bb1d-11ed-b6e9-005056c00001System.out.println("任务负责人:" + task.getAssignee()); // tomSystem.out.println("任务名称:" + task.getName()); // 经理审批}}// 5、办理个人任务@Testpublic void completeGroupTask() {Task task = taskService.createTaskQuery().taskAssignee("tom") //要查询的负责人.singleResult();//返回一条taskService.complete(task.getId());} }

10.3、网关

网关用来控制流程的流向,通常会和流程变量一起使用。

排他网关

  • 排他网关:只有一条路径会被选择

当你的流程出现这样的场景:请假申请,两天以内,部门经理审批流程就结束了,两天以上需要总经理直接审批,这个时候就需要排他网关

在这里插入图片描述

并行网关

  • 并(平)行网关:所有路径会被同时选择

当出现这样的场景:请假申请开始,需要部门经理和总经理都审批,两者没有前后需要两个人全部审批才能进入下个节点人事审批。这个时候就需要并行网关

在这里插入图片描述

与排他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。

包含网关

包容网关:可以同时执行多条线路,也可以在网关上设置条件,可以看做是排他网关和并行网关的结合体。
当出现这样的场景:请假申请大于等于2天需要由部门总经理审批,小于2天由部门经理审批,请假申请必须经过人事经理审批。这个时候就需要包含网关

在这里插入图片描述

package com.jerry.auth.activiti;import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** ClassName: ProcessTestGateway 
* Package: com.jerry.auth.activiti
* Description:** @Author: jerry_jy* @Create: 2023-03-05 19:52* @Version: 1.0*/ @SpringBootTest public class ProcessTestGateway {@Autowiredprivate RepositoryService repositoryService;//注入RuntimeService@Autowiredprivate RuntimeService runtimeService;@Autowiredprivate TaskService taskService;@Autowiredprivate HistoryService historyService;//1 部署流程定义@Testpublic void deployProcess() {Deployment deployment = repositoryService.createDeployment().addClasspathResource("process/qingjia003.bpmn20.xml").name("请假申请流程003").deploy();System.out.println(deployment.getId()); // af9242f0-bb4c-11ed-85bf-005056c00001System.out.println(deployment.getName()); // 请假申请流程002}//2 启动流程实例@Testpublic void startProcessInstance() {Map map = new HashMap<>();//设置请假天数map.put("day", "3");ProcessInstance processInstance = // runtimeService.startProcessInstanceByKey("qingjia002", map);runtimeService.startProcessInstanceByKey("qingjia003");System.out.println(processInstance.getProcessDefinitionId()); // qingjia002:1:afac0c82-bb4c-11ed-85bf-005056c00001System.out.println(processInstance.getId()); // 90d46e2c-bb4d-11ed-9b92-005056c00001}//3 查询个人的代办任务--zhao6@Testpublic void findTaskList() {// String assign = "zhao6"; // String assign = "gousheng"; // String assign = "xiaocui"; // String assign = "wang5"; // String assign = "gouwa";String assign = "xiaoli";List list = taskService.createTaskQuery().taskAssignee(assign).list();for (Task task : list) {System.out.println("流程实例id:" + task.getProcessInstanceId());System.out.println("任务id:" + task.getId());System.out.println("任务负责人:" + task.getAssignee());System.out.println("任务名称:" + task.getName());}}//完成任务@Testpublic void completeTask() {Task task = taskService.createTaskQuery() // .taskAssignee("zhao6") //要查询的负责人 // .taskAssignee("xiaocui") //要查询的负责人 // .taskAssignee("gousheng") // .taskAssignee("wang5").taskAssignee("gouwa").singleResult();//返回一条//完成任务,参数:任务idtaskService.complete(task.getId());} }

在这里插入图片描述

11、审批管理

在这里插入图片描述

11.1、审批设置–CRUD

package com.jerry.process.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jerry.common.result.Result;
import com.jerry.model.process.ProcessType;
import com.jerry.process.service.OaProcessTypeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;/*** 

* 审批类型 前端控制器*

** @author jerry* @since 2023-03-05*/ @Api(value = "审批类型", tags = "审批类型") @RestController @RequestMapping(value = "/admin/process/processType") public class OaProcessTypeController {@Autowiredprivate OaProcessTypeService processTypeService;@ApiOperation(value = "获取分页列表")@GetMapping("{page}/{pageSize}")public Result index(@PathVariable Long page, @PathVariable Long pageSize) {Page pageInfo = new Page<>(page, pageSize);Page pageModel = processTypeService.page(pageInfo);return Result.ok(pageModel);}@PreAuthorize("hasAuthority('bnt.processType.list')")@ApiOperation(value = "获取")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {ProcessType processType = processTypeService.getById(id);return Result.ok(processType);}@PreAuthorize("hasAuthority('bnt.processType.add')")@ApiOperation(value = "新增")@PostMapping("save")public Result save(@RequestBody ProcessType processType) {processTypeService.save(processType);return Result.ok();}@PreAuthorize("hasAuthority('bnt.processType.update')")@ApiOperation(value = "修改")@PutMapping("update")public Result updateById(@RequestBody ProcessType processType) {processTypeService.updateById(processType);return Result.ok();}@ApiOperation(value = "删除")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {processTypeService.removeById(id);return Result.ok();}}

在这里插入图片描述

11.2、模板审批–CRUD

package com.jerry.process.controller;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jerry.common.result.Result;
import com.jerry.model.process.ProcessTemplate;
import com.jerry.process.service.OaProcessTemplateService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** 

* 审批模板 前端控制器*

** @author jerry* @since 2023-03-05*/ @Api(value = "审批模板管理", tags = "审批模板管理") @RestController @RequestMapping(value = "/admin/process/processTemplate") public class OaProcessTemplateController {@Autowiredprivate OaProcessTemplateService processTemplateService;// 分页查询审批模板@ApiOperation("获取分页查询审批模板数据")@GetMapping("{page}/{pageSize}")public Result index(@PathVariable Long page, @PathVariable Long pageSize){Page pageInfo = new Page<>(page, pageSize);//分页查询审批模板,把审批类型对应名称查询IPage pageModel =processTemplateService.selectPageProcessTemplate(pageInfo);return Result.ok(pageModel);}//@PreAuthorize("hasAuthority('bnt.processTemplate.list')")@ApiOperation(value = "获取")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {ProcessTemplate processTemplate = processTemplateService.getById(id);return Result.ok(processTemplate);}//@PreAuthorize("hasAuthority('bnt.processTemplate.templateSet')")@ApiOperation(value = "新增")@PostMapping("save")public Result save(@RequestBody ProcessTemplate processTemplate) {processTemplateService.save(processTemplate);return Result.ok();}//@PreAuthorize("hasAuthority('bnt.processTemplate.templateSet')")@ApiOperation(value = "修改")@PutMapping("update")public Result updateById(@RequestBody ProcessTemplate processTemplate) {processTemplateService.updateById(processTemplate);return Result.ok();}//@PreAuthorize("hasAuthority('bnt.processTemplate.remove')")@ApiOperation(value = "删除")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {processTemplateService.removeById(id);return Result.ok();}}

11.3、添加审批模板

OaProcessTypeController

@ApiOperation(value = "获取全部审批分类")@GetMapping("findAll")public Result findAll() {return Result.ok(processTypeService.list());}

OaProcessTemplateController

@ApiOperation(value = "上传流程定义")@PostMapping("/uploadProcessDefinition")public Result uploadProcessDefinition(MultipartFile file) throws FileNotFoundException {// 获取classes目录位置String path = new File(ResourceUtils.getURL("classpath:").getPath()).getAbsolutePath();// 设置上传文件夹File tempFile = new File(path + "/processes/");if (!tempFile.exists()) {tempFile.mkdirs();}// 创建空文件,实现文件写入String filename = file.getOriginalFilename();File zipFile = new File(path + "/processes/" + filename);// 保存文件try {file.transferTo(zipFile);} catch (IOException e) {return Result.fail();}Map map = new HashMap<>();//根据上传地址后续部署流程定义,文件名称为流程定义的默认keymap.put("processDefinitionPath", "processes/" + filename);map.put("processDefinitionKey", filename.substring(0, filename.lastIndexOf(".")));return Result.ok(map);}public static void main(String[] args) {try {String path = new File(ResourceUtils.getURL("classpath:").getPath()).getAbsolutePath();System.out.println("path = " + path); //E:\CodeLife\IdeaProject\guigu-oa\guigu-oa-parent\service-oa\target\classes} catch (FileNotFoundException e) {throw new RuntimeException(e);}}

在这里插入图片描述

11.4、查看审批模板

整合前端,无后台接口

在这里插入图片描述

11.5、审批列表

分页查询

OaProcessController

package com.jerry.process.controller;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jerry.common.result.Result;
import com.jerry.model.process.Process;
import com.jerry.process.service.OaProcessService;
import com.jerry.vo.process.ProcessQueryVo;
import com.jerry.vo.process.ProcessVo;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/*** 

* 审批类型 前端控制器*

** @author jerry* @since 2023-03-06*/ @RestController @RequestMapping(value = "/admin/process") public class OaProcessController {@Autowiredprivate OaProcessService processService;//审批管理列表@ApiOperation(value = "获取分页列表")@GetMapping("{page}/{limit}")public Result index(@PathVariable Long page,@PathVariable Long limit,ProcessQueryVo processQueryVo) {Page pageInfo = new Page<>(page, limit);IPage pageModel = processService.selectPage(pageInfo,processQueryVo);return Result.ok();} }

OaProcessService

public interface OaProcessService extends IService {//审批管理列表IPage selectPage(Page pageInfo, ProcessQueryVo processQueryVo);
}

OaProcessServiceImpl

    //审批管理列表@Overridepublic IPage selectPage(Page pageInfo, ProcessQueryVo processQueryVo) {IPage pageModel =  baseMapper.selectPage(pageInfo,processQueryVo);return pageModel;}

OaProcessMapper

    //审批管理列表IPage selectPage(Page pageInfo, @Param("vo") ProcessQueryVo processQueryVo);

涉及到4张表的多表查询,自己编写SQL语句

OaProcessMapper.xml





修改mapper的映射路径

在这里插入图片描述

页面展示

在这里插入图片描述

部署流程定义

OaProcessTemplateServiceImpl

    // 修改模板的发布状态 status==1 代表已发布// 流程定义部署@Overridepublic void publish(Long id) {// 修改模板的发布状态 status==1 代表已发布ProcessTemplate processTemplate = baseMapper.selectById(id);processTemplate.setStatus(1);baseMapper.updateById(processTemplate);// 流程定义部署if (StringUtils.isEmpty(processTemplate.getProcessDefinitionPath())){processService.deployByZip(processTemplate.getProcessDefinitionPath());}}
}

OaProcessService

    // 流程定义部署void deployByZip(String deployPath);

OaProcessServiceImpl

    // 流程定义部署@Overridepublic void deployByZip(String deployPath) {InputStream inputStream= this.getClass().getClassLoader().getResourceAsStream(deployPath);ZipInputStream zipInputStream = new ZipInputStream(inputStream);// 部署Deployment deployment = repositoryService.createDeployment().addZipInputStream(zipInputStream).deploy();System.out.println("deployment.getId() = " + deployment.getId());System.out.println("deployment.getName() = " + deployment.getName());}

12、前端审批

12.1、OA审批

在这里插入图片描述

node -v
v 16.16.0

报错

npm ERR! path F:\guigu-oa\guigu-oa-web\node_modules\node-sass
npm ERR! command failed
npm ERR! command C:\WINDOWS\system32\cmd.exe /d /s /c node scripts/build.js
npm ERR! Building: E:\nodejs\node.exe F:\guigu-oa\guigu-oa-web\node_modules\node-gyp\bin\node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
npm ERR! gyp info it worked if it ends with ok
npm ERR! gyp verb cli [
npm ERR! gyp verb cli   'E:\\nodejs\\node.exe',
npm ERR! gyp verb cli   'F:\\guigu-oa\\guigu-oa-web\\node_modules\\node-gyp\\bin\\node-gyp.js',
npm ERR! gyp verb cli   'rebuild',
npm ERR! gyp verb cli   '--verbose',
npm ERR! gyp verb cli   '--libsass_ext=',
npm ERR! gyp verb cli   '--libsass_cflags=',
npm ERR! gyp verb cli   '--libsass_ldflags=',
npm ERR! gyp verb cli   '--libsass_library='
npm ERR! gyp verb cli ]
npm ERR! gyp info using node-gyp@3.8.0
npm ERR! gyp info using node@16.16.0 | win32 | x64
npm ERR! gyp verb command rebuild []
npm ERR! gyp verb command clean []
npm ERR! gyp verb clean removing "build" directory

在这里插入图片描述

nodejs版本过高,与node-sass不兼容,降级版本

v14.15.0

npm install没问题

在这里插入图片描述

13、代码托管

Git

在这里插入图片描述

Gitee

https://gitee.com/jinyang-jy/OnlineOfficeSystem.git

GitHub

网盘资料

链接:https://pan.baidu.com/s/1ZVNqzPlcfMH89NgUYNYZtQ?pwd=2022
提取码:2022

相关内容

热门资讯

122.(leaflet篇)l... 听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
Vue使用pdf-lib为文件... 之前也写过两篇预览pdf的,但是没有加水印,这是链接:Vu...
PyQt5数据库开发1 4.1... 文章目录 前言 步骤/方法 1 使用windows身份登录 2 启用混合登录模式 3 允许远程连接服...
Android studio ... 解决 Android studio 出现“The emulator process for AVD ...
Linux基础命令大全(上) ♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维...
再谈解决“因为文件包含病毒或潜... 前面出了一篇博文专门来解决“因为文件包含病毒或潜在的垃圾软件”的问题,其中第二种方法有...
南京邮电大学通达学院2023c... 题目展示 一.问题描述 实验题目1 定义一个学生类,其中包括如下内容: (1)私有数据成员 ①年龄 ...
PageObject 六大原则 PageObject六大原则: 1.封装服务的方法 2.不要暴露页面的细节 3.通过r...
【Linux网络编程】01:S... Socket多进程 OVERVIEWSocket多进程1.Server2.Client3.bug&...
数据结构刷题(二十五):122... 1.122. 买卖股票的最佳时机 II思路:贪心。把利润分解为每天为单位的维度,然后收...
浏览器事件循环 事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间࿰...
8个免费图片/照片压缩工具帮您... 继续查看一些最好的图像压缩工具,以提升用户体验和存储空间以及网站使用支持。 无数图像压...
计算机二级Python备考(2... 目录  一、选择题 1.在Python语言中: 2.知识点 二、基本操作题 1. j...
端电压 相电压 线电压 记得刚接触矢量控制的时候,拿到板子,就赶紧去测各种波形,结...
如何使用Python检测和识别... 车牌检测与识别技术用途广泛,可以用于道路系统、无票停车场、车辆门禁等。这项技术结合了计...
带环链表详解 目录 一、什么是环形链表 二、判断是否为环形链表 2.1 具体题目 2.2 具体思路 2.3 思路的...
【C语言进阶:刨根究底字符串函... 本节重点内容: 深入理解strcpy函数的使用学会strcpy函数的模拟实现⚡strc...
Django web开发(一)... 文章目录前端开发1.快速开发网站2.标签2.1 编码2.2 title2.3 标题2.4 div和s...