云尚办公笔记(代码注释非常详细)

2023-11-23 14:30

本文主要是介绍云尚办公笔记(代码注释非常详细),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 搭建环境

1.1 项目模块

  • 只使用了一个模块,感觉更加方便
    在这里插入图片描述

1.2 配置依赖关系

  • 只有一个模块,因此只使用了一个配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><!--    spingboot的版本--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.atguigu</groupId><artifactId>gui-oa-java</artifactId><version>0.0.1-SNAPSHOT</version><name>gui-oa-java</name><description>gui-oa-java</description><properties>
<!--        统一配置版本--><java.version>1.8</java.version><java.version>1.8</java.version><mybatis-plus.version>3.4.1</mybatis-plus.version><mysql.version>8.0.30</mysql.version><knife4j.version>3.0.3</knife4j.version><jwt.version>0.9.1</jwt.version><fastjson.version>2.0.21</fastjson.version></properties><dependencies>
<!--        spingboot启动类--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatis-plus 持久层--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!--        mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version></dependency><!--        mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><!--        springboot测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></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-->
<!--        可用于将Java对象转换为其JSON表示。它还可用于将JSON字符串转换为等效的Java对象--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><!--        lombok用来简化实体类--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><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>

1.3 MyBatis-Plus

  • 已学过黑马视频
  • 黑马视频链接

1.4 MyBatis-Plus入门

  • 已学过黑马视频
  • 黑马视频链接

1.4.1 MyBatis-Plus条件构造器

  • 1 设置配置文件 com.atguigu.guioajava.config
