云上办公系统项目

1、云上办公系统1.1、介绍1.2、核心技术1.3、开发环境说明1.4、产品展示后台前台

1.5、 个人总结

2、后端环境搭建2.1、建库建表2.2、创建Maven项目pom文件guigu-oa-parentcommoncommon-utilservice-utilmodelservice-oa

配置数据源、服务器端口号application.ymlapplication-dev.yml

导入实体类

2.3、编写代码启动类

3、后端角色管理3.1、查询所有角色SysRoleMapperSysRoleServiceSysRoleServiceImpl编写测试类编写统一结果返回类ResultCodeEnumResult

SysRoleController测试

3.2、集成knife4jSwagger介绍目的使用步骤添加依赖添加knife4j配置类Controller层添加注解测试

3.3、分页查询所有角色MybatisPlusConfig主启动类上添加包扫描SysRoleController测试

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

4、统一异常处理4.1、全局异常处理4.2、特定异常处理4.3、自定义异常处理GlobalExceptionHandlerGuiguException

5、前端环境搭建安装脚手架工程前后联调的流程修改前端的IP地址编写后台登录/登出的请求修改前端的跳转地址修改响应状态码测试

6、前端角色管理6.1、角色列表修改路由创建角色页面定义角色管理相关的API请求函数测试

6.2、角色删除sysRole.jslist.vue

6.3、角色添加6.4、角色修改与数据回显6.5、批量删除sysRole.jslist.vue页面展示

7、用户管理7.1、用户管理CRUD需求分析代码生成器编写代码测试整合前端前端页面 list.vue添加路由定义API接口

页面展示

7.2、用户管理分配角色需求分析接口分析编写代码前端展示

7.3、修改用户状态需求分析编写代码整合前端定义前端路由修改前端页面

页面展示

8、菜单管理8.1、菜单管理CRUD需求分析编写代码接口测试整合前端sysMenu.jslist.vue

页面展示

8.2、角色分配菜单功能需求分析编写代码整合前端router/index.jssysRole/list.vuesysMenu.jsassignAuth.vue

页面展示

9、权限管理(重难点)9.1、用户登录权限管理需求分析引入JWT修改用户登录先引入MD5工具类修改SysUserControler保存用户的方法修改IndexController的登录方法SysMenuServiceSysMenuServiceImpl

接口测试登录接口测试info接口测试

整合前端页面展示

9.2、用户认证整合SpringSecurity引入依赖添加配置类测试

用户认证流程分析自定义组件的编写自定义加密器PasswordEncoder自定义用户对象UserDetailsUserDetailsServiceUserDetailsServiceImpl自定义用户认证接口认证解析token配置用户认证

测试

9.3、用户权限控制流程分析修改代码spring-security模块配置redis修改TokenLoginFilter修改TokenAuthenticationFilter修改WebSecurityConfig类service-oa模块添加redis配置控制controller层接口权限异常处理

测试

10、Activiti10.1、Activiti流程操作配置Activiti引入Activiti依赖添加配置重启项目

使用activiti插件下载activiti-explorer解压部署访问activiti-explorer

10.2、流程控制绘制流程新建绘制导出下载文件

部署流程流程实例任务分配任务组

10.3、网关排他网关并行网关包含网关

11、审批管理11.1、审批设置--CRUD11.2、模板审批--CRUD11.3、添加审批模板11.4、查看审批模板11.5、审批列表分页查询页面展示部署流程定义

12、前端审批12.1、OA审批

13、代码托管GitGiteeGitHub网盘资料

申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计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.27Build 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

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.3.6.RELEASE

com.jerry

guigu-oa-parent

1.0

pom

common

model

service-oa

1.8

3.4.1

8.0.27

3.0.3

0.9.1

2.0.21

com.baomidou

mybatis-plus-boot-starter

${mybatis-plus.version}

mysql

mysql-connector-java

${mysql.version}

com.github.xiaoymin

knife4j-spring-boot-starter

${knife4j.version}

io.jsonwebtoken

jjwt

${jwt.version}

com.alibaba

