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 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@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);
}
}
- 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.StringUtils
LambdaQueryWrapper<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和message
public 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和message
public 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 data
public 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 自己传入data
public 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和message
public 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 message
public 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参数end
Docket 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", "[email protected]"))
.build();
}
}
- Controller层添加注解
@Api(tags = "角色管理")
@RestController
@RequestMapping("/admin/system/sysRole")
public class SysRoleController {
@Autowired
private 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数据格式
@ResponseBody
public Result error(Exception e){
// 输出异常信息
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
e.printStackTrace();
return Result.fail(201,e.getMessage());
}
// 特定异常处理
@ExceptionHandler(ArithmeticException.class)
// 返回json数据格式
@ResponseBody
public Result error(ArithmeticException e){
// 输出异常信息
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
e.printStackTrace();
return Result.fail(201,e.getMessage());
}
// 业务异常处理
@ExceptionHandler(BusinessException.class)
// 返回json数据格式
@ResponseBody
public Result error(BusinessException e){
// 输出异常信息
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
e.printStackTrace();
return Result.fail(e.getCode(),e.getMsg());
}
// 系统异常处理
@ExceptionHandler(SystemException.class)
// 返回json数据格式
@ResponseBody
public 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 {
@Autowired
private 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.StringUtils
LambdaQueryWrapper<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:后端将数组变为List
public 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
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:8080
target: '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, 且请求头名称为 token
const token = store.getters.token
if (token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
// config.headers['X-Token'] = getToken()
config.headers['token'] = token
}
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();
// 前面是项目部工程的绝对路径 后面是到java
gc.setOutputDir("D:\\Code_Java\\guigu-oa\\guigu-oa-java"+"/src/main/java");
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.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 {
@Autowired
private 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 {
@Autowired
private SysUserRoleService sysUserRoleService;
// 根据用户ID获取角色数据
@Override
public Map<String, Object> findRoleByUserId(Long userId) {
// 1 查询所有的角色 返回List集合
List<SysRole> allSysRoles = baseMapper.selectList(null);
// 2 根据用户ID获取所属角色ID
// 从查询出的所有角色中获取角色ID
LambdaQueryWrapper<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;
}
// 为一个用户分配多个角色
@Override
public 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
// 更新用户状态
@Override
public 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 {
@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.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 {
@Override
public 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 = 0
if (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
// 删除菜单
@Override
public 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)
@Override
public 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要变为true
allSysMenus.stream().forEach( item ->{
if (roleIds.contains(item.getId())){
item.setSelect(true);
} else {
item.setSelect(false);
}
});
// 4 返回规定格式的菜单树形结构
List<SysMenu> sysMenuTrees = MenuHelper.buildTree(allSysMenus);
return sysMenuTrees;
}
// 给角色分配菜单
@Override
public 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似乎不能使用continue
for (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")
// 当前的角色ID
private 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字符串中获取用户ID
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;
}
}
// // 从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
@Autowired
private 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.list
List<String> permsList = sysMenuService.findUserPermsByUserId(userId);
// 6 返回的数据:t odo:表示需要完善的地方
Map<String, Object> map = new HashMap<>();
map.put("roles","[admin]");
// 返回的是name 不是username
map.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获取用户可以操作的菜单列表(前端的路由不能写死啦)
@Override
public 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必须为1
lqw.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指的就是SysMenuService
List<RouterVo> routerList = this.buildRouter(sysMenuListTree);
return routerList;
}
- 把树形结构转变为所需要的路由结构
- 实现 buildRouter 方法
- 操作文件:com.atguigu.auth.service.impl.SysMenuServiceImpl
- menu表中有一个type字段 0:一级菜单, 1:二级菜单, 2 :按钮
// 构建成框架要求的路由结构 this指的就是SysMenuService
private 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的简写
@Override
public List<String> findUserPermsByUserId(Long userId) {
// 集合最终都要构建 放在外面统一构建
List<SysMenu> sysMenuList = new ArrayList<>();
// 1 判断当前用户是否是管理员 userID = 1
if(userId.longValue() == 1){
// 1.1 如果是管理员 查询所有按钮列表
// 查询所有按钮列表
LambdaQueryWrapper<SysMenu> lqw = new LambdaQueryWrapper<>();
// Status必须为1
lqw.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">
select
distinct *
from sys_menu m
inner join sys_role_menu rm on rm.menu_id = m.id
inner join sys_user_role ur on ur.role_id = rm.role_id
where
ur.user_id = #{userId}
and m.status = 1
and rm.is_deleted = 0
and ur.is_deleted = 0
and 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 {
@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中定义getUserByUsername方法
// 根据用户名进行查询
SysUser getUserByUsername(String username);
- SysUserServiceImpl实现getUserByUsername方法
@Override
public 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 登录认证
// 获取输入的用户名和密码 调用方法进行认证
@Override
public 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 认证成功调用方法
@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));
}
// 4 认证失败调用方法
@Override
protected 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 {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
//如果是登录接口,直接放行 不需要做认证判定
if("/admin/system/index/login".equals(request.getRequestURI())) {
chain.doFilter(request, response);
return;
}
// 得到当前用户 得到请求头中的token
UsernamePasswordAuthenticationToken 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里 获取token
String 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 {
@Autowired
private UserDetailsService 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");
}
}
- 这里会出现一个报错
- 报错原因:这行代码注入的必须是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 {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysMenuService sysMenuService;
@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("账号已停用");
}
// 根据用户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
- 这里会有一个报错,后面解决
// 注入redis
private 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 认证成功调用方法
@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());
// 获取当前用户权限数据 放到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里 获取token
String 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.129
port: 6379
database: 0
timeout: 1800000
password: 123456
jedis:
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)
@ResponseBody
public 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 {
@Autowired
private 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
@Autowired
private 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 {
@Autowired
private ProcessTypeService processTypeService;
/**
* 获取分页审批模板列表
* @param pageParam
* @return
* 在审批模板中 有一个审批类型
* 在 process_template 表中有一个字段 process_type_id
* 根据审批类型ID查询审批类型名称
*/
@Override
public IPage<ProcessTemplate> selectPage2(Page<ProcessTemplate> pageParam) {
// 1 调用basemapper实现分页查询,查出来的结果不包含审批类型名称
Page<ProcessTemplate> processTemplatePage = baseMapper.selectPage(pageParam, null);
// 2 取得processTemplatePage里面的分页数据records
List<ProcessTemplate> records = processTemplatePage.getRecords();
// 3 遍历records,获取里面的类型ID type_id
List<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数据放入page
processTemplatePage.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<>();
//根据上传地址后续部署流程定义,文件名称为流程定义的默认key
map.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 {
@Autowired
private 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 {
// 获取审批管理分页列表
@Override
public 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参数就叫做vo
IPage<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">
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 process a
left join process_template b on b.id = a.process_template_id
left join process_type c on c.id = a.process_type_id
left 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 >= #{vo.createTimeBegin}
</if>
<if test="vo.createTimeEnd != null and vo.createTimeEnd != ''">
and a.create_time <= #{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/*.xml
mapper-locations: classpath:com/atguigu/serviceoa/*/mapper/xml/*.xml
9.4.3 部署流程定义
- com.atguigu.serviceoa.process.service.ProcessService
// 部署流程定义
// deployPath:zip文件的路径
void deployByZip(String deployPath);