package com.atguigu.guioajava.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
// 实际上是要扫描到mapper包
//@MapperScan("com.atguigu.guioajava.serviceoa")
public class MybatisPlusConfig {/*** 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置* MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 注意自己数据库的类型interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}@Beanpublic ConfigurationCustomizer configurationCustomizer() {return configuration -> configuration.setUseDeprecatedExecutor(false);}
}
  • 2 编写代码
    /*** 条件分页查询* @param page 当前页* @param limit 每页显示记录数* @param sysRoleQueryVo 条件对象* @return*/@ApiOperation("条件分页查询")@GetMapping("{page}/{limit}")public Result pageQueryRole(@PathVariable Long page,@PathVariable Long limit,SysRoleQueryVo sysRoleQueryVo) {//调用service的方法实现//1 创建Page对象,传递分页相关参数//page 当前页  limit 每页显示记录数Page<SysRole> pageParam = new Page<>(page,limit);//2 封装条件,判断条件是否为空,不为空进行封装// 用StringUtils判断字符串是否为空  org.springframework.util.StringUtilsLambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();String roleName = sysRoleQueryVo.getRoleName();wrapper.like(!StringUtils.isEmpty(roleName),SysRole::getRoleName,roleName);//3 调用方法实现IPage<SysRole> pageModel = sysRoleService.page(pageParam, wrapper);System.out.println(pageModel);return Result.ok(SUCCESS,pageModel);}
  • 结果的格式
    在这里插入图片描述

1.5 角色管理

1.5.1 定义统一返回结果对象

  • 定义枚举类 com.atguigu.guioajava.common.result
package com.atguigu.guioajava.common.result;import lombok.Getter;
/*** 统一返回结果状态信息类*/
@Getter
public enum ResultCodeEnum {SUCCESS(200,"成功"),FAIL(201, "失败"),SERVICE_ERROR(2012, "服务异常"),DATA_ERROR(204, "数据异常"),LOGIN_AUTH(208, "未登陆"),PERMISSION(209, "没有权限");private Integer code;private String message;private ResultCodeEnum(Integer code, String message) {this.code = code;this.message = message;}
}
  • 2 定义结果类(和老师写的有些不同)
  • com.atguigu.guioajava.common.result
package com.atguigu.guioajava.common.result;import lombok.Data;@Data
public class Result<T> {//返回码private Integer code;//返回消息private String message;//返回数据private T data;// 构造私有化   不能new啦private Result(){};// 1 成功// 1.1 自己写code和messagepublic static <T> Result<T> ok(Integer code, String message) {Result<T> result = new Result<T>();result.setCode(code);result.setMessage(message);return result;}// 1.2 ResultCodeEnum设置的code和messagepublic static <T> Result<T> ok(ResultCodeEnum resultCodeEnum) {Result<T> result = new Result<T>();result.setCode(resultCodeEnum.getCode());result.setMessage(resultCodeEnum.getMessage());return result;}// 1.3 自己写code  message  datapublic static <T> Result<T> ok(Integer code, String message,T data) {Result<T> result = new Result<T>();result.setCode(code);result.setMessage(message);result.setData(data);return result;}// 1.4 ResultCodeEnum设置的code和message  自己传入datapublic static <T> Result<T> ok(ResultCodeEnum resultCodeEnum,T data) {Result<T> result = new Result<T>();result.setCode(resultCodeEnum.getCode());result.setMessage(resultCodeEnum.getMessage());result.setData(data);return result;}// 1.5 直接设置好成功操作public static <T> Result<T> ok() {Result<T> result = new Result<T>();result.setCode(200);result.setMessage("成功");return result;}// 2 失败// 2.1 ResultCodeEnum设置的code和messagepublic static <T> Result<T> fail(ResultCodeEnum resultCodeEnum) {Result<T> result = new Result<T>();result.setCode(resultCodeEnum.getCode());result.setMessage(resultCodeEnum.getMessage());return result;}// 2.2 自己写code  messagepublic static <T> Result<T> fail(Integer code, String message) {Result<T> result = new Result<T>();result.setCode(code);result.setMessage(message);return result;}// 1.3 直接设置好失败操作public static <T> Result<T> fail() {Result<T> result = new Result<T>();result.setCode(200);result.setMessage("数据异常");return result;}
}

1.5.2 knife4j

  • 文档地址:https://doc.xiaominfo.com/
  • knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案
  • 添加依赖
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
  • 添加knife4j配置类 com.atguigu.guioajava.config
package com.atguigu.guioajava.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
import java.util.ArrayList;
import java.util.List;
/*** knife4j配置信息* 直接全部复制 不然有些类的导入会出错*/
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfig {@Bean/*** 添加头的信息*/public Docket adminApiConfig(){List<Parameter> pars = new ArrayList<>();ParameterBuilder tokenPar = new ParameterBuilder();tokenPar.name("token").description("用户token").defaultValue("").modelRef(new ModelRef("string")).parameterType("header").required(false).build();pars.add(tokenPar.build());//添加head参数endDocket adminApi = new Docket(DocumentationType.SWAGGER_2).groupName("adminApi").apiInfo(adminApiInfo()).select()// 想要显示全部 就把这两句话注释掉//只作用于com.atguigu包下.apis(RequestHandlerSelectors.basePackage("com.atguigu"))//只显示admin路径下的页面.paths(PathSelectors.regex("/admin/.*")).build().globalOperationParameters(pars);return adminApi;}private ApiInfo adminApiInfo(){return new ApiInfoBuilder().title("后台管理系统-API文档").description("本文档描述了后台管理系统微服务接口定义").version("1.0").contact(new Contact("atguigu", "http://atguigu.com", "atguigu@qq.com")).build();}
}
  • Controller层添加注解
@Api(tags = "角色管理")
@RestController
@RequestMapping("/admin/system/sysRole")
public class SysRoleController {@Autowiredprivate SysRoleService sysRoleService;@ApiOperation(value = "获取全部角色列表")@GetMapping("findAll")public Result<List<SysRole>> findAll() {List<SysRole> roleList = sysRoleService.list();return Result.ok(roleList);}
}
  • 测试:http://localhost:8800/doc.html

配置日期时间格式
application-dev.yml添加以下内容
jackson在spring下面和datasource是一个级别的

  jackson:# 设置日期格式date-format: yyyy-MM-dd HH:mm:ss# 东八区  要加八个小时time-zone: GMT+8

1.5.3 统一异常处理

  • 全局异常处理
  • 特定异常处理
  • 自定义异常处理
package com.atguigu.guioajava.common.exception;
import com.atguigu.guioajava.common.result.ResultCodeEnum;
import lombok.Data;
/*** 业务异常:用户行为不规范*/
@Data
public class BusinessException extends RuntimeException{private Integer code;// 状态码private String msg;// 描述信息// 有code的public BusinessException(Integer code) {this.code = code;}public BusinessException(Integer code,String message) {super(message);this.code = code;this.msg = message;}/*** 接收枚举类型对象* @param resultCodeEnum*/public BusinessException(ResultCodeEnum resultCodeEnum) {super(resultCodeEnum.getMessage());this.code = resultCodeEnum.getCode();this.msg = resultCodeEnum.getMessage();}
}
package com.atguigu.guioajava.common.exception;import com.atguigu.guioajava.common.result.ResultCodeEnum;
import lombok.Data;/*** 系统异常*/
@Data
public class SystemException extends RuntimeException{private Integer code;// 状态码private String msg;// 描述信息public SystemException(Integer code) {this.code = code;}public SystemException(Integer code, String message) {super(message);this.code = code;this.msg = message;}/*** 接收枚举类型对象* @param resultCodeEnum*/public SystemException(ResultCodeEnum resultCodeEnum) {super(resultCodeEnum.getMessage());this.code = resultCodeEnum.getCode();this.msg = resultCodeEnum.getMessage();}
}
  • 创建统一异常处理器 com.atguigu.guioajava.common.exception
package com.atguigu.guioajava.common.exception;
import com.atguigu.guioajava.common.result.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/*** 全局异常处理类*/
@ControllerAdvice
public class GlobalExceptionHandler {// 执行的异常种类 全局异常处理@ExceptionHandler(Exception.class)// 返回json数据格式@ResponseBodypublic Result error(Exception e){// 输出异常信息//记录日志//发送消息给运维//发送邮件给开发人员,ex对象发送给开发人员e.printStackTrace();return Result.fail(201,e.getMessage());}// 特定异常处理@ExceptionHandler(ArithmeticException.class)// 返回json数据格式@ResponseBodypublic Result error(ArithmeticException e){// 输出异常信息//记录日志//发送消息给运维//发送邮件给开发人员,ex对象发送给开发人员e.printStackTrace();return Result.fail(201,e.getMessage());}// 业务异常处理@ExceptionHandler(BusinessException.class)// 返回json数据格式@ResponseBodypublic Result error(BusinessException e){// 输出异常信息//记录日志//发送消息给运维//发送邮件给开发人员,ex对象发送给开发人员e.printStackTrace();return Result.fail(e.getCode(),e.getMsg());}// 系统异常处理@ExceptionHandler(SystemException.class)// 返回json数据格式@ResponseBodypublic Result error(SystemException e){// 输出异常信息//记录日志//发送消息给运维//发送邮件给开发人员,ex对象发送给开发人员e.printStackTrace();return Result.fail(e.getCode(),e.getMsg());}
}

完整的Controller层代码

package com.atguigu.guioajava.serviceoa.controller;
import com.atguigu.guioajava.common.exception.BusinessException;
import com.atguigu.guioajava.common.result.Result;
import com.atguigu.guioajava.enity.model.system.SysRole;
import com.atguigu.guioajava.enity.vo.system.SysRoleQueryVo;
import com.atguigu.guioajava.serviceoa.service.SysRoleService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.atguigu.guioajava.common.result.ResultCodeEnum.FAIL;
import static com.atguigu.guioajava.common.result.ResultCodeEnum.SUCCESS;
@Api(tags = "角色管理")
@RestController
@RequestMapping("/admin/system/sysRole")
public class SysRoleController {@Autowiredprivate SysRoleService sysRoleService;@ApiOperation(value = "获取全部角色列表")@GetMapping("findAll")public Result<List<SysRole>> findAll() {// java.lang.ArithmeticException lang后面才是异常的class// int a=10/0;
//        try {
//            int a = 10/0;
//        }catch(Exception e) {
//            throw new BusinessException(201,"出现自定义异常");
//        }List<SysRole> roleList = sysRoleService.list();return Result.ok(SUCCESS,roleList);}/*** 条件分页查询* @param page 当前页* @param limit 每页显示记录数* @param sysRoleQueryVo 条件对象* @return*/@ApiOperation("条件分页查询")@GetMapping("{page}/{limit}")public Result pageQueryRole(@PathVariable Long page,@PathVariable Long limit,SysRoleQueryVo sysRoleQueryVo) {//调用service的方法实现//1 创建Page对象,传递分页相关参数//page 当前页  limit 每页显示记录数Page<SysRole> pageParam = new Page<>(page,limit);//2 封装条件,判断条件是否为空,不为空进行封装// 用StringUtils判断字符串是否为空  org.springframework.util.StringUtilsLambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();String roleName = sysRoleQueryVo.getRoleName();wrapper.like(!StringUtils.isEmpty(roleName),SysRole::getRoleName,roleName);//3 调用方法实现IPage<SysRole> pageModel = sysRoleService.page(pageParam, wrapper);System.out.println(pageModel);return Result.ok(SUCCESS,pageModel);}@ApiOperation(value = "根据id获取角色")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {SysRole role = sysRoleService.getById(id);// 三元运算符 更加优雅return null == role ? Result.fail(FAIL) : Result.ok(SUCCESS,role);}@ApiOperation(value = "新增角色")@PostMapping("save")public Result save(@RequestBody @Validated SysRole role) {sysRoleService.save(role);return Result.ok();}@ApiOperation(value = "修改角色")@PutMapping("update")public Result updateById(@RequestBody SysRole role) {sysRoleService.updateById(role);return Result.ok();}@ApiOperation(value = "根据id删除角色")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {boolean b = sysRoleService.removeById(id);return b ? Result.ok() : Result.fail(FAIL);}@ApiOperation(value = "根据id列表删除")@DeleteMapping("batchRemove")// 前端数组[1,2,3](前端list最终会转化为数组格式)  @RequestBody:后端将数组变为Listpublic Result batchRemove(@RequestBody List<Long> idList) {boolean b = sysRoleService.removeByIds(idList);return b ? Result.ok() : Result.fail(FAIL);}
}

2 前端基础知识(已学过)

3 角色管理前端

3.1 前端框架

3.1.1 vue-element-admin

  • vue-element-admin是基于element-ui 的一套后台管理系统集成方案
  • GitHub地址
  • 项目在线预览

3.1.2 vue-admin-template

3.1.2.1 简介
  • vue-admin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版)
  • GitHub地址
  • vue-admin-template 的基础上进行二次开发,把 vue-element-admin当做工具箱
3.1.2.2 使用

在这里插入图片描述

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

3.1.3 实现登录&退出登录

在这里插入图片描述

3.1.3.1 改变登陆的访问路径
  • 方法一:.env.development文件
  • 本地IP+端口号
# base api
# VUE_APP_BASE_API = '/dev-api'
VUE_APP_BASE_API = 'http://localhost/8800'
  • 方法二:vue.config.js文件 第四十行
  • 注释掉mock接口配置
  • 配置代理转发请求到目标接口
    // 把这一段话注释掉// before: require('./mock/mock-server.js')proxy: {'/dev-api': { // 匹配所有以 '/dev-api'开头的请求路径// 把所有以/dev-api开头的请求路径转为http://localhost:8080target: 'http://localhost:8080',changeOrigin: true, // 支持跨域pathRewrite: { // 重写路径: 去掉路径中开头的'/dev-api''^/dev-api': ''}}}

在这里插入图片描述

3.1.3.2 后端接口
  • 在后端创建相关接口返回与Mock相同的数据
package com.atguigu.guioajava.serviceoa.controller;import com.atguigu.guioajava.common.result.Result;
import com.atguigu.guioajava.common.result.ResultCodeEnum;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;/*** <p>* 后台登录登出* </p>*/
@Api(tags = "后台登录管理")
@RestController
@RequestMapping("/admin/system/index")
public class IndexController {/*** 登录* @return*/@PostMapping("login")public Result login() {Map<String, Object> map = new HashMap<>();// {code: 200, message: "成功", data: {token: "admin"}}map.put("token","admin");return Result.ok(ResultCodeEnum.SUCCESS,map);}/*** 获取用户信息* @return*/@GetMapping("info")public Result info() {Map<String, Object> map = new HashMap<>();// avatar: "https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg"// name: "admin"// roles: "[admin]"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(ResultCodeEnum.SUCCESS,map);}/*** 退出* @return*/@PostMapping("logout")public Result logout(){return Result.ok();}
}
3.1.3.3 前端部分
  • 修改登录退出相关接口的路径
  • api的user.js文件(三个都要改)
export function login(data) {return request({// url: '/vue-admin-template/user/login',url: '/vue-admin-template/user/login',method: 'post',data})
}
  • 修改返回状态码,返回成功默认为20000,修改为200
  • src\utils\request.js(48行)
    // if the custom code is not 20000, it is judged as an error.// if (res.code !== 20000) {if (res.code !== 200) {Message({message: res.message || 'Error',type: 'error',duration: 5 * 1000})
    // if (store.getters.token) {//   // let each request carry token//   // ['X-Token'] is a custom headers key//   // please modify it according to the actual situation//   config.headers['X-Token'] = getToken()// }// 修改的地方:请求自动携带token, 且请求头名称为 tokenconst token = store.getters.tokenif (token) {// let each request carry token// ['X-Token'] is a custom headers key// please modify it according to the actual situation// config.headers['X-Token'] = getToken()config.headers['token'] = token}

4 用户管理

  • 需求分析
    在这里插入图片描述

4.1 代码生成器

  • 1 引入依赖
	<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>
  • 2 代码
package com.atguigu.common.codeGenerator;
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();// 前面是项目部工程的绝对路径  后面是到javagc.setOutputDir("D:\\Code_Java\\guigu-oa\\guigu-oa-java"+"/src/main/java");gc.setServiceName("%sService");	//去掉Service接口的首字母Igc.setAuthor("atguigu");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("123456");dsc.setDbType(DbType.MYSQL);mpg.setDataSource(dsc);// 4、包配置PackageConfig pc = new PackageConfig();pc.setParent("com.atguigu");pc.setModuleName("serviceoa"); //模块名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();}
}

4.2 CRUD(复制粘贴即可)

  • 后端
package com.atguigu.serviceoa.controller;
import com.atguigu.common.result.Result;
import com.atguigu.common.result.ResultCodeEnum;
import com.atguigu.enity.model.system.SysUser;
import com.atguigu.enity.vo.system.SysUserQueryVo;
import com.atguigu.serviceoa.service.SysUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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 atguigu* @since 2023-04-21*/
@RestController
@RequestMapping("admin/system/sysUser")
public class SysUserController {@Autowiredprivate SysUserService sysUserService;//用户条件分页查询@ApiOperation("用户条件分页查询")@GetMapping("{page}/{limit}")public Result index(@PathVariable Long page,@PathVariable Long limit,SysUserQueryVo sysUserQueryVo) {//创建page对象Page<SysUser> pageParam = new Page<>(page,limit);//封装条件,判断条件值不为空LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();//获取条件值String username = sysUserQueryVo.getKeyword();String createTimeBegin = sysUserQueryVo.getCreateTimeBegin();String createTimeEnd = sysUserQueryVo.getCreateTimeEnd();//判断条件值不为空//like 模糊查询wrapper.like(!StringUtils.isEmpty(username),SysUser::getUsername,username);// 用户的创建时间在createTimeBegin和createTimeEnd之间//ge 大于等于wrapper.ge(!StringUtils.isEmpty(createTimeBegin),SysUser::getCreateTime,createTimeBegin);//le 小于等于wrapper.le(!StringUtils.isEmpty(createTimeEnd),SysUser::getCreateTime,createTimeEnd);//调用mp的方法实现条件分页查询IPage<SysUser> pageModel = sysUserService.page(pageParam, wrapper);return Result.ok(ResultCodeEnum.SUCCESS,pageModel);}@ApiOperation(value = "获取用户")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {SysUser user = sysUserService.getById(id);return Result.ok(ResultCodeEnum.SUCCESS,user);}@ApiOperation(value = "保存用户")@PostMapping("save")public Result save(@RequestBody SysUser user) {sysUserService.save(user);return Result.ok();}@ApiOperation(value = "更新用户")@PutMapping("update")public Result updateById(@RequestBody SysUser user) {sysUserService.updateById(user);return Result.ok();}@ApiOperation(value = "删除用户")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {sysUserService.removeById(id);return Result.ok();}
}
  • 前端

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

{path: '/system',component: Layout,meta: {title: '系统管理',icon: 'el-icon-s-tools'},alwaysShow: true,children: [{name: 'sysUser',path: 'sysUser',component: () => import('@/views/system/sysUser/list'),meta: {title: '用户管理',icon: 'el-icon-s-custom'},},{path: 'sysRole',component: () => import('@/views/system/sysRole/list'),meta: {title: '角色管理',icon: 'el-icon-s-help'},}]
},
  • 定义基础api
  • 创建文件 src/api/system/sysUser.js(复制粘贴即可)
  • 实现页面功能
  • 创建src/views/system/sysUser/list.vue

4.3 给用户分配角色

  • 需求分析

在这里插入图片描述

  • 操作类:SysRoleController
    @ApiOperation(value = "根据用户ID获取角色数据")@GetMapping("/toAssign/{userId}")public Result toAssign(@PathVariable Long userId) {Map<String, Object> roleMap = sysRoleService.findRoleByUserId(userId);return Result.ok(roleMap);}@ApiOperation(value = "为一个用户分配多个角色")@PostMapping("/doAssign")public Result doAssign(@RequestBody AssginRoleVo assginRoleVo) {sysRoleService.doAssign(assginRoleVo);return Result.ok();}
  • service接口 && service接口实现
  • 操作类:SysRoleService
    // 根据用户ID获取角色数据Map<String, Object> findRoleByUserId(Long userId);// 为一个用户分配多个角色void doAssign(AssginRoleVo assginRoleVo);
  • 操作类:SysRoleServiceImpl
package com.atguigu.serviceoa.service.impl;
import com.atguigu.enity.model.system.SysRole;
import com.atguigu.enity.model.system.SysUserRole;
import com.atguigu.enity.vo.system.AssginRoleVo;
import com.atguigu.serviceoa.mapper.SysRoleMapper;
import com.atguigu.serviceoa.service.SysRoleService;
import com.atguigu.serviceoa.service.SysUserRoleService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@Service
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {@Autowiredprivate SysUserRoleService sysUserRoleService;// 根据用户ID获取角色数据@Overridepublic Map<String, Object> findRoleByUserId(Long userId) {// 1 查询所有的角色 返回List集合List<SysRole> allSysRoles = baseMapper.selectList(null);// 2 根据用户ID获取所属角色ID// 从查询出的所有角色中获取角色IDLambdaQueryWrapper<SysUserRole> lqwUserRole = new LambdaQueryWrapper<>();lqwUserRole.eq(SysUserRole::getUserId,userId);List<SysUserRole> sysUserRoleList = sysUserRoleService.list(lqwUserRole);List<Long> roleIdList = sysUserRoleList.stream().map(item -> item.getRoleId()).collect(Collectors.toList());// 3 根据所属角色ID查询出相应的角色对象// 从查询出的所有角色中获取角色对象List<SysRole> sysRoleList = new ArrayList<>();allSysRoles.stream().forEach( item ->{if (roleIdList.contains(item.getId())){sysRoleList.add(item);}});// 4 进行数据封装Map<String, Object> roleMap = new HashMap<>();roleMap.put("assginRoleList", sysRoleList);roleMap.put("allRolesList",allSysRoles);return roleMap;}// 为一个用户分配多个角色@Overridepublic void doAssign(AssginRoleVo assginRoleVo) {// 1 根据userId删除sys_role_user表中的数据LambdaQueryWrapper<SysUserRole> lqw = new LambdaQueryWrapper<>();lqw.eq(SysUserRole::getUserId,assginRoleVo.getUserId());sysUserRoleService.remove(lqw);// 2 根据assginRoleVo中的RoleIdList重新分配List<Long> roleIdList = assginRoleVo.getRoleIdList();// 定义一个集合 一次性存入数据库List<SysUserRole> sysUserRoleList = new ArrayList<>();roleIdList.stream().forEach( item ->{SysUserRole sysUserRole = new SysUserRole();sysUserRole.setUserId(assginRoleVo.getUserId());sysUserRole.setRoleId(item);sysUserRoleList.add(sysUserRole);});sysUserRoleService.saveBatch(sysUserRoleList);}
}

4.4 更改用户状态&&整合前端

  • 需求分析

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

  • 操作类:SysUserController
    @ApiOperation(value = "更新状态")@GetMapping("updateStatus/{id}/{status}")public Result updateStatus(@PathVariable Long id, @PathVariable Integer status) {sysUserService.updateStatus(id, status);return Result.ok();}
  • 操作类:SysUserService
    // 更新用户状态void updateStatus(Long id, Integer status);
  • 操作类:SysUserServiceImpl
    // 更新用户状态@Overridepublic void updateStatus(Long id, Integer status) {// 1 根据用户ID获取用户对象SysUser sysUser = baseMapper.selectById(id);// 2 设置状态修改值sysUser.setStatus(status);// 3 调用方法进行修改baseMapper.updateById(sysUser);}
  • 整合前端(复制粘贴即可)

5 菜单管理

  • 需求分析
    在这里插入图片描述
  • 代码生成器(连续生成两个)
        // 指定表的名称strategy.setInclude("sys_menu","sys_role_menu");

5.1 CRUD

  • SysMenuController
package com.atguigu.serviceoa.controller;
import com.atguigu.common.result.Result;
import com.atguigu.enity.model.system.SysMenu;
import com.atguigu.serviceoa.service.SysMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;/*** <p>* 菜单表 前端控制器* </p>** @author atguigu* @since 2023-04-21*/
@Api(tags = "菜单管理")
@RestController
@RequestMapping("/admin/system/sysMenu")
public class SysMenuController {@Autowiredprivate 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.removeById(id);return Result.ok();}
}

5.2 获取菜单

  • 需求分析
    在这里插入图片描述

  • sysMenu实体类

package com.atguigu.enity.model.system;
import com.atguigu.enity.model.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;@Data
@ApiModel(description = "菜单")
@TableName("sys_menu")
public class SysMenu extends BaseEntity {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "所属上级")@TableField("parent_id")private Long parentId;@ApiModelProperty(value = "名称")@TableField("name")private String name;@ApiModelProperty(value = "类型(1:菜单,2:按钮)")@TableField("type")private Integer type;@ApiModelProperty(value = "路由地址")@TableField("path")private String path;@ApiModelProperty(value = "组件路径")@TableField("component")private String component;@ApiModelProperty(value = "权限标识")@TableField("perms")private String perms;@ApiModelProperty(value = "图标")@TableField("icon")private String icon;@ApiModelProperty(value = "排序")@TableField("sort_value")private Integer sortValue;@ApiModelProperty(value = "状态(0:禁止,1:正常)")@TableField("status")private Integer status;// 下级列表@TableField(exist = false)private List<SysMenu> children;//是否选中@TableField(exist = false)private boolean isSelect;
}
  • SysUserService接口
    // 获取菜单List<SysMenu> findNodes();
  • SysUserServiceImpl实现
  • 构建树形结构
package com.atguigu.serviceoa.service.impl;
import com.atguigu.common.util.MenuHelper;
import com.atguigu.enity.model.system.SysMenu;
import com.atguigu.serviceoa.mapper.SysMenuMapper;
import com.atguigu.serviceoa.service.SysMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/*** 菜单表 服务实现类*/
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {@Overridepublic List<SysMenu> findNodes() {// 1 查询所有菜单数据List<SysMenu> allSysMenuList = baseMapper.selectList(null);// 2 构建树形结构
//        第一层
//                children:[
//                        第二层
//                                ...
//                        ]List<SysMenu> resultList = MenuHelper.buildTree(allSysMenuList);return resultList;}
}
  • 添加帮助类
  • com.atguigu.common.util.MenuHelper
package com.atguigu.common.util;
import com.atguigu.enity.model.system.SysMenu;
import java.util.ArrayList;
import java.util.List;
public class MenuHelper {/*** 使用递归方法建菜单* @param allSysMenuList* @return*/public static List<SysMenu> buildTree(List<SysMenu> allSysMenuList) {// 1 找到递归的入口  2 找到递归结束的条件// 1 创建List集合 用于存放最终数据List<SysMenu> trees = new ArrayList<>();// 2 把所有菜单那进行遍历allSysMenuList.forEach( sysMenu ->{// 递归入口进入 parentID = 0if (sysMenu.getParentId().longValue() == 0){// 定义一个方法 传入入口和所有菜单的数据trees.add(getChildrens(sysMenu,allSysMenuList));}});// 返回数据return trees;}// 定义一个方法 传入入口和所有菜单的数据// 按照入口查下一层数据  一层一层往下查private static SysMenu getChildrens(SysMenu sysMenu, List<SysMenu> allSysMenuList) {// 当前节点的ID与下一层节点的ID是否相同// 相同就封装起来  不一样 就没有// 遍历所有菜单数据  判断ID和allSysMenuList中的每一项的parentId对应关系allSysMenuList.stream().forEach( item ->{if (sysMenu.getId().longValue() == item.getParentId().longValue()){// 如果sysMenu.getChildren()为空 就要加一个空的集合if (sysMenu.getChildren()==null){sysMenu.setChildren(new ArrayList<>());}// 相同 封装起来// 这个是得到sysMenu的属性// 也可能还有下一层sysMenu.getChildren().add(getChildrens(item,allSysMenuList));}});return sysMenu;}
}

5.3 删除菜单方法的完善

  • 需求分析:当主菜单下面有子菜单的时候 不能删除 另外写个方法
  • com.atguigu.serviceoa.controller.SysMenuController
    @ApiOperation(value = "删除菜单")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {sysMenuService.removeMenuById(id);return Result.ok();}
  • com.atguigu.serviceoa.service.impl.SysMenuServiceImpl
    // 删除菜单@Overridepublic void removeMenuById(Long id) {// 判断当前菜单是否有子菜单LambdaQueryWrapper<SysMenu> lqw = new LambdaQueryWrapper<>();// 判断方式是当前菜单的id与其他菜单的parentID相同lqw.eq(SysMenu::getParentId,id);// 查询数量Integer integer = baseMapper.selectCount(lqw);if (integer>0){throw  new BusinessException(201,"当前菜单含有子菜单,不能删除");}else {baseMapper.deleteById(id);}}

5.4 用户管理前端实现(复制粘贴)

  • 添加路由:修改 src/router/index.js 文件
  • 定义基础api 创建文件 src/api/system/sysMenu.js
  • 实现页面功能 创建src/views/system/sysMenu/list.vue

5.5 给角色分配菜单

  • 需求分析
    在这里插入图片描述
  • 操作类:SysMenuController
    @ApiOperation(value = "查询所有菜单和根据角色ID获取分配给角色的菜单")@GetMapping("toAssign/{roleId}")public Result toAssign(@PathVariable Long roleId) {List<SysMenu> list = sysMenuService.findSysMenuByRoleId(roleId);return Result.ok(list);}@ApiOperation(value = "给角色分配菜单")@PostMapping("/doAssign")public Result doAssign(@RequestBody AssginMenuVo assginMenuVo) {sysMenuService.doAssign(assginMenuVo);return Result.ok();}
  • 操作类:SysMenuService
    // 查询所有菜单和根据角色ID获取分配给角色的菜单List<SysMenu> findSysMenuByRoleId(Long roleId);// 给角色分配菜单void doAssign(AssginMenuVo assginMenuVo);
  • 操作类:SysMenuServiceImpl
    // 查询所有菜单和根据角色ID获取分配给角色的菜单(菜单表中的isSelect要变为true)@Overridepublic List<SysMenu> findSysMenuByRoleId(Long roleId) {// 1 查询所有菜单  status==1 菜单才可以用LambdaQueryWrapper<SysMenu> lqwMenu = new LambdaQueryWrapper<>();lqwMenu.eq(SysMenu::getStatus,1);List<SysMenu> allSysMenus = baseMapper.selectList(lqwMenu);// 2 根据角色ID查询角色对应的所有菜单ID(SysRoleMenu)LambdaQueryWrapper<SysRoleMenu> lqwRoleMenu = new LambdaQueryWrapper<>();lqwRoleMenu.eq(SysRoleMenu::getRoleId,roleId);List<SysRoleMenu> sysRoleMenus = sysRoleMenuService.list(lqwRoleMenu);List<Long> roleIds = sysRoleMenus.stream().map(item -> item.getMenuId()).collect(Collectors.toList());// 3 拿菜单ID和所有菜单进行比较 有对应ID的 菜单表中的isSelect要变为trueallSysMenus.stream().forEach( item ->{if (roleIds.contains(item.getId())){item.setSelect(true);} else {item.setSelect(false);}});// 4 返回规定格式的菜单树形结构List<SysMenu> sysMenuTrees = MenuHelper.buildTree(allSysMenus);return sysMenuTrees;}// 给角色分配菜单@Overridepublic void doAssign(AssginMenuVo assginMenuVo) {// 1 根据角色ID删除角色菜单表中的数据LambdaQueryWrapper<SysRoleMenu> lqw = new LambdaQueryWrapper<>();lqw.eq(SysRoleMenu::getRoleId,assginMenuVo.getRoleId());sysRoleMenuService.remove(lqw);// 2 从参数中获取角色新分配的菜单ID 进行遍历 然后将数据添加入角色菜单表中List<Long> menuIdList = assginMenuVo.getMenuIdList();List<SysRoleMenu> sysRoleMenus =new ArrayList<>();// foreach似乎不能使用continuefor (Long item : menuIdList) {// 如果为空的话 就直接跳出本次循环if (StringUtils.isEmpty(item)) {continue;}SysRoleMenu sysRoleMenu = new SysRoleMenu();sysRoleMenu.setMenuId(item);sysRoleMenu.setRoleId(assginMenuVo.getRoleId());sysRoleMenus.add(sysRoleMenu);}// 将数据存入数据库sysRoleMenuService.saveBatch(sysRoleMenus);}
  • AssginMenuVo实体类
  • 给角色分配菜单方法的参数
package com.atguigu.enity.vo.system;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel(description = "分配菜单")
@Data
public class AssginMenuVo {@ApiModelProperty(value = "角色id")// 当前的角色IDprivate Long roleId;// 从前端返回的角色所选择的菜单ID的列表@ApiModelProperty(value = "菜单id列表")private List<Long> menuIdList;}

6 权限管理

6.1 权限管理介绍

  • 大致可归为三种:页面权限(菜单级)、操作权限(按钮级)、数据权限。当前系统只是讲解:菜单权限与按钮权限的控制。
    在这里插入图片描述

6.1.1 权限管理设计思路

  • 接下来需要实现这两个接口:

1、用户登录

2、登录成功根据token获取用户相关信息(菜单权限及按钮权限数据等)
在这 里插入  图片描述

6.2 JWT

  • 一个JWT由三个部分组成:JWT头、有效载荷、签名哈希,最后由这三者组合进行base64url编码得到JWT

6.2.1 项目集成JWT

  • 操作模块:common-util
  • 引入依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId>
</dependency>
  • 添加JWT帮助类
  • com.atguigu.common.util.JwtHelper
package com.atguigu.common.util;
import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;
import java.util.Date;
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;}// 从token字符串中获取用户IDpublic 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;}}// // 从token字符串中获取用户名称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");// 输出的token包含三部分 用点进行隔开System.out.println(token);Long userId = JwtHelper.getUserId(token);String username = JwtHelper.getUsername(token);System.out.println(userId);System.out.println(username);}
}

6.3 用户登录

  • LoginVo :登录时 前端返回的参数实体类
package com.atguigu.enity.vo.system;
import lombok.Data;
/*** 登录对象*/
@Data
public class LoginVo {/*** 用户名称*/private String username;/*** 密码*/private String password;
}
  • 使用MD5对密码进行加密
package com.atguigu.common.util;
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"));}
}
  • 操作文件:com.atguigu.serviceoa.controller.IndexController
        @Autowiredprivate SysUserService sysUserService;/*** 登录* @return*/@PostMapping("login")public Result login(@RequestBody LoginVo loginVo) {// 前端接收的数据:{"code":200,"data":{"token":"admin-token"}}// 1 获取用户输入名和密码String username = loginVo.getUsername();String password = loginVo.getPassword();// 2 根据用户名查询数据库LambdaQueryWrapper<SysUser> lqw = new LambdaQueryWrapper<>();lqw.eq(SysUser::getUsername,username);// 根据用户名查询只能查出一条数据(getOne)SysUser sysUserServiceOne = sysUserService.getOne(lqw);// 3 用户信息是否存在if(sysUserServiceOne == null){throw new BusinessException(201,"用户不存在");}// 4 判断密码是否正确// 密码进行加密 使用MD5// 数据库的密码String password1 = sysUserServiceOne.getPassword();// 输入的密码进行加密String password_input = MD5.encrypt(password);if(!password_input.equals(password1)){throw new BusinessException(201,"密码错误");}// 5 判断用户是否被禁用  1:可用 0:禁用if(sysUserServiceOne.getStatus().intValue() == 0){throw new BusinessException(201,"用户已被禁用");}// 6 使用jwt根据用户id 和 用户名生成token字符串String token = JwtHelper.createToken(sysUserServiceOne.getId(),sysUserServiceOne.getUsername());// 7 结果返回Map<String,String> map = new HashMap<>();map.put("token",token);return Result.ok(map);}

6.4 获取用户信息

  • 接口数据:后端返回给前端的数据
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");
// 返回用户可以操作的按钮
map.put("buttons", new ArrayList<>());
// 返回用户可以操作的菜单
map.put("routers", new ArrayList<>());

说明:主要是获取当前登录用户的菜单权限及按钮权限数据

  • 操作文件:com.atguigu.auth.controller.IndexController
    /*** 登录之后获取用户信息      1 菜单操作权限 2 按钮操作权限* @return*/@GetMapping("info")public Result info(HttpServletRequest request) {// 需要返回数据格式// {"code":200,"data":{"roles":["admin"],"introduction":"I am a super administrator",// "avatar":"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif","name":"Super Admin"}}// 1 从请求头获取用户信息(获取请求头token字符串)// 需要在前端设置(登录时把token放在请求头中)String token = request.getHeader("token");// 2 从token字符串获取用户ID和用户名称Long userId = JwtHelper.getUserId(token);String username = JwtHelper.getUsername(token);// 3 根据用户ID查询数据库 把对应的用户查询出来SysUser sysUser = sysUserService.getById(userId);// 4 根据用户ID获取用户可以操作的菜单列表(前端的路由不能写死啦)// 用户角色关系表 角色菜单关系表 菜单表// 查询数据库动态构造路由List<RouterVo> routerVoList = sysMenuService.findUserMenuListByUserId(userId);// 5 根据用户ID获取用户可以操作的按钮权限列表  perms(按钮权限) 是 permissions的简写// 返回的是 bnt.sysUser.listList<String> permsList = sysMenuService.findUserPermsByUserId(userId);// 6 返回的数据:t odo:表示需要完善的地方Map<String, Object> map = new HashMap<>();map.put("roles","[admin]");// 返回的是name 不是usernamemap.put("name",sysUser.getName());map.put("avatar","https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");// 6.1 用户可以操作的菜单map.put("routers",routerVoList);// 6.2 用户可以操作的按钮map.put("buttons",permsList);return Result.ok(map);}

6.4.1 获取用户菜单列表

  • 最终生成的路由格式
    在这里插入图片描述

  • 路由实体类 RouterVo

package com.atguigu.vo.system;
import lombok.Data;
import java.util.List;
/*** 路由配置信息*/
@Data
public class RouterVo
{/*** 路由名字*///private String name;/*** 路由地址*/private String path;/*** 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现*/private boolean hidden;/*** 组件地址*/private String component;/*** 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面*/private Boolean alwaysShow;/*** 其他元素*/private MetaVo meta;/*** 子路由*/private List<RouterVo> children;
}
  • 菜单表
    在这里插入图片描述

  • 实现 findUserMenuListByUserId 方法

  • 操作文件:com.atguigu.auth.service.SysMenuService

  • 根据用户ID获取用户可以操作的菜单列表(前端的路由不能写死啦)

    // 根据用户ID获取用户可以操作的菜单列表(前端的路由不能写死啦)List<RouterVo> findUserMenuListByUserId(Long userId);
  • 操作文件:com.atguigu.auth.service.impl.SysMenuServiceImpl
  • 一个约定:userId=1 为管理员
    // 根据用户ID获取用户可以操作的菜单列表(前端的路由不能写死啦)@Overridepublic List<RouterVo> findUserMenuListByUserId(Long userId) {// 集合最终都要构建  放在外面同意构建List<SysMenu> sysMenuList = new ArrayList<>();// 1 判断当前用户是否是管理员  userID = 1// 1.1 如果是管理员 查询所有菜单列表if(userId.longValue()==1){// 查询所有菜单列表LambdaQueryWrapper<SysMenu> lqw = new LambdaQueryWrapper<>();// Status必须为1lqw.eq(SysMenu::getStatus,1);// 按照SortValue升序排列lqw.orderByAsc(SysMenu::getSortValue);sysMenuList = baseMapper.selectList(lqw);}else {// 1.2 如果不是管理员 根据userId查询可以操作的菜单列表// 多表关联查询 :用户角色关系表 角色菜单关系表 菜单表// 编写sql语句进行实现sysMenuList = baseMapper.findMenuListByUserId(userId);}// 2 把查询出来的数据构建成框架需要的路由结构// 2.1 使用菜单操作工具类构建成树形结构List<SysMenu> sysMenuListTree = MenuHelper.buildTree(sysMenuList);// 2.1 构建成框架要求的路由结构  this指的就是SysMenuServiceList<RouterVo> routerList = this.buildRouter(sysMenuListTree);return routerList;}
  • 把树形结构转变为所需要的路由结构
  • 实现 buildRouter 方法
  • 操作文件:com.atguigu.auth.service.impl.SysMenuServiceImpl
  • menu表中有一个type字段 0:一级菜单, 1:二级菜单, 2 :按钮
    // 构建成框架要求的路由结构  this指的就是SysMenuServiceprivate List<RouterVo> buildRouter(List<SysMenu> sysMenuListTree) {// 创建List集合 存储最终数据List<RouterVo> routers = new ArrayList<>();// 遍历sysMenuListTree 树形结构但是并不完整 放入一些必要的数据// 主菜单 比如:系统管理 日志管理 审批设置sysMenuListTree.stream().forEach( sysMenu -> {RouterVo router = new RouterVo();router.setHidden(false);router.setAlwaysShow(false);// 使用了一个自定义的方法 getRouterPath(sysMenu)router.setPath(getRouterPath(sysMenu));router.setComponent(sysMenu.getComponent());// 一个新的metaVo实体类router.setMeta(new MetaVo(sysMenu.getName(), sysMenu.getIcon()));// 下一层数据部分List<SysMenu> children = sysMenu.getChildren();// 分配权限的type也是2 但是他有path component  隐藏路由// type=1说明是二级菜单,在系统管理下面,比如用户管理 角色管理 菜单管理if(sysMenu.getType().intValue()==1){// 首先加载出隐藏路由// type = 2 component不为空List<SysMenu> hiddenMenuList = children.stream().filter(item -> !StringUtils.isEmpty(item.getComponent())).collect(Collectors.toList());// 隐藏路由可能不止一个 还是要遍历// 隐藏路由放入一些必要的数据  setHidden(true)hiddenMenuList.stream().forEach( item ->{RouterVo router1 = new RouterVo();// Hidden是true 说明是隐藏路由router1.setHidden(true);router1.setAlwaysShow(false);router1.setPath(getRouterPath(sysMenu));router1.setComponent(sysMenu.getComponent());router1.setMeta(new MetaVo(sysMenu.getName(), sysMenu.getIcon()));routers.add(router1);});// else 说明sysMenu不是二级菜单  应该是一级菜单} else {// children不为空// 集合不为空  说明为一级菜单if(!CollectionUtils.isEmpty(children)){if(children.size()>0){// 总是会被显示router.setAlwaysShow(true);}// 进行递归操作router.setChildren(buildRouter(children));}}routers.add(router);} );return routers;}
  • 隐藏路由
    在这里插入图片描述

隐藏路由的特点:
type = 2
component不为空

  • 实现 getRouterPath(sysMenu) 方法
  • 操作文件:com.atguigu.auth.service.impl.SysMenuServiceImpl
    /*** 获取路由地址* @param menu 菜单信息* @return 路由地址*/public String getRouterPath(SysMenu menu) {// 主路由要加 "/"String routerPath = "/" + menu.getPath();// 是子路由 不用加“/”if(menu.getParentId().intValue() != 0) {routerPath = menu.getPath();}return routerPath;}

6.4.2 获取用户按钮权限

  • 对于按钮权限标识不为空才能被用户使用
    在这里插入图片描述

  • 实现 findUserPermsByUserId 方法

  • 操作文件:com.atguigu.auth.service.SysMenuService

  • 根据用户ID获取用户可以操作的按钮权限列表

    // 根据用户ID获取用户可以操作的按钮权限列表  perms 是 permissions的简写List<String> findUserPermsByUserId(Long userId);
  • 实现 findUserPermsByUserId 方法
  • 操作文件:com.atguigu.auth.service.SysMenuServiceImpl
  • findMenuListByUserId 多表关联查询 方法共用
    // 根据用户ID获取用户可以操作的按钮权限列表  perms 是 permissions的简写@Overridepublic List<String> findUserPermsByUserId(Long userId) {// 集合最终都要构建  放在外面统一构建List<SysMenu> sysMenuList = new ArrayList<>();// 1 判断当前用户是否是管理员  userID = 1if(userId.longValue() == 1){// 1.1 如果是管理员 查询所有按钮列表// 查询所有按钮列表LambdaQueryWrapper<SysMenu> lqw = new LambdaQueryWrapper<>();// Status必须为1lqw.eq(SysMenu::getStatus,1);sysMenuList = baseMapper.selectList(lqw);}else {// 1.2 如果不是管理员 根据userId查询可以操作的按钮列表// 多表关联查询 :用户角色关系表 角色菜单关系表 菜单表sysMenuList = baseMapper.findMenuListByUserId(userId);}// 2 从查询出来的数据里面,获取可以操作的按钮的list集合(perms)List<String> permsList = sysMenuList.stream()// 先过滤 type= 2 为按钮.filter(item -> item.getType() == 2)// 取出pems = bnt.sysUser.update 的数据.map(item -> item.getPerms()).collect(Collectors.toList());return permsList;}

6.4.3 多表关联查询

  • 操作文件:com.atguigu.auth.mapper.SysMenuMapper以及对应的xml文件
  • 实现 baseMapper.findMenuListByUserId(userId) 方法
    // 1.2 如果不是管理员 根据userId查询可以操作的菜单列表// 多表关联查询 :用户角色关系表 角色菜单关系表 菜单表List<SysMenu> findMenuListByUserId(@Param("userId") Long userId);
<?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.atguigu.serviceoa.mapper.SysMenuMapper"><!--    namespace根据自己需要创建的的mapper的路径和名称填写--><!--    自定义返回结果--><resultMap id="sysMenuMap" type="com.atguigu.enity.model.system.SysMenu" autoMapping="true"></resultMap><!-- 用于select查询公用抽取的列 -->
<!--    <sql id="columns">-->
<!--        m.id,m.parent_id,m.name,m.type,m.path,m.component,m.perms,m.icon,-->
<!--        m.sort_value,m.status,m.create_time,m.update_time,m.is_deleted-->
<!--    </sql>-->
<!--    # #{}接收传过来的参数值-->
<!--    # 状态为1 而且没有被删除的才能被查询出来--><select id="findMenuListByUserId" resultMap="sysMenuMap">selectdistinct *from sys_menu minner join sys_role_menu rm on rm.menu_id = m.idinner join sys_user_role ur on ur.role_id = rm.role_idwhereur.user_id = #{userId}and m.status = 1and rm.is_deleted = 0and ur.is_deleted = 0and m.is_deleted = 0</select>
</mapper>
  • 出现报错
    在这里插入图片描述
  • 报错原因
    在这里插入图片描述
  • 方式二
  • 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

7 Spring Security

7.1 Spring Security介绍

  • Web 应用的安全性包括**用户认证(Authentication)和用户授权(Authorization)**两个部分,这两点也是 SpringSecurity 重要核心功能
  • 用户认证指的是:系统认为用户是否能登录
  • 用户授权指的是:系统判断用户是否有权限去做某些事情

7.2 Spring Security实现权限

  • 要对Web资源进行保护,最好的办法莫过于Filter
  • 要想对方法调用进行保护,最好的办法莫过于AOP。(不改变原代码 增加功能)

7.2.1 Spring Security入门

  • 创建springSecurity包
  • 引入依赖
        <!-- 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>
  • 在config文件夹中创建配置类
package com.atguigu.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
  • 启动项目测试

7.2.2 用户认证

  • 需求分析
    在这里插入图片描述
    在这里插入图片描述
7.2.2.1 加密器PasswordEncoder
package com.atguigu.common.springSecurity.custom;
import com.atguigu.common.util.MD5;
import org.springframework.stereotype.Component;/*** 密码处理*/
@Component
public class CustomMd5PasswordEncoder {public String encode(CharSequence rawPassword) {return MD5.encrypt(rawPassword.toString());}public boolean matches(CharSequence rawPassword, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));}
}
7.2.2.2 添加CustomUser对象
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
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;}
}
7.2.2.3 业务对象UserDetailsService
  • com.atguigu.common.springSecurity.custom
public interface UserDetailsService {/*** 根据用户名获取用户对象(获取不到直接抛异常)*/UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
  • com.atguigu.serviceoa.service.impl
package com.atguigu.serviceoa.service.impl;
import com.atguigu.common.springSecurity.custom.CustomUser;
import com.atguigu.enity.model.system.SysUser;
import com.atguigu.serviceoa.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Collections;
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 根据用户名进行查询SysUser sysUser = sysUserService.getUserByUsername(username);if(null == sysUser) {throw new UsernameNotFoundException("用户名不存在!");}if(sysUser.getStatus().intValue() == 0) {throw new RuntimeException("账号已停用");}return new CustomUser(sysUser, Collections.emptyList());}
}
  • 在SysUserService中定义getUserByUsername方法
    // 根据用户名进行查询SysUser getUserByUsername(String username);
  • SysUserServiceImpl实现getUserByUsername方法
    @Overridepublic SysUser getUserByUsername(String username) {LambdaQueryWrapper<SysUser> lqw = new LambdaQueryWrapper<>();lqw.eq(SysUser::getUsername,username);SysUser sysUser = baseMapper.selectOne(lqw);return sysUser;}
7.2.2.4 自定义用户认证接口TokenLoginFilter
  • com.atguigu.common.springSecurity.filter
package com.atguigu.common.springSecurity.filter;
import com.atguigu.common.result.Result;
import com.atguigu.common.result.ResultCodeEnum;
import com.atguigu.common.springSecurity.custom.CustomUser;
import com.atguigu.common.util.JwtHelper;
import com.atguigu.common.util.ResponseUtil;
import com.atguigu.enity.vo.system.LoginVo;
import com.fasterxml.jackson.databind.ObjectMapper;
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;public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {// 1 构造方法public TokenLoginFilter(AuthenticationManager authenticationManager) {this.setAuthenticationManager(authenticationManager);this.setPostOnly(false);//指定登录接口及提交方式,可以指定任意路径(需要自己修改)this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));}// 2 登录认证// 获取输入的用户名和密码 调用方法进行认证@Overridepublic Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)throws AuthenticationException {try {// 需要自己修改(登录封装的实体类LoginVo)// 获取用户信息LoginVo loginVo = new ObjectMapper().readValue(req.getInputStream(), LoginVo.class);// 封装对象Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());// 调用spring-security中的方法完成认证return this.getAuthenticationManager().authenticate(authenticationToken);} catch (IOException e) {throw new RuntimeException(e);}}// 3 认证成功调用方法@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication auth) throws IOException, ServletException {// 获取当前用户对象CustomUser customUser = (CustomUser) auth.getPrincipal();// 生成tokenString token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());// 返回数据Map<String, Object> map = new HashMap<>();map.put("token", token);// 使用原生方式返回 使用了工具类// 不要导错包 使我们自己定义的工具类ResponseUtil.out(response, Result.ok(map));}// 4 认证失败调用方法@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(response, Result.fail(ResultCodeEnum.LOGIN_ERROR));}
}
  • 认证成功调用方法,使用原生方式返回 使用了工具类
  • com.atguigu.common.util
package com.atguigu.common.util;import com.atguigu.common.result.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;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();}}
}
7.2.2.5 认证解析token
  • com.atguigu.common.springSecurity.filter