fastjson

${fastjson.version}

org.apache.maven.plugins

maven-compiler-plugin

3.1

1.8

1.8

common

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

4.0.0

com.jerry

guigu-oa-parent

1.0

common

pom

common-util

service-util

common-util

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

4.0.0

com.jerry

common

1.0

common-util

jar

org.springframework.boot

spring-boot-starter-web

provided

io.jsonwebtoken

jjwt

org.projectlombok

lombok

com.alibaba

fastjson

service-util

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

4.0.0

com.jerry

common

1.0

service-util

com.jerry

common-util

1.0

org.springframework.boot

spring-boot-starter-web

com.baomidou

mybatis-plus-boot-starter

mysql

mysql-connector-java

model

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

4.0.0

com.jerry

guigu-oa-parent

1.0

model

org.projectlombok

lombok

com.github.xiaoymin

knife4j-spring-boot-starter

provided

com.baomidou

mybatis-plus-boot-starter

provided

service-oa

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

4.0.0

com.jerry

guigu-oa-parent

1.0

service-oa

jar

com.jerry

model

1.0

com.jerry

service-util

1.0

org.springframework.boot

spring-boot-starter-test

test

${project.artifactId}

org.springframework.boot

spring-boot-maven-plugin

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

application.yml

spring:

application:

name: service-oa

profiles:

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

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3306/guigu-oa?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8

username: root

password: root

导入实体类

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方法就行

@Autowired

private SysRoleMapper sysRoleMapper;

@Autowired

private SysRoleService sysRoleService;

// 使用MP 封装的 service 来操作数据库,查询所有记录

@Test

public void getAllByService(){

List list = sysRoleService.list();

list.forEach(System.out::println);

}

// 使用MP 封装的 mapper查询所有记录

@Test

public void getAllByMapper(){

List sysRoles = sysRoleMapper.selectList(null);

sysRoles.forEach(System.out::println);

}

// 添加操作

@Test

public 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自动回填

}

// 修改操作

@Test

public void updateById(){

// 根据id查询

SysRole sysRole = sysRoleMapper.selectById(9);

// 设置修改值

sysRole.setRoleName("角色管理员1");

// 调用方法实现最终修改

int update = sysRoleMapper.updateById(sysRole);

System.out.println("update = " + update); // 受影响的行数

}

// 根据id删除

@Test

public void deleteById(){

int delete = sysRoleMapper.deleteById(9);

System.out.println("delete = " + delete); // 受影响的行数

}

// 批量删除

@Test

public void deleteBatchByIds(){

// int delete = sysRoleMapper.delete(null);

// 或者下面这种写法

int delete = sysRoleMapper.deleteBatchIds(Arrays.asList(1, 2));

System.out.println("ids = " + delete); // 受影响的行数

}

// 条件查询

@Test

public 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;

}

// 构造私有化 外部不能new

private 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 {

@Autowired

private 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.xiaoymin

knife4j-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 {

@Bean

public 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参数end

Docket 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 避免缓存出现问题(该属性会在旧插件移除后一同移除)

*/

@Bean

public MybatisPlusInterceptor mybatisPlusInterceptor() {

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

return interceptor;

}

@Bean

public ConfigurationCustomizer configurationCustomizer() {

return configuration -> configuration.setUseDeprecatedExecutor(false);

}

}

主启动类上添加包扫描

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:ss

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

@ResponseBody

public Result error(Exception e){

e.printStackTrace();

return Result.fail().message("执行全局处理异常...");

}

/**

* 特定异常处理

* @param e

* @return

*/

@ExceptionHandler(ArithmeticException.class)

@ResponseBody

public Result error(ArithmeticException e){

e.printStackTrace();

return Result.fail().message("执行特定处理异常...");

}

/**

* 自定义异常处理

* @param e

* @return

*/

@ExceptionHandler(GuiguException.class)

@ResponseBody

public 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();

}

@Override

