项目整体结构图:
pom.xml添加相关依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zzg</groupId>
<artifactId>zzg-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>zzg-activity</artifactId>
<!-- 版本集中管理 -->
<properties>
<javax.servlet-api.version>3.1.0</javax.servlet-api.version>
<mybatis-spring-boot-starter.version>1.3.2</mybatis-spring-boot-starter.version>
<mysql-connector-java.version>8.0.11</mysql-connector-java.version>
<druid-spring-boot-starter.version>1.1.9</druid-spring-boot-starter.version>
<activiti-spring-boot-starter-basic.version>6.0.0</activiti-spring-boot-starter-basic.version>
</properties>
<dependencies>
<!--springboot web 基础模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
</dependency>
<!--mybatis orm 模块 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
<scope>runtime</scope>
</dependency>
<!--数据库连接池druid 模块 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-spring-boot-starter.version}</version>
</dependency>
<!-- activiti 工作流程引擎 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>${activiti-spring-boot-starter-basic.version}</version>
</dependency>
<!-- Thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>
application.properties配置文件
# 指定服务端口
server.port=9092
# 指定服务 名称
# server.context-path=/ureport
#mybatis xml 文件配置
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
# MyBatis mysql8 配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/activiti?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true&nullCatalogMeansCurrent=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Druid 配置
# 初始化时建立物理连接的个数
spring.datasource.druid.initial-size=5
# 最大连接池数量
spring.datasource.druid.max-active=30
# 最小连接池数量
spring.datasource.druid.min-idle=5
# 获取连接时最大等待时间,单位毫秒
spring.datasource.druid.max-wait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 连接保持空闲而不被驱逐的最小时间
spring.datasource.druid.min-evictable-idle-time-millis=300000
# 用来检测连接是否有效的sql,要求是一个查询语句
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle=true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-borrow=false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-return=false
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
spring.datasource.druid.pool-prepared-statements=true
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=50
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计
#spring.datasource.druid.filters=stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 合并多个DruidDataSource的监控数据
spring.datasource.druid.use-global-data-source-stat=true
# 配置sql 注入方式
spring.datasource.druid.filters=stat,log4j
#日志文件配置
logging.config=classpath:logback.xml
#activiti 工作流程引擎
#自动检查、部署流程定义文件
spring.activiti.check-process-definitions=true
#自动更新数据库结构
spring.activiti.database-schema-update=false
#流程定义文件存放目录
spring.activiti.process-definition-location-prefix=classpath*:processes/
#thymeleaf 模板引擎配置
# 开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
# 访问template下的html文件需要配置模板,映射
spring.thymeleaf.prefix.classpath=/templates
spring.thymeleaf.suffix=.html
程序入口Applciation:
import org.activiti.spring.boot.SecurityAutoConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication(exclude=SecurityAutoConfiguration.class)
@EnableTransactionManagement
@MapperScan("com.digipower.ucas.mapper")
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// 注意这里要指向原先用main方法执行的Application启动类
return builder.sources(Application.class);
}
}
Drudid 配置对象:
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
/**
* Druid 配置对象
* @author zzg
*
*/
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean druidServletRegistrationBean() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.setServlet(new StatViewServlet());
servletRegistrationBean.addUrlMappings("/druid/*");
servletRegistrationBean.addInitParameter("allow", "");
servletRegistrationBean.addInitParameter("deny", "");
servletRegistrationBean.addInitParameter("loginUsername", "admin");
servletRegistrationBean.addInitParameter("loginPassword", "admin");
return servletRegistrationBean;
}
/**
* 注册DruidFilter拦截
*
* @return
*/
@Bean
public FilterRegistrationBean duridFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
Map<String, String> initParams = new HashMap<String, String>();
//设置忽略请求
initParams.put("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
filterRegistrationBean.setInitParameters(initParams);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
thymeleaf 示列Controller
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/api/thymeleaf")
public class ThymeleafController {
@RequestMapping("/index")
public String index(Map<String, String> map) {
map.put("username", "深圳世纪位图");
return "index";
}
}
thymeleaf 模板文件(/template/index.html)
<!DOCTYPE html>
<!-- 引入thymeleaf 的名称空间 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Thymeleaf 模板引擎</title>
</head>
<body>
<h2>Hello <span th:text="${username}">这里用于显示用户名</span></h2>
</body>
</html>
logback.xml 日志文件配置
<?xml version="1.0" encoding="UTF-8"?>
<!-- 从高到地低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则 根据当前ROOT 级别,日志输出时,级别高于root默认的级别时 会输出 -->
<!-- 以下 每个配置的 filter 是过滤掉输出文件里面,会出现高级别文件,依然出现低级别的日志信息,通过filter 过滤只记录本级别的日志-->
<!-- 属性描述 scan:性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,
默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 定义日志文件 输入位置 -->
<property name="log_dir" value="/logs/activity" />
<!-- 日志最大的历史 30天 -->
<property name="maxHistory" value="30"/>
<!-- ConsoleAppender 控制台输出日志 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 对日志进行格式化 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
</encoder>
</appender>
<!-- ERROR级别日志 -->
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 RollingFileAppender-->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录WARN级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志输出位置 可相对、和绝对路径 -->
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/neo4j-error-log.log</fileNamePattern>
<!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件假设设置每个月滚动,且<maxHistory>是6,
则只保存最近6个月的文件,删除之前的旧文件。注意,删除旧文件是,那些为了归档而创建的目录也会被删除-->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<!-- 按照固定窗口模式生成日志文件,当文件大于20MB时,生成新的日志文件。窗口大小是1到3,当保存了3个归档文件后,将覆盖最早的日志。
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy> -->
<!-- 查看当前活动文件的大小,如果超过指定大小会告知RollingFileAppender 触发当前活动文件滚动
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy> -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- WARN级别日志 appender -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录WARN级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/neo4j-warn-log.log
</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- INFO级别日志 appender -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录INFO级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/neo4j-info-log.log
</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- DEBUG级别日志 appender -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/neo4j-debug-log.log
</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<logger name="java.sql.PreparedStatement" value="DEBUG" />
<logger name="java.sql.Connection" value="DEBUG" />
<logger name="java.sql.Statement" value="DEBUG" />
<logger name="com.ibatis" value="DEBUG" />
<logger name="com.ibatis.common.jdbc.SimpleDataSource" value="DEBUG" />
<logger name="com.ibatis.common.jdbc.ScriptRunner" level="DEBUG"/>
<logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" value="DEBUG" />
<logger name="org.springframework.web" level="DEBUG"/>
<logger name="com.zzg" level="DEBUG"/>
<!-- root级别 DEBUG -->
<root level="ERROR">
<!-- 控制台输出 -->
<appender-ref ref="STDOUT" />
<!-- 文件输出 -->
<appender-ref ref="ERROR" />
<appender-ref ref="INFO" />
<appender-ref ref="WARN" />
<appender-ref ref="DEBUG" />
</root>
</configuration>
Activiti 流程引擎示列类:
package com.digipower.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.activiti.engine.HistoryService;
import org.activiti.engine.IdentityService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/api/leave")
public class LeaveController {
// 日志记录
public static final Logger log = LoggerFactory.getLogger(LeaveController.class);
@Autowired(required = false)
private RuntimeService runtimeService;
@Autowired(required = false)
private TaskService taskService;
@Autowired(required = false)
private IdentityService identityService;
@Autowired(required = false)
private RepositoryService repositoryService;
@Autowired(required = false)
private ProcessEngine processEngine;
@Autowired(required = false)
private HistoryService historyService;
/**
* 部署资源
*
* @param request
* @param response
*/
@RequestMapping("/deploy")
public void deploy(HttpServletRequest request, HttpServletResponse response) {
Deployment deployment = processEngine.getRepositoryService()// 获取流程定义和部署对象相关的Service
.createDeployment() // 创建一个部署的构建器
.name("请假申请审核流程")// 设置流程的名称
.addClasspathResource("processes/LeaveProcess.bpmn")// 加载资源文件,一次只能加载一个文件
.addClasspathResource("processes/LeaveProcess.png").deploy();// 完成部署
log.info("【部署的id】:{}", deployment.getId());
log.info("【部署的名称】:{}", deployment.getName());
log.info("【部署时间】:{}" + deployment.getDeploymentTime());
}
/**
* 查询部署的流程定义
*
* @param request
* @param response
*/
@RequestMapping("/queryDeployProcess")
public void queryDeployProcess(HttpServletRequest request, HttpServletResponse response) {
RepositoryService repositoryService = processEngine.getRepositoryService();
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey("myProcess")
.orderByProcessDefinitionVersion().desc().list();
for (ProcessDefinition p : list) {
log.info("------------------------");
log.info("【流程部署id】:{}", p.getDeploymentId());
log.info("【流程定义id】:{}", p.getId());
log.info("【流程定义名称】:{}", p.getName());
log.info("【流程定义key】:{}", p.getKey());
log.info("【流程定义版本】:{}", p.getVersion());
}
}
/**
* 删除部署的流程定义
*/
@RequestMapping("/deleteDeployProcess")
public void deleteDeployProcess(HttpServletRequest request, HttpServletResponse response) {
RepositoryService repositoryService = processEngine.getRepositoryService();
// 下面的参数是 DeploymentId
repositoryService.deleteDeployment("***"); // 根据部署的id来删除
}
/**
* @desc: 发起流程
* @auth: cao_wencao
* @date: 2020/1/9 15:54
*/
@RequestMapping("/initProcess")
public void initProcess(HttpServletRequest request, HttpServletResponse response) {
log.debug("【流程开启开始执行】。。。。。。。。。");
Map<String, Object> map = new HashMap<String, Object>();
//流程图里写的${user} ,这里传进去user
map.put("user", "周");
//流程启动
//指定流程的发起者 不指定发起者的字段就为空,注意跟审核人分开
identityService.setAuthenticatedUserId("zhangsan");
runtimeService.startProcessInstanceByKey("myProcess", map);
ExecutionEntity pi = (ExecutionEntity) runtimeService.startProcessInstanceByKey("myProcess", map);
log.info("启动流程成功!");
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
log.info("任务ID: {}", task.getId());
log.info("任务的办理人: {}", task.getAssignee());
log.info("任务名称: {}", task.getName());
log.info("任务的创建时间: {}", task.getCreateTime());
log.info("流程实例ID: {}", task.getProcessInstanceId());
}
/**
* @desc: 根据taskid审核任务
* @auth: cao_wencao
* @date: 2020/1/9 16:07
*/
@RequestMapping("/audit")
public void audit(HttpServletRequest request, HttpServletResponse response) {
String taskId = request.getParameter("taskId");
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("projectUser", "黄");
// 开启后,环节会走到发起请假请求,要完成这个环节,才能到下一个审核环节
taskService.complete(taskId, map2);
}
// 通过用户名查询该用户的所有任务
@RequestMapping("/checkByUser")
@ResponseBody
public String checkByUser(HttpServletRequest request, HttpServletResponse response) {
String user = request.getParameter("user");
List<Task> tasks = taskService//与任务相关的Service
.createTaskQuery()//创建一个任务查询对象
.taskAssignee(user)
.list();
if (tasks != null && tasks.size() > 0) {
for (Task task : tasks) {
System.out.println("任务ID:" + task.getId());
System.out.println("任务的办理人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
System.out.println("任务的创建时间:" + task.getCreateTime());
System.out.println("流程实例ID:" + task.getProcessInstanceId());
}
}
return "success";
}
}
Activiti 流程引擎资源文件(processes/LeaveProcess.bpmn 和processes/LeaveProcesses.png)
具体流程文件创建,请参考:com.zzg.mapping 代码文件夹和mapping/ucas 资源文件夹为MyBatis3 ORM 框架使用