package com.atguigu.common.springSecurity.filter;
import com.atguigu.common.result.Result;
import com.atguigu.common.result.ResultCodeEnum;
import com.atguigu.common.util.JwtHelper;
import com.atguigu.common.util.ResponseUtil;
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;public class TokenAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws ServletException, IOException {//如果是登录接口,直接放行 不需要做认证判定if("/admin/system/index/login".equals(request.getRequestURI())) {chain.doFilter(request, response);return;}// 得到当前用户  得到请求头中的tokenUsernamePasswordAuthenticationToken authentication = getAuthentication(request);if(null != authentication) {// 如果返回值不为空 就放在上下文对象中SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(request, response);} else {// 为空  返回失败ResponseUtil.out(response, Result.fail(ResultCodeEnum.PERMISSION));}}// 从请求头中获取对象private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {// token置于header里 获取tokenString token = request.getHeader("token");// token不为空if (!StringUtils.isEmpty(token)) {String useruame = JwtHelper.getUsername(token);// usename不为空 返回返回封装的对象if (!StringUtils.isEmpty(useruame)) {return new UsernamePasswordAuthenticationToken(useruame, null, Collections.emptyList());}}return null;}
}
7.2.2.6 配置用户认证
  • 修改WebSecurityConfig配置类
  • com.atguigu.config