public 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) {

// debugger

this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {

confirmButtonText: '确定',

cancelButtonText: '取消',

type: 'warning'

}).then(() => { // promise

// 点击确定,远程调用ajax

return api.removeById(id)

}).then((response) => {

// 刷新页面

this.fetchData(this.page)

// 提示信息

this.$message.success(response.message || '删除成功')

})

}

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提供的代码生成器直接生成 mapper,service,impl,controller,手动创建的话,也行

service-oa

com.baomidou

mybatis-plus-generator

3.4.1

org.apache.velocity

velocity-engine-core

2.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接口的首字母I

gc.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 {

@Autowired

private 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 {

@Autowired

private SysUserRoleService sysUserRoleService;

//1 查询所有角色 和 当前用户所属角色

@Override

public Map findRoleDataByUserId(Long userId) {

//1 查询所有角色,返回list集合,返回

List allRoleList =

baseMapper.selectList(null);

//2 根据userid查询 角色用户关系表,查询userid对应所有角色id

LambdaQueryWrapper 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 为用户分配角色

@Override

public 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

@Transactional

public 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 {

@Override

public List findNodes() {

// 1、查询所有 的数据

List sysMenuList = baseMapper.selectList(null);

// 2、构建树形结构

List list = MenuHelper.buildTree(sysMenuList);

return list;

}

// 删除菜单

@Override

public 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 = 0

if (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 {

@Autowired

private 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.jsonwebtoken

jjwt

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 {

@Autowired

private SysUserService sysUserService;

@Autowired

private 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 获取用户可以操作的菜单列表

@Override

public 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_menu

sysMenusList = 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 获取用户可以操作的按钮列表

@Override

public 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_menu

sysMenusList = 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.boot

spring-boot-maven-plugin

src/main/java

**/*.yml

**/*.properties

**/*.xml

false

src/main/resources

**/*.yml

**/*.properties

**/*.xml

false

2、application-dev.yml添加

mybatis-plus:

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

整合前端

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

页面展示

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

9.2、用户认证

整合SpringSecurity

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

新建一个spring-security的module

引入依赖

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

4.0.0

com.jerry

common

1.0

spring-security

com.jerry

common-util

1.0

org.springframework.boot

spring-boot-starter-security

org.springframework.boot

spring-boot-starter-web

provided

添加配置类

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 {

@Autowired

private SysUserService sysUserService;

@Override

public 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

// 根据用户名查询

@Override

public 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"));

}

// 登录认证过程

// 获取输入的用户名和密码,调用方法认证

@Override

public 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);

}

}

// 认证成功调用的方法

@Override

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,

Authentication auth) throws IOException, ServletException {

// 获取当前用户

CustomUser customUser = (CustomUser) auth.getPrincipal();

// 生成token

String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());

// 返回

Map map = new HashMap<>();

map.put("token", token);

ResponseUtil.out(response, Result.ok(map));

}

// 认证失败调用的方法

@Override

protected 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() {

}

@Override

protected 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 {

@Autowired

private UserDetailsService userDetailsService; // 装载的是 org.springframework.security.core.userdetails.UserDetailsService;

@Autowired

private CustomMd5PasswordEncoder customMd5PasswordEncoder;

@Bean

@Override

protected AuthenticationManager authenticationManager() throws Exception {

return super.authenticationManager();

}

@Override

protected 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()));

//禁用session

http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

}

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

// 指定UserDetailService和加密器

auth.userDetailsService(userDetailsService)

.passwordEncoder(customMd5PasswordEncoder);

}

/**

* 配置哪些请求不拦截

* 排除swagger相关请求

*

* @param web

* @throws Exception

*/

@Override

public 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<>();

// 遍历 authList

for (String perms : userPermsList) {

authList.add(new SimpleGrantedAuthority(perms.trim()));

}

return new CustomUser(sysUser, authList);

spring-security模块配置redis

添加依赖

org.springframework.boot

spring-boot-starter-data-redis

修改TokenLoginFilter

修改TokenAuthenticationFilter

修改WebSecurityConfig类

配置类添加注解:

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

service-oa模块添加redis配置

application-dev.yml配文件

spring:

redis:

host: localhost

