云上办公系统项目
申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计13077字,阅读大概需要30分钟
更多学习内容, 欢迎关注我
个人公众号:不懂开发的程序猿
个人网站:https://jerry-jy.co/
1、云上办公系统
1.1、介绍
云上办公系统是一套自动办公系统,系统主要包含:管理端和员工端
员工端采用微信公众号操作,包含:办公审批、微信授权登录、消息推送等功能
项目服务器端架构:SpringBoot + MyBatisPlus + SpringSecurity + Redis + Activiti+ MySQL
前端架构:vue–admin–template + Node.js + Npm + Vue + ElementUI + Axios
1.2、核心技术
基础框架:SpringBoot |
---|
数据缓存:Redis |
数据库:MySQL |
权限控制:SpringSecurity |
工作流引擎:Activiti |
前端技术:vue–admin–template + Node.js + Npm + Vue + ElementUI + Axios |
微信公众号:公众号菜单 + 微信授权登录 + 消息推送 |
1.3、开发环境说明
工具 | 版本 |
---|---|
后台 | SpringBoot 2.3.6 + MyBatisPlus 3.4.1 |
服务器 | Tomcat 8.5.73 |
数据库 | MySQL 8.0.27 |
Build Tools | Maven 3.8.5 |
前端 | Vue + ElementUI + Node.js 14.15.0 |
开发工具 | IDEA 2022.3 |
版本管理工具 | Git |
1.4、产品展示
后台
登录页
【审批管理】–【审批列表】
前台
审批页面
1.5、 个人总结
我认为该项目对我来说主要的帮助有:
1、项目是前后端分离的,符合目前主流业务开发逻辑,作为后端程序员,复习前端Vue + ElementUI框架, 巩固练习使用前端的脚手架工程,学习使用前后端联调开发过程
2、项目中引入JWT加密token,用作用户登录身份校验,用 SpringSecurity 来做权限控制,涉及多表查询,是项目的重难点学习对象,也是对前面学习SpringSecurity的一个巩固
3、前端使用微信公众号来作为前端接入口,以前没有开发过,也是亮点。
4、引入 工作流引擎:Activiti 作为组件,第一次用,学习下
2、后端环境搭建
2.1、建库建表
sql语句太多了,见文末的资料
2.2、创建Maven项目
pom文件
guigu-oa-parent
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
</parent>
<groupId>com.jerry</groupId>
<artifactId>guigu-oa-parent</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>model</module>
<module>service-oa</module>
</modules>
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.4.1</mybatis-plus.version>
<mysql.version>8.0.27</mysql.version>
<knife4j.version>3.0.3</knife4j.version>
<jwt.version>0.9.1</jwt.version>
<fastjson.version>2.0.21</fastjson.version>
</properties>
<!--配置dependencyManagement锁定依赖的版本-->
<dependencyManagement>
<dependencies>
<!--mybatis-plus 持久层-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--knife4j-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!--jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
common
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jerry</groupId>
<artifactId>guigu-oa-parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>common</artifactId>
<packaging>pom</packaging>
<modules>
<module>common-util</module>
<module>service-util</module>
</modules>
</project>
common-util
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jerry</groupId>
<artifactId>common</artifactId>
<version>1.0</version>
</parent>
<artifactId>common-util</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project>
service-util
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jerry</groupId>
<artifactId>common</artifactId>
<version>1.0</version>
</parent>
<artifactId>service-util</artifactId>
<dependencies>
<dependency>
<groupId>com.jerry</groupId>
<artifactId>common-util</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
model
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jerry</groupId>
<artifactId>guigu-oa-parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>model</artifactId>
<dependencies>
<!--lombok用来简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency>
</dependencies>
</project>
service-oa
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jerry</groupId>
<artifactId>guigu-oa-parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>service-oa</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.jerry</groupId>
<artifactId>model</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.jerry</groupId>
<artifactId>service-util</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置数据源、服务器端口号
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<SysRole> {
}
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<SysRole> {
}
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<SysRoleMapper, SysRole> implements SysRoleService {
}
编写测试类
目的是:
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<SysRole> list = sysRoleService.list();
list.forEach(System.out::println);
}
// 使用MP 封装的 mapper查询所有记录
@Test
public void getAllByMapper(){
List<SysRole> 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<SysRole> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("role_name", "系统管理员");
// 或者下面这种写法
LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysRole::getRoleName,"系统管理员");
List<SysRole> 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<T> {
private Integer code; // 状态码
private String message; // 返回信息
private T data; // 统一返回的结果数据
/**
* 封装返回数据
* @param body
* @param resultCodeEnum
* @return
* @param <T>
*/
public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
Result<T> result = new Result<>();
// 封装数据
if (body!=null){
result.setData(body);
}
// 状态码
result.setCode(resultCodeEnum.getCode());
//返回信息
result.setMessage(resultCodeEnum.getMessage());
return result;
}
// 构造私有化 外部不能new
private Result(){}
// 成功 空结果
public static <T>Result<T> ok(){
return build(null,ResultCodeEnum.SUCCESS);
}
/**
* 成功 返回有数据的结果
* @param data
* @return
* @param <T>
*/
public static <T>Result<T> ok(T data){
return build(data,ResultCodeEnum.SUCCESS);
}
// 失败
public static <T>Result<T> fail(){
return build(null,ResultCodeEnum.FAIL);
}
/**
* 失败 返回有数据的结果
* @param data
* @return
* @param <T>
*/
public static <T>Result<T> fail(T data){
return build(data,ResultCodeEnum.FAIL);
}
public Result<T> message(String msg){
this.setMessage(msg);
return this;
}
public Result<T> code(Integer code){
this.setCode(code);
return this;
}
}
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<SysRole> getAll(){
// List<SysRole> list = sysRoleService.list();
// return list;
// }
/**
* 统一返回数据结果
* @return
*/
@GetMapping("/getAll")
private Result getAll(){
List<SysRole> 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介绍
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
1、及时性 (接口变更后,能够及时准确地通知相关前后端开发人员)
2、规范性 (并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息)
3、一致性 (接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧)
目的
使用步骤
添加依赖
service-uitl.pom
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
添加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<Parameter> pars = new ArrayList<>();
ParameterBuilder tokenPar = new ParameterBuilder();
tokenPar.name("token")
.description("用户token")
.defaultValue("")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build();
pars.add(tokenPar.build());
//添加head参数end
Docket adminApi = new Docket(DocumentationType.SWAGGER_2)
.groupName("adminApi")
.apiInfo(adminApiInfo())
.select()
//只显示admin路径下的页面
.apis(RequestHandlerSelectors.basePackage("com.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层添加注解
测试
http://localhost:8800/doc.html
3.3、分页查询所有角色
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<SysRole> sysRolePage = new Page<>(page, pageSize);
// 2、构造分页查询条件, 判断条件是否为空,不为空进行封装
LambdaQueryWrapper<SysRole> 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<Long> ids){
boolean is_success = sysRoleService.removeByIds(ids);
if (is_success) {
return Result.ok();
} else {
return Result.fail();
}
}
测试
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、前端环境搭建
安装脚手架工程
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<String, Object> map = new HashMap<>();
map.put("token","admin-token");
return Result.ok(map);
}
/**
* info
* @return
*/
@GetMapping("/info")
public Result info(){
Map<String, Object> map = new HashMap<>();
map.put("roles","[admin]");
map.put("name","admin");
map.put("avatar","https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
return Result.ok(map);
}
/**
* logout
* @return
*/
@ApiOperation("登出")
@PostMapping("/logout")
public Result logout(){
return Result.ok();
}
}
修改前端的跳转地址
修改响应状态码
测试
6、前端角色管理
6.1、角色列表
修改路由
{
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'
},
}
]
},
创建角色页面
<template>
<div class="app-container">
<!--查询表单-->
<div class="search-div">
<el-form label-width="70px" size="small">
<el-row>
<el-col :span="24">
<el-form-item label="角色名称">
<el-input style="width: 100%" v-model="searchObj.roleName" placeholder="角色名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row style="display:flex">
<el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="fetchData()">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetData">重置</el-button>
</el-row>
</el-form>
</div>
<!-- 表格 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
@selection-change="handleSelectionChange">
<el-table-column type="selection"/>
<el-table-column
label="序号"
width="70"
align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="roleName" label="角色名称" />
<el-table-column prop="roleCode" label="角色编码" />
<el-table-column prop="createTime" label="创建时间" width="160"/>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row.id)" title="修改"/>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除"/>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
style="padding: 30px 0; text-align: center;"
layout="total, prev, pager, next, jumper"
@current-change="fetchData"
/>
</div>
</template>
<script>
import api from '@/api/system/sysRole'
export default {
// 定义数据模型
// 定义数据模型
data() {
return {
list: [], // 列表
total: 0, // 总记录数
page: 1, // 页码
limit: 2, // 每页记录数
searchObj: {}, // 查询条件
multipleSelection: []// 批量删除选中的记录列表
}
},
// 页面渲染成功后获取数据
created() {
this.fetchData()
},
// 定义方法
methods: {
fetchData(current=1) {
this.page = current
// 调用api
api.getPageList(this.page, this.limit, this.searchObj).then(response => {
this.list = response.data.records
this.total = response.data.total
})
},
}
}
</script>
定义角色管理相关的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
<template>
<div class="app-container">
<!--查询表单-->
<div class="search-div">
<el-form label-width="70px" size="small">
<el-row>
<el-col :span="24">
<el-form-item label="角色名称">
<el-input style="width: 100%" v-model="searchObj.roleName" placeholder="角色名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row style="display:flex">
<el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="fetchData()">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetData">重置</el-button>
</el-row>
</el-form>
</div>
<!-- 表格 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;"
@selection-change="handleSelectionChange">
<el-table-column type="selection"/>
<el-table-column
label="序号"
width="70"
align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="roleName" label="角色名称" />
<el-table-column prop="roleCode" label="角色编码" />
<el-table-column prop="createTime" label="创建时间" width="160"/>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row.id)" title="修改"/>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除"/>
</template>
</el-table-column>
</el-table>
<!-- 工具条 -->
<div class="tools-div">
<el-button type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>
<el-button class="btn-add" size="mini" @click="batchRemove()" >批量删除</el-button>
</div>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
style="padding: 30px 0; text-align: center;"
layout="total, prev, pager, next, jumper"
@current-change="fetchData"
/>
<el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" >
<el-form ref="dataForm" :model="sysRole" label-width="150px" size="small" style="padding-right: 40px;">
<el-form-item label="角色名称">
<el-input v-model="sysRole.roleName"/>
</el-form-item>
<el-form-item label="角色编码">
<el-input v-model="sysRole.roleCode"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
<el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/system/sysRole'
export default {
// 定义数据模型
// 定义数据模型
data() {
return {
list: [], // 列表
total: 0, // 总记录数
page: 1, // 页码
limit: 2, // 每页记录数
searchObj: {}, // 查询条件
multipleSelection: [],// 批量删除选中的记录列表
dialogVisible: false,
sysRole: {},
saveBtnDisabled: false
}
},
// 页面渲染成功后获取数据
created() {
this.fetchData()
},
// 定义方法
methods: {
// 当多选选项发生变化的时候调用
handleSelectionChange(selection) {
console.log(selection)
this.multipleSelection = selection
},
// 批量删除
batchRemove() {
if (this.multipleSelection.length === 0) {
this.$message.warning('请选择要删除的记录!')
return
}
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 点击确定,远程调用ajax
// 遍历selection,将id取出放入id列表
var idList = []
this.multipleSelection.forEach(item => {
idList.push(item.id)
})
// 调用api
return api.batchRemove(idList)
}).then((response) => {
this.fetchData()
this.$message.success(response.message)
})
},
// 点击修改,弹出框,根据id查询数据显示
edit(id) {
// 弹出框
this.dialogVisible = true
// 根据id查询
this.fetchDataById(id)
},
fetchDataById(id) {
api.getById(id).then(response => {
this.sysRole = response.data
})
},
// 点击添加弹框
add(){
this.dialogVisible = true
},
saveOrUpdate() {
this.saveBtnDisabled = true // 防止表单重复提交
// 根据id判断
if (!this.sysRole.id) { // 添加
this.saveData()
} else { // 修改
this.updateData()
}
},
// 新增
saveData() {
api.save(this.sysRole).then(response => {
// 提示
this.$message.success(response.message || '操作成功')
// 关闭弹框
this.dialogVisible = false
// 刷新页面
this.fetchData(this.page)
})
},
// 修改
updateData() {
api.updateById(this.sysRole).then(response => {
// 提示
this.$message.success(response.message || '操作成功')
// 关闭弹框
this.dialogVisible = false
// 刷新页面
this.fetchData(this.page)
})
},
// 条件分页查询
fetchData(current=1) {
this.page = current
// 调用api
api.getPageList(this.page, this.limit, this.searchObj).then(response => {
this.list = response.data.records
this.total = response.data.total
})
},
// 根据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 || '删除成功')
})
}
}
}
</script>
页面展示
7、用户管理
7.1、用户管理CRUD
需求分析
代码生成器
- 可以采用MyBatisPlus提供的代码生成器直接生成
mapper
,service
,impl
,controller
, - 手动创建的话,也行
service-oa
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
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();
}
}
编写代码
SysUserMapper
public interface SysUserMapper extends BaseMapper<SysUser> {
}
SysUserService
public interface SysUserService extends IService<SysUser> {
}
SysUserServiceImpl
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> 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.*;
/**
* <p>
* 用户表 前端控制器
* </p>
*
* @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<SysUser> sysUserPage = new Page<>(page, pageSize);
LambdaQueryWrapper<SysUser> 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
<template>
<div class="app-container">
<div class="search-div">
<el-form label-width="70px" size="small">
<el-row>
<el-col :span="8">
<el-form-item label="关 键 字">
<el-input style="width: 95%" v-model="searchObj.keyword" placeholder="用户名/姓名/手机号码"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="操作时间">
<el-date-picker
v-model="createTimes"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="margin-right: 10px;width: 100%;"
/>
</el-form-item>
</el-col>
</el-row>
<el-row style="display:flex">
<el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="fetchData()">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetData">重置</el-button>
</el-row>
</el-form>
</div>
<!-- 工具条 -->
<div class="tools-div">
<el-button type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>
</div>
<!-- 列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;">
<el-table-column
label="序号"
width="70"
align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" width="100"/>
<el-table-column prop="name" label="姓名" width="70"/>
<el-table-column prop="phone" label="手机" width="120"/>
<el-table-column prop="postName" label="岗位" width="100"/>
<el-table-column prop="deptName" label="部门" width="100"/>
<el-table-column label="所属角色" width="130">
<template slot-scope="scope">
<span v-for="item in scope.row.roleList" :key="item.id" style="margin-right: 10px;">{{ item.roleName }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status === 1"
@change="switchStatus(scope.row)">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160"/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row.id)" title="修改"/>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除" />
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
<el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" >
<el-form ref="dataForm" :model="sysUser" label-width="100px" size="small" style="padding-right: 40px;">
<el-form-item label="用户名" prop="username">
<el-input v-model="sysUser.username"/>
</el-form-item>
<el-form-item v-if="!sysUser.id" label="密码" prop="password">
<el-input v-model="sysUser.password" type="password"/>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="sysUser.name"/>
</el-form-item>
<el-form-item label="手机" prop="phone">
<el-input v-model="sysUser.phone"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
<el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/system/sysUser'
const defaultForm = {
id: '',
username: '',
password: '',
name: '',
phone: '',
status: 1
}
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 5, // 每页记录数
searchObj: {}, // 查询表单对象
createTimes: [],
dialogVisible: false,
sysUser: defaultForm,
saveBtnDisabled: false,
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 当页码发生改变的时候
changeSize(size) {
console.log(size)
this.limit = size
this.fetchData(1)
},
// 加载banner列表数据
fetchData(page = 1) {
debugger
this.page = page
console.log('翻页。。。' + this.page)
if(this.createTimes && this.createTimes.length ==2) {
this.searchObj.createTimeBegin = this.createTimes[0]
this.searchObj.createTimeEnd = this.createTimes[1]
}
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
//this.list = response.data.list
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 重置查询表单
resetData() {
console.log('重置查询表单')
this.searchObj = {}
this.createTimes = []
this.fetchData()
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定,远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData()
this.$message.success(response.message || '删除成功')
}).catch(() => {
this.$message.info('取消删除')
})
},
// -------------
add(){
this.dialogVisible = true
this.sysUser = Object.assign({}, defaultForm)
},
edit(id) {
this.dialogVisible = true
this.fetchDataById(id)
},
fetchDataById(id) {
api.getById(id).then(response => {
this.sysUser = response.data
})
},
saveOrUpdate() {
this.$refs.dataForm.validate(valid => {
if (valid) {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.sysUser.id) {
this.saveData()
} else {
this.updateData()
}
}
})
},
// 新增
saveData() {
api.save(this.sysUser).then(response => {
this.$message.success('操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
},
// 根据id更新记录
updateData() {
api.updateById(this.sysUser).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
}
}
}
</script>
添加路由
{
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、用户管理分配角色
需求分析
多对多的关系
接口分析
编写代码
代码是写在service-oa类中的
SysUserRoleMapper
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
}
SysUserRoleService
public interface SysUserRoleService extends IService<SysUserRole> {
}
SysUserRoleServiceImpl
@Service
public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUserRole> implements SysUserRoleService {
}
SysRoleController
// 1、查询所有角色 和 当前用户所属角色
@ApiOperation("根据用户获取角色数据")
@GetMapping("/toAssign/{userId}")
public Result toAssign(@PathVariable Long userId) {
Map<String, Object> 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<SysRoleMapper, SysRole> implements SysRoleService {
@Autowired
private SysUserRoleService sysUserRoleService;
//1 查询所有角色 和 当前用户所属角色
@Override
public Map<String, Object> findRoleDataByUserId(Long userId) {
//1 查询所有角色,返回list集合,返回
List<SysRole> allRoleList =
baseMapper.selectList(null);
//2 根据userid查询 角色用户关系表,查询userid对应所有角色id
LambdaQueryWrapper<SysUserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUserRole::getUserId,userId);
List<SysUserRole> existUserRoleList = sysUserRoleService.list(wrapper);
//从查询出来的用户id对应角色list集合,获取所有角色id
// List<Long> list = new ArrayList<>();
// for (SysUserRole sysUserRole:existUserRoleList) {
// Long roleId = sysUserRole.getRoleId();
// list.add(roleId);
// }
List<Long> existRoleIdList =
existUserRoleList.stream().map(c -> c.getRoleId()).collect(Collectors.toList());
//3 根据查询所有角色id,找到对应角色信息
//根据角色id到所有的角色的list集合进行比较
List<SysRole> assignRoleList = new ArrayList<>();
for(SysRole sysRole : allRoleList) {
//比较
if(existRoleIdList.contains(sysRole.getId())) {
assignRoleList.add(sysRole);
}
}
//4 把得到两部分数据封装map集合,返回
Map<String, Object> roleMap = new HashMap<>();
roleMap.put("assginRoleList", assignRoleList);
roleMap.put("allRolesList", allRoleList);
return roleMap;
}
//2 为用户分配角色
@Override
public void doAssign(AssginRoleVo assginRoleVo) {
//把用户之前分配角色数据删除,用户角色关系表里面,根据userid删除
LambdaQueryWrapper<SysUserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUserRole::getUserId,assginRoleVo.getUserId());
sysUserRoleService.remove(wrapper);
//重新进行分配
List<Long> roleIdList = assginRoleVo.getRoleIdList();
for(Long roleId:roleIdList) {
if(StringUtils.isEmpty(roleId)) {
continue;
}
SysUserRole sysUserRole = new SysUserRole();
sysUserRole.setUserId(assginRoleVo.getUserId());
sysUserRole.setRoleId(roleId);
sysUserRoleService.save(sysUserRole);
}
}
}
前端展示
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<SysUser> {
// 更新状态
void updateStatus(Long id, Integer status);
}
SysUserServiceImpl
@Service
@Slf4j
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> 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
<template>
<div class="app-container">
<div class="search-div">
<el-form label-width="70px" size="small">
<el-row>
<el-col :span="8">
<el-form-item label="关 键 字">
<el-input style="width: 95%" v-model="searchObj.keyword" placeholder="用户名/姓名/手机号码"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="操作时间">
<el-date-picker
v-model="createTimes"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="yyyy-MM-dd HH:mm:ss"
style="margin-right: 10px;width: 100%;"
/>
</el-form-item>
</el-col>
</el-row>
<el-row style="display:flex">
<el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="fetchData()">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetData">重置</el-button>
</el-row>
</el-form>
</div>
<!-- 工具条 -->
<div class="tools-div">
<el-button type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>
</div>
<!-- 列表 -->
<el-table
v-loading="listLoading"
:data="list"
stripe
border
style="width: 100%;margin-top: 10px;">
<el-table-column
label="序号"
width="70"
align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" width="100"/>
<el-table-column prop="name" label="姓名" width="70"/>
<el-table-column prop="phone" label="手机" width="120"/>
<el-table-column prop="postName" label="岗位" width="100"/>
<el-table-column prop="deptName" label="部门" width="100"/>
<el-table-column label="所属角色" width="130">
<template slot-scope="scope">
<span v-for="item in scope.row.roleList" :key="item.id" style="margin-right: 10px;">{{ item.roleName }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status === 1"
@change="switchStatus(scope.row)">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160"/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row.id)" title="修改"/>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除" />
<el-button type="warning" icon="el-icon-baseball" size="mini" @click="showAssignRole(scope.row)" title="分配角色"/>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="page"
:total="total"
:page-size="limit"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;"
layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData"
@size-change="changeSize"
/>
<el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%" >
<el-form ref="dataForm" :model="sysUser" label-width="100px" size="small" style="padding-right: 40px;">
<el-form-item label="用户名" prop="username">
<el-input v-model="sysUser.username"/>
</el-form-item>
<el-form-item v-if="!sysUser.id" label="密码" prop="password">
<el-input v-model="sysUser.password" type="password"/>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="sysUser.name"/>
</el-form-item>
<el-form-item label="手机" prop="phone">
<el-input v-model="sysUser.phone"/>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
<el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
</span>
</el-dialog>
<el-dialog title="分配角色" :visible.sync="dialogRoleVisible">
<el-form label-width="80px">
<el-form-item label="用户名">
<el-input disabled :value="sysUser.username"></el-input>
</el-form-item>
<el-form-item label="角色列表">
<el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
<div style="margin: 15px 0;"></div>
<el-checkbox-group v-model="userRoleIds" @change="handleCheckedChange">
<el-checkbox v-for="role in allRoles" :key="role.id" :label="role.id">{{role.roleName}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<div slot="footer">
<el-button type="primary" @click="assignRole" size="small">保存</el-button>
<el-button @click="dialogRoleVisible = false" size="small">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/system/sysUser'
import roleApi from '@/api/system/sysRole'
const defaultForm = {
id: '',
username: '',
password: '',
name: '',
phone: '',
status: 1
}
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // banner列表
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {}, // 查询表单对象
createTimes: [],
dialogVisible: false,
sysUser: defaultForm,
saveBtnDisabled: false,
dialogRoleVisible: false,
allRoles: [], // 所有角色列表
userRoleIds: [], // 用户的角色ID的列表
isIndeterminate: false, // 是否是不确定的
checkAll: false // 是否全选
}
},
// 生命周期函数:内存准备完毕,页面尚未渲染
created() {
console.log('list created......')
this.fetchData()
roleApi.findAll().then(response => {
this.roleList = response.data;
})
},
// 生命周期函数:内存准备完毕,页面渲染成功
mounted() {
console.log('list mounted......')
},
methods: {
// 当页码发生改变的时候
changeSize(size) {
console.log(size)
this.limit = size
this.fetchData(1)
},
// 加载banner列表数据
fetchData(page = 1) {
debugger
this.page = page
console.log('翻页。。。' + this.page)
if(this.createTimes && this.createTimes.length ==2) {
this.searchObj.createTimeBegin = this.createTimes[0]
this.searchObj.createTimeEnd = this.createTimes[1]
}
api.getPageList(this.page, this.limit, this.searchObj).then(
response => {
//this.list = response.data.list
this.list = response.data.records
this.total = response.data.total
// 数据加载并绑定成功
this.listLoading = false
}
)
},
// 重置查询表单
resetData() {
console.log('重置查询表单')
this.searchObj = {}
this.createTimes = []
this.fetchData()
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定,远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData(this.page)
this.$message.success(response.message || '删除成功')
}).catch(() => {
this.$message.info('取消删除')
})
},
// -------------
add(){
this.dialogVisible = true
this.sysUser = Object.assign({}, defaultForm)
},
edit(id) {
this.dialogVisible = true
this.fetchDataById(id)
},
fetchDataById(id) {
api.getById(id).then(response => {
this.sysUser = response.data
})
},
saveOrUpdate() {
this.$refs.dataForm.validate(valid => {
if (valid) {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.sysUser.id) {
this.saveData()
} else {
this.updateData()
}
}
})
},
// 新增
saveData() {
api.save(this.sysUser).then(response => {
this.$message.success('操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
},
// 根据id更新记录
updateData() {
api.updateById(this.sysUser).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
},
showAssignRole (row) {
this.sysUser = row
this.dialogRoleVisible = true
this.getRoles()
},
getRoles () {
roleApi.getRoles(this.sysUser.id).then(response => {
const {allRolesList, assginRoleList} = response.data
this.allRoles = allRolesList
this.userRoleIds = assginRoleList.map(item => item.id)
this.checkAll = allRolesList.length===assginRoleList.length
this.isIndeterminate = assginRoleList.length>0 && assginRoleList.length<allRolesList.length
})
},
/*
全选勾选状态发生改变的监听
*/
handleCheckAllChange (value) {// value 当前勾选状态true/false
// 如果当前全选, userRoleIds就是所有角色id的数组, 否则是空数组
this.userRoleIds = value ? this.allRoles.map(item => item.id) : []
// 如果当前不是全选也不全不选时, 指定为false
this.isIndeterminate = false
},
/*
角色列表选中项发生改变的监听
*/
handleCheckedChange (value) {
const {userRoleIds, allRoles} = this
this.checkAll = userRoleIds.length === allRoles.length && allRoles.length>0
this.isIndeterminate = userRoleIds.length>0 && userRoleIds.length<allRoles.length
},
assignRole () {
let assginRoleVo = {
userId: this.sysUser.id,
roleIdList: this.userRoleIds
}
roleApi.assignRoles(assginRoleVo).then(response => {
this.$message.success(response.message || '分配角色成功')
this.dialogRoleVisible = false
this.fetchData(this.page)
})
},
switchStatus(row) {
row.status = row.status === 1 ? 0 : 1
api.updateStatus(row.id, row.status).then(response => {
if (response.code) {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData()
}
})
}
}
}
</script>
页面展示
8、菜单管理
8.1、菜单管理CRUD
需求分析
编写代码
SysMenuMapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {
}
SysRoleMenuMapper
public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenu> {
}
SysMenuService
public interface SysMenuService extends IService<SysMenu> {
List<SysMenu> findNodes();
// 删除菜单
void removeMenuById(Long id);
}
SysRoleMenuService
public interface SysRoleMenuService extends IService<SysRoleMenu> {
}
SysMenuServiceImpl
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
@Override
public List<SysMenu> findNodes() {
// 1、查询所有 的数据
List<SysMenu> sysMenuList = baseMapper.selectList(null);
// 2、构建树形结构
List<SysMenu> list = MenuHelper.buildTree(sysMenuList);
return list;
}
// 删除菜单
@Override
public void removeMenuById(Long id) {
// 判断当前菜单是否有下一层菜单
LambdaQueryWrapper<SysMenu> 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<SysMenu> buildTree(List<SysMenu> sysMenuList) {
// 存放最终数据
List<SysMenu> 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<SysMenu> sysMenuList){
sysMenu.setChildren(new ArrayList<SysMenu>());
// 遍历所有的菜单数据,判断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<SysRoleMenuMapper, SysRoleMenu> 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;
/**
* <p>
* 菜单表 前端控制器
* </p>
*
* @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<SysMenu> 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
<template>
<div class="app-container">
<!-- 工具条 -->
<div class="tools-div">
<el-button type="success" icon="el-icon-plus" size="mini" @click="add()">添 加</el-button>
</div>
<el-table
:data="sysMenuList"
style="width: 100%;margin-bottom: 20px;margin-top: 10px;"
row-key="id"
border
:default-expand-all="false"
:tree-props="{children: 'children'}">
<el-table-column prop="name" label="菜单名称" width="160"/>
<el-table-column label="图标">
<template slot-scope="scope">
<i :class="scope.row.icon"></i>
</template>
</el-table-column>
<el-table-column prop="perms" label="权限标识" width="160"/>
<el-table-column prop="path" label="路由地址" width="120"/>
<el-table-column prop="component" label="组件路径" width="160"/>
<el-table-column prop="sortValue" label="排序" width="60"/>
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status === 1" disabled="true">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160"/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="success" v-if="scope.row.type !== 2" icon="el-icon-plus" size="mini" @click="add(scope.row)" title="添加下级节点"/>
<el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row)" title="修改"/>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除" :disabled="scope.row.children.length > 0"/>
</template>
</el-table-column>
</el-table>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="40%" >
<el-form ref="dataForm" :model="sysMenu" label-width="150px" size="small" style="padding-right: 40px;">
<el-form-item label="上级部门" v-if="sysMenu.id === ''">
<el-input v-model="sysMenu.parentName" disabled="true"/>
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="sysMenu.type" :disabled="typeDisabled">
<el-radio :label="0" :disabled="type0Disabled">目录</el-radio>
<el-radio :label="1" :disabled="type1Disabled">菜单</el-radio>
<el-radio :label="2" :disabled="type2Disabled">按钮</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单名称" prop="name">
<el-input v-model="sysMenu.name"/>
</el-form-item>
<el-form-item label="图标" prop="icon" v-if="sysMenu.type !== 2">
<el-select v-model="sysMenu.icon" clearable>
<el-option v-for="item in iconList" :key="item.class" :label="item.class" :value="item.class">
<span style="float: left;">
<i :class="item.class"></i> <!-- 如果动态显示图标,这里添加判断 -->
</span>
<span style="padding-left: 6px;">{{ item.class }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="sysMenu.sortValue" controls-position="right" :min="0" />
</el-form-item>
<el-form-item prop="path">
<span slot="label">
<el-tooltip content="访问的路由地址,如:`sysUser`" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
路由地址
</span>
<el-input v-model="sysMenu.path" placeholder="请输入路由地址" />
</el-form-item>
<el-form-item prop="component" v-if="sysMenu.type !== 0">
<span slot="label">
<el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
组件路径
</span>
<el-input v-model="sysMenu.component" placeholder="请输入组件路径" />
</el-form-item>
<el-form-item v-if="sysMenu.type === 2">
<el-input v-model="sysMenu.perms" placeholder="请输入权限标识" maxlength="100" />
<span slot="label">
<el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(hasAuthority('bnt.sysRole.list'))" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
权限字符
</span>
</el-form-item>
<el-form-item label="状态" prop="type">
<el-radio-group v-model="sysMenu.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
<el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/system/sysMenu'
const defaultForm = {
id: '',
parentId: '',
name: '',
type: 0,
path: '',
component: '',
perms: '',
icon: '',
sortValue: 1,
status: 1
}
export default {
// 定义数据
data() {
return {
sysMenuList: [],
expandKeys: [], // 需要自动展开的项
typeDisabled: false,
type0Disabled: false,
type1Disabled: false,
type2Disabled: false,
dialogTitle: '',
dialogVisible: false,
sysMenu: defaultForm,
saveBtnDisabled: false,
iconList: [
{
class: "el-icon-s-tools",
},
{
class: "el-icon-s-custom",
},
{
class: "el-icon-setting",
},
{
class: "el-icon-user-solid",
},
{
class: "el-icon-s-help",
},
{
class: "el-icon-phone",
},
{
class: "el-icon-s-unfold",
},
{
class: "el-icon-s-operation",
},
{
class: "el-icon-more-outline",
},
{
class: "el-icon-s-check",
},
{
class: "el-icon-tickets",
},
{
class: "el-icon-s-goods",
},
{
class: "el-icon-document-remove",
},
{
class: "el-icon-warning",
},
{
class: "el-icon-warning-outline",
},
{
class: "el-icon-question",
},
{
class: "el-icon-info",
}
]
}
},
// 当页面加载时获取数据
created() {
this.fetchData()
},
methods: {
// 调用api层获取数据库中的数据
fetchData() {
console.log('加载列表')
api.findNodes().then(response => {
this.sysMenuList = response.data
console.log(this.sysMenuList)
})
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定,远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData()
this.$message({
type: 'success',
message: '删除成功!'
})
}).catch(() => {
this.$message.info('取消删除')
})
},
// -------------
add(row){
debugger
this.typeDisabled = false
this.dialogTitle = '添加下级节点'
this.dialogVisible = true
this.sysMenu = Object.assign({}, defaultForm)
this.sysMenu.id = ''
if(row) {
this.sysMenu.parentId = row.id
this.sysMenu.parentName = row.name
//this.sysMenu.component = 'ParentView'
if(row.type === 0) {
this.sysMenu.type = 1
this.typeDisabled = false
this.type0Disabled = false
this.type1Disabled = false
this.type2Disabled = true
} else if(row.type === 1) {
this.sysMenu.type = 2
this.typeDisabled = true
}
} else {
this.dialogTitle = '添加目录节点'
this.sysMenu.type = 0
this.sysMenu.component = 'Layout'
this.sysMenu.parentId = 0
this.typeDisabled = true
}
},
edit(row) {
debugger
this.dialogTitle = '修改节点'
this.dialogVisible = true
this.sysMenu = Object.assign({}, row)
this.typeDisabled = true
},
saveOrUpdate() {
if(this.sysMenu.type === 0 && this.sysMenu.parentId !== 0) {
this.sysMenu.component = 'ParentView'
}
this.$refs.dataForm.validate(valid => {
if (valid) {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.sysMenu.id) {
this.saveData()
} else {
this.updateData()
}
}
})
},
// 新增
saveData() {
api.save(this.sysMenu).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
},
// 根据id更新记录
updateData() {
api.updateById(this.sysMenu).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData()
})
}
}
}
</script>
页面展示
8.2、角色分配菜单功能
需求分析
编写代码
整合前端
router/index.js
{
path: 'assignAuth',
component: () => import('@/views/system/sysRole/assignAuth'),
meta: {
activeMenu: '/system/sysRole',
title: '角色授权'
},
hidden: true,
}
sysRole/list.vue
<el-button type="warning" icon="el-icon-baseball" size="mini" @click="showAssignAuth(scope.row)" title="分配权限"/>
// 跳转到分配菜单的页面
showAssignAuth(row) {
this.$router.push('/system/assignAuth?id='+row.id+'&roleName='+row.roleName);
},
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
<template>
<div class="app-container">
<div style="padding: 20px 20px 0 20px;">
授权角色:{{ $route.query.roleName }}
</div>
<el-tree
style="margin: 20px 0"
ref="tree"
:data="sysMenuList"
node-key="id"
show-checkbox
default-expand-all
:props="defaultProps"
/>
<div style="padding: 20px 20px;">
<el-button :loading="loading" type="primary" icon="el-icon-check" size="mini" @click="save">保存</el-button>
<el-button @click="$router.push('/system/sysRole')" size="mini" icon="el-icon-refresh-right">返回</el-button>
</div>
</div>
</template>
<script>
import api from '@/api/system/sysMenu'
export default {
name: 'roleAuth',
data() {
return {
loading: false, // 用来标识是否正在保存请求中的标识, 防止重复提交
sysMenuList: [], // 所有
defaultProps: {
children: 'children',
label: 'name'
},
};
},
created() {
this.fetchData()
},
methods: {
/*
初始化
*/
fetchData() {
const roleId = this.$route.query.id
api.toAssign(roleId).then(result => {
const sysMenuList = result.data
this.sysMenuList = sysMenuList
const checkedIds = this.getCheckedIds(sysMenuList)
console.log('getPermissions() checkedIds', checkedIds)
this.$refs.tree.setCheckedKeys(checkedIds)
})
},
/*
得到所有选中的id列表
*/
getCheckedIds (auths, initArr = []) {
return auths.reduce((pre, item) => {
if (item.select && item.children.length === 0) {
pre.push(item.id)
} else if (item.children) {
this.getCheckedIds(item.children, initArr)
}
return pre
}, initArr)
},
/*
保存权限列表
*/
save() {
// debugger
//获取到当前子节点
//const checkedNodes = this.$refs.tree.getCheckedNodes()
//获取到当前子节点及父节点
const allCheckedNodes = this.$refs.tree.getCheckedNodes(false, true);
let idList = allCheckedNodes.map(node => node.id);
console.log(idList)
let assginMenuVo = {
roleId: this.$route.query.id,
menuIdList: idList
}
this.loading = true
api.doAssign(assginMenuVo).then(result => {
this.loading = false
this.$message.success(result.$message || '分配权限成功')
this.$router.push('/system/sysRole');
})
}
}
};
</script>
页面展示
9、权限管理(重难点)
9.1、用户登录权限管理
需求分析
引入JWT
-
JWT是JSON Web Token的缩写
-
最重要的作用就是对 token信息的防伪作用。
common-util
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
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<Claims> 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<Claims> 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<String, Object> map = new HashMap<>();
// map.put("token","admin-token");
// return Result.ok(map);
// 1、获取用户名和密码
// 2、根据用户名查询数据库
String username = loginVo.getUsername();
LambdaQueryWrapper<SysUser> 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<String, Object> 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<RouterVo> routerList = sysMenuService.findUserMenuListByUserId(userId);
// 5、根据 用户id 获取用户可以操作的按钮列表
List<String> permsList = sysMenuService.findUserPermsByUserId(userId);
// 6、返回相应的数据
Map<String, Object> 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<RouterVo> findUserMenuListByUserId(Long userId);
// 根据 用户id 获取用户可以操作的按钮列表
List<String> findUserPermsByUserId(Long userId);
SysMenuServiceImpl
// 根据 用户id 获取用户可以操作的菜单列表
@Override
public List<RouterVo> findUserMenuListByUserId(Long userId) {
List<SysMenu> sysMenusList = null;
// 1、判断当前用户是否是管理员 userId=1 是管理员
// 1.1、 如果是管理员,查询所有菜单列表
if (userId.longValue() == 1) {
// 查询所有菜单列表
LambdaQueryWrapper<SysMenu> 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<SysMenu> sysMenuTreeList = MenuHelper.buildTree(sysMenusList);
// 构建框架要求的路由结构
List<RouterVo> routerList = this.buildRouter(sysMenuTreeList);
return routerList;
}
// 构建框架要求的路由结构
private List<RouterVo> buildRouter(List<SysMenu> menus) {
// 创建 list 集合,存值最终数据
List<RouterVo> 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<SysMenu> children = menu.getChildren();
if (menu.getType().intValue() == 1) {
// 加载隐藏路由
List<SysMenu> 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<String> findUserPermsByUserId(Long userId) {
// 1、判断是否是管理员,如果是管理员,查询所有按钮列表
List<SysMenu> sysMenusList = null;
if (userId.longValue() == 1) {
// 查询所有菜单列表
LambdaQueryWrapper<SysMenu> 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<String> permsList = sysMenusList.stream()
.filter(item -> item.getType() == 2)
.map(item -> item.getPerms())
.collect(Collectors.toList());
return permsList;
}
接口测试
登录接口测试
info接口测试
解决思路是:
1、在pom.xml添加
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes> <include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2、application-dev.yml添加
mybatis-plus:
mapper-locations: classpath:com/atguigu/auth/mapper/xml/*.xml
整合前端
从这部分开始,整合前端不在写了,比较麻烦,直接复用现有的
页面展示
9.2、用户认证
整合SpringSecurity
本项目采用 Spring-Security
来做用户认证和权限控制,也可以采用 Shiro
引入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jerry</groupId>
<artifactId>common</artifactId>
<version>1.0</version>
</parent>
<artifactId>spring-security</artifactId>
<dependencies>
<dependency>
<groupId>com.jerry</groupId>
<artifactId>common-util</artifactId>
<version>1.0</version>
</dependency>
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>
</dependencies>
</project>
添加配置类
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
用户认证
流程分析
自定义组件的编写
操作spring-security
module
自定义加密器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<? extends GrantedAuthority> 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-oa
module
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<SysUser> 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 <br>
* Package: com.jerry.security.filter <br>
* 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<String, Object> 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 <br>
* Package: com.jerry.common.result <br>
* 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 <br>
* Package: com.jerry.security.filter <br>
* 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<String> userPermsList = sysMenuService.findUserPermsByUserId(sysUser.getId());
// 创建list集合,封装最终权限数据
List<SimpleGrantedAuthority> authList = new ArrayList<>();
// 遍历 authList
for (String perms : userPermsList) {
authList.add(new SimpleGrantedAuthority(perms.trim()));
}
return new CustomUser(sysUser, authList);
spring-security模块配置redis
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
修改TokenLoginFilter
修改TokenAuthenticationFilter
修改WebSecurityConfig类
配置类添加注解:
开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认
service-oa模块添加redis配置
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模块引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<scope>provided</scope>
</dependency>
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
中
<!--引入activiti的springboot启动器 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
<exclusions>
<exclusion>
<artifactId>mybatis</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
添加配置
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
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef">
<process id="qingjia" isExecutable="true">
<startEvent id="sid-14A3E2A6-84E4-49E0-BF92-3DABD741430B"></startEvent>
<userTask id="sid-38632C81-C407-4F0D-944D-FC30F90637A3" name="张三审批" activiti:assignee="zhangsan"></userTask>
<sequenceFlow id="sid-081A176E-6756-4C4C-B36C-2649B12CFC5D" sourceRef="sid-14A3E2A6-84E4-49E0-BF92-3DABD741430B" targetRef="sid-38632C81-C407-4F0D-944D-FC30F90637A3"></sequenceFlow>
<userTask id="sid-655780D5-8492-494F-9E30-2CFD6691E98D" name="李四审批" activiti:assignee="lisi"></userTask>
<sequenceFlow id="sid-7DCE821D-4AE0-4F27-9811-80B575E7A758" sourceRef="sid-38632C81-C407-4F0D-944D-FC30F90637A3" targetRef="sid-655780D5-8492-494F-9E30-2CFD6691E98D"></sequenceFlow>
<endEvent id="sid-7EE28419-BC61-49AC-8990-C63C4D2F7C0D"></endEvent>
<sequenceFlow id="sid-2E583A5C-265A-4C05-B5E1-7F5DB98291F1" sourceRef="sid-655780D5-8492-494F-9E30-2CFD6691E98D" targetRef="sid-7EE28419-BC61-49AC-8990-C63C4D2F7C0D"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_qingjia">
<bpmndi:BPMNPlane bpmnElement="qingjia" id="BPMNPlane_qingjia">
<bpmndi:BPMNShape bpmnElement="sid-14A3E2A6-84E4-49E0-BF92-3DABD741430B" id="BPMNShape_sid-14A3E2A6-84E4-49E0-BF92-3DABD741430B">
<omgdc:Bounds height="30.0" width="30.0" x="93.5" y="75.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-38632C81-C407-4F0D-944D-FC30F90637A3" id="BPMNShape_sid-38632C81-C407-4F0D-944D-FC30F90637A3">
<omgdc:Bounds height="80.0" width="100.0" x="168.5" y="50.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-655780D5-8492-494F-9E30-2CFD6691E98D" id="BPMNShape_sid-655780D5-8492-494F-9E30-2CFD6691E98D">
<omgdc:Bounds height="80.0" width="100.0" x="313.5" y="50.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-7EE28419-BC61-49AC-8990-C63C4D2F7C0D" id="BPMNShape_sid-7EE28419-BC61-49AC-8990-C63C4D2F7C0D">
<omgdc:Bounds height="28.0" width="28.0" x="458.5" y="76.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-7DCE821D-4AE0-4F27-9811-80B575E7A758" id="BPMNEdge_sid-7DCE821D-4AE0-4F27-9811-80B575E7A758">
<omgdi:waypoint x="268.5" y="90.0"></omgdi:waypoint>
<omgdi:waypoint x="313.5" y="90.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-081A176E-6756-4C4C-B36C-2649B12CFC5D" id="BPMNEdge_sid-081A176E-6756-4C4C-B36C-2649B12CFC5D">
<omgdi:waypoint x="123.5" y="90.0"></omgdi:waypoint>
<omgdi:waypoint x="168.5" y="90.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-2E583A5C-265A-4C05-B5E1-7F5DB98291F1" id="BPMNEdge_sid-2E583A5C-265A-4C05-B5E1-7F5DB98291F1">
<omgdi:waypoint x="413.5" y="90.0"></omgdi:waypoint>
<omgdi:waypoint x="458.5" y="90.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
将资源文件放入项目
在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 <br>
* Package: com.jerry.activiti <br>
* 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 <br>
* Package: com.jerry.activiti <br>
* 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<ProcessDefinition> 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<HistoricTaskInstance> 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<Task> 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 <br>
* Package: com.jerry.auth.activiti <br>
* 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<Task> 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<Task> 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<String, Object> 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<Task> 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 <br>
* Package: com.jerry.auth.activiti <br>
* 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 <br>
* Package: com.jerry.auth.activiti <br>
* 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<String, Object> 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<Task> 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<Task> 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 <br>
* Package: com.jerry.auth.activiti <br>
* 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<String, Object> 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<Task> 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.*;
/**
* <p>
* 审批类型 前端控制器
* </p>
*
* @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<ProcessType> pageInfo = new Page<>(page, pageSize);
Page<ProcessType> 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.*;
/**
* <p>
* 审批模板 前端控制器
* </p>
*
* @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<ProcessTemplate> pageInfo = new Page<>(page, pageSize);
//分页查询审批模板,把审批类型对应名称查询
IPage<ProcessTemplate> 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<String, Object> 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:CodeLifeIdeaProjectguigu-oaguigu-oa-parentservice-oatargetclasses
} 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;
/**
* <p>
* 审批类型 前端控制器
* </p>
*
* @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<ProcessVo> pageInfo = new Page<>(page, limit);
IPage<ProcessVo> pageModel = processService.selectPage(pageInfo,processQueryVo);
return Result.ok();
}
}
OaProcessService
public interface OaProcessService extends IService<Process> {
//审批管理列表
IPage<ProcessVo> selectPage(Page<ProcessVo> pageInfo, ProcessQueryVo processQueryVo);
}
OaProcessServiceImpl
//审批管理列表
@Override
public IPage<ProcessVo> selectPage(Page<ProcessVo> pageInfo, ProcessQueryVo processQueryVo) {
IPage<ProcessVo> pageModel = baseMapper.selectPage(pageInfo,processQueryVo);
return pageModel;
}
OaProcessMapper
//审批管理列表
IPage<ProcessVo> selectPage(Page<ProcessVo> pageInfo, @Param("vo") ProcessQueryVo processQueryVo);
OaProcessMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jerry.process.mapper.OaProcessMapper">
<select id="selectPage" resultType="com.jerry.vo.process.ProcessVo">
SELECT
a.id,a.process_code,a.user_id,a.process_template_id,a.process_type_id,a.title,a.description,a.form_values,a.process_instance_id,a.current_auditor,a.status,a.create_time,a.update_time,
b.name AS processTemplateName,
c.name AS processTypeName,
d.name
FROM oa_process a
LEFT JOIN sys_user d ON a.user_id =d.id
LEFT JOIN oa_process_template b ON a.process_template_id = b.id
LEFT JOIN oa_process_type c ON a.process_type_id = c.id
<where>
<if test="vo.keyword != null and vo.keyword != ''">
and (a.process_code like CONCAT('%',#{vo.keyword},'%') or
a.title like CONCAT('%',#{vo.keyword},'%'))
</if>
<if test="vo.userId != null and vo.userId != ''">
and a.user_id = #{vo.userId}
</if>
<if test="vo.status != null and vo.status != ''">
and a.status = #{vo.status}
</if>
<if test="vo.createTimeBegin != null and vo.createTimeBegin != ''">
and a.create_time >= #{vo.createTimeBegin}
</if>
<if test="vo.createTimeEnd != null and vo.createTimeEnd != ''">
and a.create_time <= #{vo.createTimeEnd}
</if>
</where>
</select>
</mapper>
修改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-oaguigu-oa-webnode_modulesnode-sass
npm ERR! command failed
npm ERR! command C:WINDOWSsystem32cmd.exe /d /s /c node scripts/build.js
npm ERR! Building: E:nodejsnode.exe F:guigu-oaguigu-oa-webnode_modulesnode-gypbinnode-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
v14.15.0
13、代码托管
Git
Gitee
https://gitee.com/jinyang-jy/OnlineOfficeSystem.git
GitHub
网盘资料
链接:https://pan.baidu.com/s/1ZVNqzPlcfMH89NgUYNYZtQ?pwd=2022
提取码:2022
原文地址:https://blog.csdn.net/qq_44807756/article/details/129386363
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_17167.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!