package com.atguigu.config;
import com.atguigu.common.springSecurity.custom.CustomMd5PasswordEncoder;
import com.atguigu.common.springSecurity.custom.UserDetailsService;
import com.atguigu.common.springSecurity.filter.TokenAuthenticationFilter;
import com.atguigu.common.springSecurity.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.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.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate CustomMd5PasswordEncoder customMd5PasswordEncoder;@Bean@Overrideprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护http//关闭csrf跨站请求伪造.csrf().disable()// 开启跨域以便前端调用接口.cors().and().authorizeRequests()// 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的// 需要自己修改.antMatchers("/admin/system/index/login").permitAll()// 这里意思是其它所有接口需要认证才能访问.anyRequest().authenticated().and()//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。.addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).addFilter(new TokenLoginFilter(authenticationManager()));//禁用sessionhttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 指定UserDetailService和加密器auth.userDetailsService(userDetailsService).passwordEncoder(customMd5PasswordEncoder);}/*** 配置哪些请求不拦截* 排除swagger相关请求* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");}
}
  • 这里会出现一个报错
    在这里插入图片描述
  • 报错原因:这行代码注入的必须是spring-security自带的类
    在这里插入图片描述
  • 解决方法:往我们自己定义的UserDetailsService中加入继承
  • extends org.springframework.security.core.userdetails.UserDetailsService
@Service
public interface UserDetailsService extends org.springframework.security.core.userdetails.UserDetailsService{/*** 根据用户名获取用户对象(获取不到直接抛异常)*/UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;}

7.2.3 用户授权

  • 需求分析
    在这里插入图片描述

  • 使用默认的FilterSecurityInterceptor来进行权限校验

  • 在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication

  • 获取其中的权限信息,判断当前用户是否拥有访问当前资源所需的权限

7.2.3.1修改loadUserByUsername接口方法
  • com.atguigu.serviceoa.service.impl.UserDetailsServiceImpl
package com.atguigu.serviceoa.service.impl;
import com.atguigu.common.springSecurity.custom.CustomUser;
import com.atguigu.common.springSecurity.custom.UserDetailsService;
import com.atguigu.enity.model.system.SysUser;
import com.atguigu.serviceoa.service.SysMenuService;
import com.atguigu.serviceoa.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysMenuService sysMenuService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 根据用户名进行查询用户名SysUser sysUser = sysUserService.getUserByUsername(username);if(null == sysUser) {throw new UsernameNotFoundException("用户名不存在!");}if(sysUser.getStatus().intValue() == 0) {throw new RuntimeException("账号已停用");}// 根据用户ID得到用户可以操作的按钮权限数据List<String> userPermsList = sysMenuService.findUserPermsByUserId(sysUser.getId());// 不能直接封装 创建一个新的集合List<SimpleGrantedAuthority> authList = new ArrayList<>();// 把查询出来的数据放进这个SimpleGrantedAuthority的集合for (String perm : userPermsList) {authList.add(new SimpleGrantedAuthority(perm.trim()));}// 返回数据return new CustomUser(sysUser, authList);}
}
7.2.3.2修改successfulAuthentication接口方法
  • 登陆成功之后需要将用户名和权限数据放到redis
  • spring-security模块配置redis,添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 登录成功我们将权限数据保单到reids
  • 这里会有一个报错,后面解决
    // 注入redisprivate RedisTemplate redisTemplate;// 1 构造方法public TokenLoginFilter(AuthenticationManager authenticationManager,RedisTemplate redisTemplate) {this.setAuthenticationManager(authenticationManager);this.setPostOnly(false);//指定登录接口及提交方式,可以指定任意路径(需要自己修改)this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));// 传递redis  构造函数参数不要忘了引用this.redisTemplate = redisTemplate;}
  • 登录成功我们将权限数据保单到reids
    // 3 认证成功调用方法@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication auth) throws IOException, ServletException {// 获取当前用户对象CustomUser customUser = (CustomUser) auth.getPrincipal();// 生成tokenString token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());// 获取当前用户权限数据 放到redis里面     key:username   value:权限数据// 权限数据是集合数据  转化为JSON格式redisTemplate.opsForValue().set(customUser.getUsername(), JSON.toJSONString(customUser.getAuthorities()));// 返回数据Map<String, Object> map = new HashMap<>();map.put("token", token);// 使用原生方式返回 使用了工具类// 不要导错包 使我们自己定义的工具类ResponseUtil.out(response, Result.ok(map));}
7.2.3.3修改TokenAuthenticationFilter类
  • getAuthentication方法
  • 从请求头中获取用户名称和权限数据
// 从请求头中获取对象private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {// token置于header里 获取tokenString token = request.getHeader("token");// token不为空if (!StringUtils.isEmpty(token)) {String username = JwtHelper.getUsername(token);// usename不为空 返回返回封装的对象if (!StringUtils.isEmpty(username)) {// 通过用户名称从redis中获取权限数据String authorString = (String) redisTemplate.opsForValue().get(username);// 把从redis获取的权限数据的字符串转化为要求的集合类型if (!StringUtils.isEmpty(authorString)){// 权限数据 authorString不为空// JSON转化为Map格式List<Map> mapList = JSON.parseArray(authorString, Map.class);System.out.println(mapList);// 定义一个空集合接收所需类型的权限数据List<SimpleGrantedAuthority> authorities = new ArrayList<>();for (Map map : mapList) {authorities.add(new SimpleGrantedAuthority((String)map.get("authority")));}return new UsernamePasswordAuthenticationToken(username, null, authorities);} else {return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());}}}return null;}
7.2.3.4 修改配置类
  • com.atguigu.config.WebSecurityConfig
// 开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认
@EnableGlobalMethodSecurity(prePostEnabled = true)
  • 两个fillter添加redisTemplate参数
  • 注意查看完整代码
    在这里插入图片描述
7.2.3.5 在controller层加权限判断注解
  • 添加redis相关配置
spring:redis:host: 192.168.199.129port: 6379database: 0timeout: 1800000password: 123456jedis:pool:max-active: 20 #最大连接数max-wait: -1   #最大阻塞等待时间(负数表示没限制)max-idle: 5    #最大空闲min-idle: 0    #最小空闲
  • 在controller层加权限判断注解
7.2.3.5 异常处理
  • 分配了权限的能够成功返回接口数据
  • 没有分配权限的会抛出异常:org.springframework.security.access.AccessDeniedException: 不允许访问
  • 全局异常添加处理
  • AccessDeniedException需要引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><scope>provided</scope>
</dependency>
  • com.atguigu.common.exception.GlobalExceptionHandler
    /*** spring security异常* @param e* @return*/// org.springframework.security.access.AccessDeniedException;// 这个包才对@ExceptionHandler(AccessDeniedException.class)@ResponseBodypublic Result error(AccessDeniedException e) throws AccessDeniedException {return Result.fail(ResultCodeEnum.PERMISSION);}

8 Activiti入门

8.1 了解工作流

  • 工作流(Workflow):就是业务上一个完整的审批流程。例如员工的请假,出差,外出采购,合同审核等等

8.1.1 Activiti介绍

  • activiti是一个工作流引擎,可以将业务系统中复杂的业务流程抽取出来
  • 官方网站:https://www.activiti.org

8.1.2 建模语言BPM

  • 事件 Event

开始:表示一个流程的开始
中间:发生的开始和结束事件之间,影响处理的流程
结束:表示该过程结束

在这里插入图片描述

  • 活动 Activities
  • 一个活动可以是一个任务,还可以是一个当前流程的子处理流程
    在这里插入图片描述

8.2 Activiti7

9 审批管理模块

9.1 审批设置需求

在这里插入图片描述

9.2 审批类型管理

9.2.1 审批类型CRUD

  • 代码生成器生成(mapper、service、controller)
  • 在serviceoa下面新建包:process
    在这里插入图片描述
  • controller层代码直接复制粘贴
package com.atguigu.serviceoa.process.controller;
import com.atguigu.common.result.Result;
import com.atguigu.enity.model.process.ProcessType;
import com.atguigu.serviceoa.process.service.ProcessTypeService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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 atguigu* @since 2023-04-27*/
@Api(value = "审批类型管理", tags = "审批管理")
@RestController
@RequestMapping("/admin/process/processType")
public class ProcessTypeController {@Autowiredprivate ProcessTypeService processTypeService;@ApiOperation(value = "获取分页列表")@GetMapping("{page}/{limit}")public Result index(@PathVariable Long page,@PathVariable Long limit) {Page<ProcessType> pageParam = new Page<>(page,limit);IPage<ProcessType> pageModel = processTypeService.page(pageParam);return Result.ok(pageModel);}@ApiOperation(value = "根据获取审批类型")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {ProcessType processType = processTypeService.getById(id);return Result.ok(processType);}@ApiOperation(value = "新增")@PostMapping("save")public Result save(@RequestBody ProcessType processType) {processTypeService.save(processType);return Result.ok();}@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();}@ApiOperation(value = "获取全部审批分类")@GetMapping("findAll")public Result findAll() {return Result.ok(processTypeService.list());}}
  • 启动项目之后可能会出现一个bug:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.atguigu.serviceoa.process.mapper.ProcessTypeMapper’
解决方法:记得在每一个service、mapper上面添加@Service、@Mapper注解

9.2.2 前端页面(复制粘贴即可)

9.3 审批模板

9.3.1 审批模板CRUD

  • 审批模板实体类
  • 这里面加了一个新的属性:类型名称
// 类型名称:表中没有,但是实体类中有  审批类型名称@TableField(exist = false)private String processTypeName;
9.3.1.1 分页查询功能
  • 由于查询出来的的数据要包含审批类型名称,需要重写分页查询方法
  • com.atguigu.serviceoa.process.controller.ProcessTemplateController
    @Autowiredprivate ProcessTemplateService processTemplateService;@ApiOperation(value = "获取分页审批模板列表")@GetMapping("{page}/{limit}")public Result index(@ApiParam(name = "page", value = "当前页码", required = true)@PathVariable Long page,@ApiParam(name = "limit", value = "每页记录数", required = true)@PathVariable Long limit) {Page<ProcessTemplate> pageParam = new Page<>(page, limit);// 在审批模板中 有一个审批类型// 在 process_template 表中有一个字段 process_type_id// 根据审批类型ID查询审批类型名称IPage<ProcessTemplate> pageModel = processTemplateService.selectPage2(pageParam);return Result.ok(pageModel);}
  • com.atguigu.serviceoa.process.service.ProcessTemplateService
package com.atguigu.serviceoa.process.service;import com.atguigu.enity.model.process.ProcessTemplate;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.stereotype.Service;/*** <p>* 审批模板 服务类* </p>** @author atguigu* @since 2023-04-27*/
@Service
public interface ProcessTemplateService extends IService<ProcessTemplate> {/*** 获取分页审批模板列表* @param pageParam* @return* 在审批模板中 有一个审批类型* 在 process_template 表中有一个字段 process_type_id* 根据审批类型ID查询审批类型名称*/IPage<ProcessTemplate> selectPage2(Page<ProcessTemplate> pageParam);
}
  • com.atguigu.serviceoa.process.service.impl.ProcessTemplateServiceImpl
package com.atguigu.serviceoa.process.service.impl;import com.atguigu.enity.model.process.ProcessTemplate;
import com.atguigu.enity.model.process.ProcessType;
import com.atguigu.serviceoa.process.mapper.ProcessTemplateMapper;
import com.atguigu.serviceoa.process.service.ProcessTemplateService;
import com.atguigu.serviceoa.process.service.ProcessTypeService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.stream.Collectors;/*** <p>* 审批模板 服务实现类* </p>** @author atguigu* @since 2023-04-27*/
@Service
public class ProcessTemplateServiceImpl extends ServiceImpl<ProcessTemplateMapper, ProcessTemplate> implements ProcessTemplateService {@Autowiredprivate ProcessTypeService processTypeService;/*** 获取分页审批模板列表* @param pageParam* @return* 在审批模板中 有一个审批类型* 在 process_template 表中有一个字段 process_type_id* 根据审批类型ID查询审批类型名称*/@Overridepublic IPage<ProcessTemplate> selectPage2(Page<ProcessTemplate> pageParam) {// 1 调用basemapper实现分页查询,查出来的结果不包含审批类型名称Page<ProcessTemplate> processTemplatePage = baseMapper.selectPage(pageParam, null);// 2 取得processTemplatePage里面的分页数据recordsList<ProcessTemplate> records = processTemplatePage.getRecords();// 3 遍历records,获取里面的类型ID  type_idList<Long> typeIdList = records.stream().map(item -> item.getProcessTypeId()).collect(Collectors.toList());// 4 根据typeIdList 查询对应的审批类型List<ProcessType> processTypes = processTypeService.listByIds(typeIdList);// 5 遍历rocords 把processType的类型名称放进去for (int i = 0; i < records.size(); i++) {records.get(i).setProcessTypeName(processTypes.get(i).getName());}// 6 将records数据放入pageprocessTemplatePage.setRecords(records);return processTemplatePage;}
}
9.3.1.2 添加审批模板
  • 流程分析
    在这里插入图片描述

基本设置:一些基本信息
表单设置:动态表单
流程设置:本地设计流程定义,上传流程定义文件及流程定义图片(压缩上传)

  • 涉及未实现接口:

获取全部审批分类
上传流程定义压缩文件

  • 获取全部审批分类接口
  • com.atguigu.serviceoa.process.controller.ProcessTypeController
    @ApiOperation(value = "获取全部审批分类")@GetMapping("findAll")public Result findAll() {return Result.ok(processTypeService.list());}
  • 上传流程定义接口
  • 文件最终是要上传到resource的process中,编译之后也就是classes的process中
  • com.atguigu.serviceoa.process.controller.ProcessTemplateController
    @ApiOperation(value = "上传流程定义文件")@PostMapping("/uploadProcessDefinition")public Result uploadProcessDefinition(MultipartFile file) throws FileNotFoundException {// 文件最终是要上传到target/classes/process这个文件夹汇中// 获取class目录位置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 imageFile = new File(path + "/processes/" + fileName);// 保存文件流到本地try {file.transferTo(imageFile);} catch (IOException e) {e.printStackTrace();return Result.fail(201,"上传失败");}Map<String, Object> map = new HashMap<>();//根据上传地址后续部署流程定义,文件名称为流程定义的默认keymap.put("processDefinitionPath", "processes/" + fileName);map.put("processDefinitionKey", fileName.substring(0, fileName.lastIndexOf(".")));return Result.ok(map);}
  • 集成form-create

1、添加依赖:在package.json文件添加依赖,注意版本号,更高的版本号可能与本项目不兼容
“@form-create/element-ui”: “^2.5.17”,
“@form-create/designer”: “^1.0.8”,
然后 npm install
2、在 main.js 中写入以下内容:
import formCreate from ‘@form-create/element-ui’
import FcDesigner from ‘@form-create/designer’
Vue.use(formCreate)
Vue.use(FcDesigner)
3、集成表单设计器
创建views/processSet/processTemplate/templateSet.vue

补充笔记:classpath

在这里插入图片描述

  • classpath指向的就是打war包之后的classes的位置
  • classes文件夹下就是我们原项目的java文件和resources文件夹里面的内容
9.3.1.3 查看审批模板(前端复制粘贴)
9.3.1.4 发布
  • 发布后审批模板就不可以修改了,然后部署流程定义
  • com.atguigu.serviceoa.process.controller.ProcessTemplateController
@ApiOperation(value = "发布")
@GetMapping("/publish/{id}")
public Result publish(@PathVariable Long id) {processTemplateService.publish(id);return Result.ok();
}
  • com.atguigu.serviceoa.process.service.impl.ProcessTemplateServiceImpl
@Transactional
@Override
public void publish(Long id) {ProcessTemplate processTemplate = this.getById(id);processTemplate.setStatus(1);processTemplateMapper.updateById(processTemplate);//TODO 部署流程定义,后续完善
}

9.4 管理端-审批管理

9.4.1 需求分析

  • 页面效果

在这里插入图片描述

  • 数据库表设计(process)
    在这里插入图片描述

9.4.2 审批管理CRUD

  • 分页查询
  • com.atguigu.serviceoa.process.controller.ProcessController
package com.atguigu.serviceoa.process.controller;
import com.atguigu.common.result.Result;
import com.atguigu.enity.vo.process.ProcessQueryVo;
import com.atguigu.enity.vo.process.ProcessVo;
import com.atguigu.serviceoa.process.service.ProcessService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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;
/*** 审批类型 前端控制器*/
@Api(tags = "审批流管理")
@RestController
@RequestMapping(value = "/admin/process")
public class ProcessController {@Autowiredprivate ProcessService processService;@ApiOperation(value = "获取审批管理分页列表")@GetMapping("{page}/{limit}")public Result index(@PathVariable Long page, @PathVariable Long limit, ProcessQueryVo processQueryVo) {Page<ProcessVo> pageParam = new Page<>(page, limit);IPage<ProcessVo> pageModel = processService.selectPage(pageParam, processQueryVo);return Result.ok(pageModel);}
}
  • com.atguigu.serviceoa.process.service.ProcessService
package com.atguigu.serviceoa.process.service;
import com.atguigu.enity.vo.process.ProcessQueryVo;
import com.atguigu.enity.vo.process.ProcessVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
/*** 审批类型 服务类*/
public interface ProcessService extends IService<Process> {// 获取审批管理分页列表IPage<ProcessVo> selectPage(Page<ProcessVo> pageParam, ProcessQueryVo processQueryVo);
}
  • com.atguigu.serviceoa.process.service.impl.ProcessServiceImpl
package com.atguigu.serviceoa.process.service.impl;
import com.atguigu.enity.vo.process.ProcessQueryVo;
import com.atguigu.enity.vo.process.ProcessVo;
import com.atguigu.serviceoa.process.mapper.ProcessMapper;
import com.atguigu.serviceoa.process.service.ProcessService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/*** 审批类型 服务实现类*/
@Service
public class ProcessServiceImpl extends ServiceImpl<ProcessMapper, Process> implements ProcessService {// 获取审批管理分页列表@Overridepublic IPage<ProcessVo> selectPage(Page<ProcessVo> pageParam, ProcessQueryVo processQueryVo) {IPage<ProcessVo> page = baseMapper.selectPage(pageParam, processQueryVo);return page;}
}
  • com.atguigu.serviceoa.process.mapper.ProcessMapper
package com.atguigu.serviceoa.process.mapper;
import com.atguigu.enity.vo.process.ProcessQueryVo;
import com.atguigu.enity.vo.process.ProcessVo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;/*** 审批类型 Mapper 接口*/
public interface ProcessMapper extends BaseMapper<Process> {// 获取审批管理分页列表// @Param("vo"):在xml文件中processQueryVo参数就叫做voIPage<ProcessVo> selectPage(Page<ProcessVo> page, @Param("vo") ProcessQueryVo processQueryVo);
}
  • com/atguigu/serviceoa/process/mapper/xml/ProcessMapper.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.atguigu.serviceoa.process.mapper.ProcessMapper"><!--    // 获取审批管理分页列表--><select id="selectPage" resultType="com.atguigu.enity.vo.process.ProcessVo">selecta.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.namefrom process aleft join process_template b on b.id = a.process_template_idleft join process_type c on c.id = a.process_type_idleft join sys_user d on d.id = a.user_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},'%') or d.phone like CONCAT('%',#{vo.keyword},'%') or d.name 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 &gt;= #{vo.createTimeBegin}</if><if test="vo.createTimeEnd != null and vo.createTimeEnd != ''">and a.create_time &lt;= #{vo.createTimeEnd}</if></where>order by id desc</select>
</mapper>
  • 前端复制粘贴即可
  • 运行过程会出现一个bug
    在这里插入图片描述
  • 配置文件中配置了xml文件的路径
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志# xml文件的路径# mapper-locations: classpath:com/atguigu/serviceoa/auth/mapper/xml/*.xmlmapper-locations: classpath:com/atguigu/serviceoa/*/mapper/xml/*.xml

9.4.3 部署流程定义

  • com.atguigu.serviceoa.process.service.ProcessService
    // 部署流程定义// deployPath:zip文件的路径void deployByZip(String deployPath);

这篇关于云尚办公笔记(代码注释非常详细)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/418566

相关文章

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如

Java操作PDF文件实现签订电子合同详细教程

《Java操作PDF文件实现签订电子合同详细教程》:本文主要介绍如何在PDF中加入电子签章与电子签名的过程,包括编写Word文件、生成PDF、为PDF格式做表单、为表单赋值、生成文档以及上传到OB... 目录前言:先看效果:1.编写word文件1.2然后生成PDF格式进行保存1.3我这里是将文件保存到本地后

windows系统下shutdown重启关机命令超详细教程

《windows系统下shutdown重启关机命令超详细教程》shutdown命令是一个强大的工具,允许你通过命令行快速完成关机、重启或注销操作,本文将为你详细解析shutdown命令的使用方法,并提... 目录一、shutdown 命令简介二、shutdown 命令的基本用法三、远程关机与重启四、实际应用

python多进程实现数据共享的示例代码

《python多进程实现数据共享的示例代码》本文介绍了Python中多进程实现数据共享的方法,包括使用multiprocessing模块和manager模块这两种方法,具有一定的参考价值,感兴趣的可以... 目录背景进程、进程创建进程间通信 进程间共享数据共享list实践背景 安卓ui自动化框架,使用的是

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行

springboot整合gateway的详细过程

《springboot整合gateway的详细过程》本文介绍了如何配置和使用SpringCloudGateway构建一个API网关,通过实例代码介绍了springboot整合gateway的过程,需要... 目录1. 添加依赖2. 配置网关路由3. 启用Eureka客户端(可选)4. 创建主应用类5. 自定

SpringBoot生成和操作PDF的代码详解

《SpringBoot生成和操作PDF的代码详解》本文主要介绍了在SpringBoot项目下,通过代码和操作步骤,详细的介绍了如何操作PDF,希望可以帮助到准备通过JAVA操作PDF的你,项目框架用的... 目录本文简介PDF文件简介代码实现PDF操作基于PDF模板生成,并下载完全基于代码生成,并保存合并P