port: 6379

database: 0

timeout: 1800000

password:

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

spring-boot-starter-security

provided

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

/**

* spring security异常

* @param e

* @return

*/

@ExceptionHandler(AccessDeniedException.class)

@ResponseBody

public Result error(AccessDeniedException e) throws AccessDeniedException {

return Result.build(null, ResultCodeEnum.PERMISSION);

}

测试

10、Activiti

10.1、Activiti流程操作

配置Activiti

引入Activiti依赖

在service-oa中

org.activiti

activiti-spring-boot-starter

7.1.0.M6

mybatis

org.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 {

@Autowired

private RepositoryService repositoryService;

// 单个文件的部署

@Test

public 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 {

@Autowired

private RepositoryService repositoryService;

@Autowired

private RuntimeService runtimeService;

@Autowired

private TaskService taskService;

@Autowired

private HistoryService historyService;

// 单个流程实例挂起

@Test

public 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 + "挂起");

}

}

// 全部流程实例挂起

@Test

public 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

*/

@Test

public void startUpProcessAddBusinessKey(){

// 启动流程实例,指定业务标识businessKey,也就是请假申请单id

ProcessInstance processInstance = runtimeService.

startProcessInstanceByKey("qingjia","1001");

// 输出

System.out.println("业务id:"+processInstance.getBusinessKey()); //1001

System.out.println("processInstance.getId() = " + processInstance.getId()); // 71f6803b-bb19-11ed-a845-005056c00001

}

/**

* 查询流程定义

*/

@Test

public 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());

}

}

/**

* 删除流程定义

*/

@Test

public void deleteDeployment() {

//部署id

String deploymentId = "qingjia:1:c493c327-bb02-11ed-8360-005056c00001";

// //删除流程定义,如果该流程定义已有流程实例启动则删除时出错

// repositoryService.deleteDeployment(deploymentId);

//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式

repositoryService.deleteDeployment(deploymentId, true);

}

// 查询已经处理的任务

@Test

public 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());

}

}

// 处理当前任务

@Test

public void completeTask(){

// 查询负责人需要处理的任务,返回一条

Task task = taskService.createTaskQuery().taskAssignee("zhangsan").singleResult();

// 完成任务

taskService.complete(task.getId());

}

// 查询个人的代办任务--zhangsan

@Test

public 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());

}

}

// 启动流程实例

@Test

public 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());

}

// 单个文件的部署

@Test

public 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 {

@Autowired

private RepositoryService repositoryService;

@Autowired

private RuntimeService runtimeService;

@Autowired

private TaskService taskService;

@Autowired

private HistoryService historyService;

///

// 监听器分配任务

// 部署流程定义

@Test

public 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-005056c00001

System.out.println("deploy.getName() = " + deploy.getName()); // 加班申请流程02

}

@Test

public void startProcessInstance02(){

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban02");

System.out.println("processInstance.getProcessDefinitionId() = " + processInstance.getProcessDefinitionId()); // jiaban02:1:ed150752-bb41-11ed-a6f2-005056c00001

System.out.println("processInstance.getId() = " + processInstance.getId()); // 06eca124-bb42-11ed-9bbc-005056c00001

}

// 查询个人的代办任务--Tim

@Test

public 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-005056c00001

System.out.println("任务id:" + task.getId()); // 06f071b8-bb42-11ed-9bbc-005056c00001

System.out.println("任务负责人:" + task.getAssignee()); // Tim

System.out.println("任务名称:" + task.getName()); // 经理审批

}

}

///

// uel-method

// 部署流程定义

@Test

public 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-005056c00001

System.out.println("deploy.getName() = " + deploy.getName()); // 加班申请流程01

}

@Test

public void startProcessInstance01(){

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("jiaban01");

System.out.println("processInstance.getProcessDefinitionId() = " + processInstance.getProcessDefinitionId()); // jiaban01:1:8c56a740-bb20-11ed-8d65-005056c00001

System.out.println("processInstance.getId() = " + processInstance.getId()); // abb9c7c4-bb20-11ed-b608-005056c00001

}

// 查询个人的代办任务--LiLei

@Test

public 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-005056c00001

System.out.println("任务id:" + task.getId()); // abbd4a38-bb20-11ed-b608-005056c00001

System.out.println("任务负责人:" + task.getAssignee()); // LiLei

System.out.println("任务名称:" + task.getName()); // 经理审批

}

}

///

// uel-value

// 部署流程定义

@Test

public void deployProcess() {

Deployment deploy = repositoryService.createDeployment().addClasspathResource("process/jiaban.bpmn20.xml").name("加班申请流程").deploy();

System.out.println("deploy.getId() = " + deploy.getId()); // 5c5519ad-bb1d-11ed-b5c8-005056c00001

System.out.println("deploy.getName() = " + deploy.getName()); // 加班申请流程

}

// 启动流程实例

@Test

public 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-005056c00001

System.out.println("processInstance.getId() = " + processInstance.getId()); // 7f720dd9-bb1d-11ed-b6e9-005056c00001

}

// 查询个人的代办任务--tom

@Test

public 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-005056c00001

System.out.println("任务id:" + task.getId()); // 7f759051-bb1d-11ed-b6e9-005056c00001

System.out.println("任务负责人:" + task.getAssignee()); // tom

System.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 {

@Autowired

private RepositoryService repositoryService;

@Autowired

private RuntimeService runtimeService;

@Autowired

private TaskService taskService;

// 1、部署流程定义

@Test

public 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-005056c00001

System.out.println("deploy.getName() = " + deploy.getName()); // 加班申请流程04

}

// 1.5、启动流程实例

@Test

public 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-005056c00001

System.out.println("processInstance.getId() = " + processInstance.getId()); // 428d0c0f-bb49-11ed-83a5-005056c00001

}

// 2、查询组任务

@Test

public 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、分配组任务

@Test

public void claimTask(){

Task task = taskService.createTaskQuery()

.taskCandidateUser("tom")

.singleResult();

if (task!=null){

taskService.claim(task.getId(),"tom");

System.out.println("分配任务完成");

}

}

// 4、查询个人的代办任务--tom

@Test

public 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-005056c00001

System.out.println("任务id:" + task.getId()); // 7f759051-bb1d-11ed-b6e9-005056c00001

System.out.println("任务负责人:" + task.getAssignee()); // tom

System.out.println("任务名称:" + task.getName()); // 经理审批

}

}

// 5、办理个人任务

@Test

public 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 {

@Autowired

private RepositoryService repositoryService;

//注入RuntimeService

@Autowired

private RuntimeService runtimeService;

@Autowired

private TaskService taskService;

@Autowired

private HistoryService historyService;

//1 部署流程定义

@Test

public void deployProcess() {

Deployment deployment = repositoryService.createDeployment()

.addClasspathResource("process/qingjia003.bpmn20.xml")

.name("请假申请流程003")

.deploy();

System.out.println(deployment.getId()); // af9242f0-bb4c-11ed-85bf-005056c00001

System.out.println(deployment.getName()); // 请假申请流程002

}

//2 启动流程实例

@Test

public 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-005056c00001

System.out.println(processInstance.getId()); // 90d46e2c-bb4d-11ed-9b92-005056c00001

}

//3 查询个人的代办任务--zhao6

@Test

public 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());

}

}

//完成任务

@Test

public void completeTask() {

Task task = taskService.createTaskQuery()

// .taskAssignee("zhao6") //要查询的负责人

// .taskAssignee("xiaocui") //要查询的负责人

// .taskAssignee("gousheng")

// .taskAssignee("wang5")

.taskAssignee("gouwa")

.singleResult();//返回一条

//完成任务,参数:任务id

taskService.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 {

@Autowired

private 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 {

@Autowired

private 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<>();

//根据上传地址后续部署流程定义,文件名称为流程定义的默认key

map.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 {

@Autowired

private 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

//审批管理列表

@Override

public 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 代表已发布

// 流程定义部署

@Override

public 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

// 流程定义部署

@Override

public 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

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: