1.效果展示
登录页
请假首页
请假流程和流程相关信息
办理业务
2.使用的技术和相关配置
2.1技术
使用的springboot2.7.19+mybatisplus+thymeleaf+spring security+mybatis+mysql+activti7
因为springsecurity 和 activti7 依赖mybatis,所以这里使用两个数据库连接工具。
2.2相关配置
2.2.1 数据库的相关配置:
在yml文件中配置:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/zhoujiajun?useSSL=false&characterEncoding=utf8
username: root
password: 123456
2.2.2springboot ssecurity:
创建一个配置类:
package com.it.springbootxiangmu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class securityconfig {
@Autowired
private DataSource dataSource;
//配置开启记住我功能
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository=new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
//配置springSecurity不拦截静态资源
@Bean
public WebSecurityCustomizer securityCustomizer() {
return (web) -> web.ignoring().antMatchers("/leavebill/**","/image/**","/images/**","/workflow/**");
}
@Bean
PasswordEncoder passwordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap();
encoders.put(encodingId, new BCryptPasswordEncoder(12));
return new DelegatingPasswordEncoder(encodingId, encoders);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((auth) -> {
try {
auth
.antMatchers("/toLoginHtml").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/layui/**").permitAll() // 添加白名单
//.antMatchers("/test").hasAuthority("admin")
//.antMatchers("/test").hasAnyAuthority(new String[]{"zhou","jia"})
//.antMatchers("/test").hasRole("jun")
//.antMatchers("/test").hasAnyRole(new String[]{"love","liu","yi","fei"})
// 所有的请求
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60);
} catch (Exception e) {
throw new RuntimeException(e);
}
}).formLogin()
// 自定义登录页面地址
.loginPage("/login.html")
.loginProcessingUrl("/login")//登录接口自定义,系统自动生成
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/main.html")
// 放行登录地址(访问登录地址无需已登录状态)
.permitAll()
.and().csrf().disable();
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.logout().logoutUrl("/logout").logoutSuccessUrl("/logout.html").permitAll();
// 构建过滤链并返回
return http.build();
}
}
配置认证模式:
package com.it.springbootxiangmu.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.it.springbootxiangmu.bean.users;
import com.it.springbootxiangmu.mapper.usersMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
public class securityService implements UserDetailsService {
@Resource
usersMapper usersMapper;
Logger logger = LoggerFactory.getLogger(getClass());
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<users> queryWrapper =new QueryWrapper<>();
queryWrapper
.eq("username",username);
users users = usersMapper.selectOne(queryWrapper);
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("admin");
String encode = "{bcrypt}"+new BCryptPasswordEncoder()
.encode(users.getPassword());
return new User(users.getUsername(),encode,authorities);
}
}
其中的usermapper是下面介绍的mybatisplus的实现类;是从数据库查找对应的密码账号,交给user去执行登录验证。
2.2.3mybatisplus配置:
yml文件中:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #??sql??
map-underscore-to-camel-case: false
# ?????src/main/java??? classpath:/com/*/*/mapper/*Mapper.xml
# ?????resource?? classpath:/mapper/**.xml
mapper-locations: classpath:/mapper/**.xml
第一个是打印sql语句
第二个是关闭驼峰命名法
第三个是制定mapper.xml文件的地址,如果不配置默认在static下的mapper中
如果要分页的话还要配置,配置类:
package com.it.springbootxiangmu.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class mybatisplusconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
最后实体bean继承basemapper就行:
package com.it.springbootxiangmu.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.it.springbootxiangmu.bean.LeaveBill;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface leavebillMapper extends BaseMapper<LeaveBill> {
@Select("select * from leavebill ")
public List<LeaveBill> fengye(Page<LeaveBill> page,
@Param(Constants.WRAPPER) QueryWrapper<LeaveBill> queryWrapper) ;
@Insert("insert into leavebill (days,content,remark,leaveDate,state,user_id) " +
"values (#{days},#{content},#{remark},#{leaveDate},#{state},#{user_id})")
public void addone(LeaveBill leaveBill);
}
2.2.4activti7的配置
在导入文件依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.2</version>
</dependency>
配置yml文件:
activiti:
history-level: audit
db-history-used: true #???????????
deployment-mode: never-fail # ?? SpringAutoDeployment??????
历史表的开启;
最后就可以开始使用activti了!
3.相关功能的实现
3.1请假单的实现
创建一个请假单的bean 并在数据库也创建一个对应的表:
package com.it.springbootxiangmu.bean;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.util.Date;
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName("leavebill")
public class LeaveBill {
@TableId(value="id",type = IdType.AUTO)
private int id;
private int days;
private String content;
private String remark;
private String leaveDate;
private int state;
private String user_id;
}
@tablename注解:告诉mybatisplus 这个bean对应的是数据库的那个表。
@tableid注解: 是表字段的id主键
其他一样的可以不写 如果不一样在对应的字段写@colum
继承basemapper ,使用mybatisplus:
package com.it.springbootxiangmu.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.it.springbootxiangmu.bean.LeaveBill;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface leavebillMapper extends BaseMapper<LeaveBill> {
@Select("select * from leavebill ")
public List<LeaveBill> fengye(Page<LeaveBill> page,
@Param(Constants.WRAPPER) QueryWrapper<LeaveBill> queryWrapper) ;
@Insert("insert into leavebill (days,content,remark,leaveDate,state,user_id) " +
"values (#{days},#{content},#{remark},#{leaveDate},#{state},#{user_id})")
public void addone(LeaveBill leaveBill);
}
mybatisplus自带生成单表简单的增删改查,但不支持复杂的,这里我自定义了一些方法;
3.1.1请假单的页面:
这里只展示一个页面的写法,后面的页面异曲同工
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>请假单页面</title>
<link rel="stylesheet" href="../layui/css/layui.css" media="all">
<style type="text/css">
.layui-form-item{
width:800px;
}
.leaveBillLocation{
position: absolute;
z-index: 22;
border:2px solid red;
}
</style>
</head>
<body>
<button onclick="addLeaveBillPop()" type="button" class="layui-btn">添加请假单信息</button>
<div id="addLeaveBillContainer" style="width:900px;height:500px;display: none;">
<!-- -----------添加form表单-------------------------- -->
<blockquote class="layui-elem-quote">修改/添加</blockquote>
<form class="layui-form" id="addForm">
<!-- 请假天数 -->
<div class="layui-form-item">
<label class="layui-form-label">请假天数:</label>
<div class="layui-input-block">
<!--如果id值Wie-1表示添加功能,不为-1表示修改功能-->
<input type="hidden" name="id" value="-1" lay-verify="title" autocomplete="off" class="layui-input">
<input type="text" name="days" lay-verify="title" autocomplete="off" placeholder="请假天数" class="layui-input">
</div>
</div>
<!--请假时间-->
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">开始时间:</label>
<div class="layui-input-inline">
<input type="text" name="leaveDate" class="layui-input" id="leaveDate" placeholder="yyyy-MM-dd hh:mm:ss">
</div>
</div>
</div>
<!-- 请假原因 -->
<div class="layui-form-item">
<label class="layui-form-label">请假缘由:</label>
<div class="layui-input-block">
<input type="text" name="content" lay-verify="title" autocomplete="off" placeholder="请假原缘由" class="layui-input">
</div>
</div>
<!-- 请假备注 -->
<div class="layui-form-item">
<label class="layui-form-label">备注信息:</label>
<div class="layui-input-block">
<textarea name="remark" placeholder="备注信息" class="layui-textarea"></textarea>
</div>
</div>
<!-- 提交按钮 -->
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" onclick="updLeaveBill()" class="layui-btn" >保存信息</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>
<div id="findprocessresource" style="display:none;width:1027px;height: auto;">
</div>
<table id="leaveBillInfo" lay-filter="test"></table>
<script type="text/html" id="leaveBillBar">
{{# if(d.state==0){ }}
<a class="layui-btn layui-btn layui-btn-xs" lay-event="submitApplication">提交申请</a>
<a class="layui-btn layui-btn layui-btn-xs" onclick="deleteLeaveBillById('{{d.id}}')" lay-event="delleavebill">删除</a>
<a class="layui-btn layui-btn layui-btn-xs" lay-event="updleavebill">修改</a>
<a class="layui-btn layui-btn-xs" lay-event="more">更多 <i class="layui-icon layui-icon-down"></i></a>
{{# } }}
{{# if(d.state==3){ }}
<a class="layui-btn layui-btn layui-btn-xs" onclick="deleteLeaveBillById('{{d.id}}')" lay-event="delleavebill">删除</a>
{{# } }}
{{# if(d.state==1){ }}
<a class="layui-btn layui-btn layui-btn-xs" lay-event="findProcess">查看进度</a>
<a class="layui-btn layui-btn layui-btn-xs" lay-event="">审核意见</a>
{{# } }}
{{# if(d.state==2){ }}
<a class="layui-btn layui-btn layui-btn-xs" lay-event="">审核意见</a>
{{# } }}
</script>
<script src="../layui/layui.js"></script>
<script src="../js/jquery-1.8.0.min.js"></script>
<script>
layui.use(['table','jquery','layer','dropdown','laydate'], function(){
var table = layui.table;
var $=layui.jquery;
var layer=layui.layer;
var dropdown = layui.dropdown; //下拉菜单
var laydate = layui.laydate;
// 渲染
laydate.render({
elem: '#leaveDate',
type: 'datetime'
});
//初始化请假单信息
table.render({
elem: '#leaveBillInfo'
,height: 312
,url: '/getLeaveBillList' //数据接口
,page: true //开启分页
,cols: [ [ //表头
{field: 'id', title: '编号', width:'10%', sort: true, fixed: 'left'}
,{field: 'days', title: '请假天数', width:'9%'}
,{field: 'content', title: '请假内容', width:'17%'}
,{field: 'remark', title: '请假备注', width: '20%'}
,{field: 'leaveDate', title: '开始时间', width: '16%'}
,{field: 'state', title: '状态', width: '8%',templet: function(d){if(d.state == 0){return '录入'}else if(d.state==1){return '审批中'}else if(d.state==2){return '审批通过'}else{return '作废'}}}
,{title: '操作', width: '20%',toolbar: '#leaveBillBar'}
] ]
});
// 单元格工具事件
table.on('tool(test)', function(obj){ //注:tool 是工具条事件名,test 是 table 原始容器的属性 lay-filter="对应的值"
var data = obj.data //获得当前行数据
,layEvent = obj.event; //获得 lay-event 对应的值
if(layEvent === 'delleavebill'){
$.get("/deleteLeaveBillById",{"leaveBillId":data.id},function(msg){
window.location.reload();
});
}else if(layEvent === 'updleavebill'){
//如果较多数据表格不全的时候,需要发送异步进行查询
//此处所有的数据比较齐全,直接使用
var id=data.id;
$.get("/findLeaveBillById",{"leaveBillId":id},function(msg){
//这个id input是隐藏域,用于区分添加还是修改
$("input[name='id']").eq(0).val(id);
$("input[name='days']").eq(0).val(msg.days);
$("input[name='content']").eq(0).val(msg.content);
$("textarea[name='remark']").eq(0).val(msg.remark);
layer.open({
type: 1,
content: $('#addLeaveBillContainer') //这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
});
});
}else if(layEvent==='submitApplication'){//提交申请
$.get("/startProcessInstance",{"leaveBillId":data.id},function(msg){
window.location.reload();
});
}else if(layEvent==='findProcess'){//查看审核进度
var resource="<img style='width:100%;' src='workflow/getResourcesByLeaveBillId?leaveBillId="+data.id+"'/>";
$("#findprocessresource").html(resource);
//发送异步请求,查询正在执行任务的坐标信息
$.get("workflow/findNodeXYWH?leaveBillId="+data.id+"",function(msg){
$("#findprocessresource").append("<div class='leaveBillLocation' style='top:"+msg.y+"px; left:"+msg.x+"px; width:"+msg.w+"px; height:"+msg.h+"px; '></div>");
});
layer.open({
type: 1,
shadeClose:true,
offset: ['0px'],
content: $('#findprocessresource'), //这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
end:function(){
$('#findprocessresource').css("display","none")
}
});
}else if(layEvent === 'more'){
//下拉菜单
dropdown.render({
elem: this //触发事件的 DOM 对象
,show: true //外部事件触发即显示
,data: [{
title: '编辑'
,id: 'edit'
},{
title: '删除'
,id: 'del'
}]
,click: function(menudata){
if(menudata.id === 'del'){
layer.confirm('真的删除行么', function(index){
//obj.del(); //删除对应行(tr)的DOM结构
//layer.close(index);
//向服务端发送删除指令
});
} else if(menudata.id === 'edit'){
layer.msg('编辑操作,当前行 ID:'+ data.id);
}
}
,align: 'right' //右对齐弹出
,style: 'box-shadow: 1px 1px 10px rgb(0 0 0 / 12%);' //设置额外样式
})
}
});
});
</script>
<script>
function addLeaveBillPop(){
//将影藏域的值重置成-1
$("input[name='id']").eq(0).val("-1");
$("input[name='days']").eq(0).val("");
$("input[name='content']").eq(0).val("");
$("textarea[name='remark']").eq(0).val("");
layer.open({
type: 1,
shadeClose:true,
content: $('#addLeaveBillContainer'),
end:function(){
$('#addLeaveBillContainer').css("display","none")
}
});
}
//通过异步请求添加请假单信息
function updLeaveBill(){
//$("#addForm").serialize()序列化表单
//点击修改弹框,在弹框中触发updLeaveBill执行修改功能
//此时id的值默认为-1;
$.ajax({
type:"get",
url:"/updLeaveBill",
cache:false,
dataType:"text",
data:$("#addForm").serialize(),
success:function(msg){
window.location.reload();
}
});
}
//根据id删除学生细腻
function deleteLeaveBillById(id){
$.get("/deleteLeaveBillId",{"id":id},function(){
window.location.reload();
});
}
</script>
</body>
</html>
所以这里我们写一个方法把从后端数据库查到的请假单信息发送到页面的
/getLeaveBillList
,
@Controller
public class leavebillController {
Logger logger = LoggerFactory.getLogger(getClass());
@Resource
leavebillService leavebillService;
//初始化分页
@RequestMapping("/getLeaveBillList")
@ResponseBody
public Map<String, Object> getLeaveBillList(HttpServletRequest request,Authentication authentication){
logger.info("test====初始化成功");
User user=(User) authentication.getPrincipal();
String username=user.getUsername();
int page=Integer.valueOf(request.getParameter("page"));
int limit=Integer.valueOf(request.getParameter("limit"));
Map<String,Object> map = new HashMap<>();
List<LeaveBill> list = leavebillService.geren(page,limit,username);
logger.info("======lsit==="+list);
map.put("code",0);
map.put("msg","执行成功");
map.put("count",leavebillService.allsize());
map.put("data",list);
return map;
}
我们使用了分页操作和只查登录人的请假单信息
User user=(User) authentication.getPrincipal();
获取当前登录人的信息
:
service层:
public List<LeaveBill> geren(int page ,int limit,String user_id){
Page<LeaveBill> page1 = new Page<>(page,limit);
QueryWrapper<LeaveBill> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id",user_id);
Page<LeaveBill> list = leavebillMapper.selectPage(page1,queryWrapper);
List<LeaveBill> list1 = list.getRecords();
return list1;
}
3.1.2添加和修改操作
controller层:
@RequestMapping("/updLeaveBill")
@ResponseBody
public String updLeaveBill(LeaveBill leaveBill , Authentication authentication){
User user=(User) authentication.getPrincipal();
String username=user.getUsername();
leaveBill.setUser_id(username);
logger.info("updLeaveBill====初始化成功");
if (leaveBill.getId()==-1){
leavebillService.addoneleavebill(leaveBill);
}
else {
leavebillService.xigai(leaveBill);
}
return "1";
}
添加前端页面传过来的id值是-1 而修改传过来的值是对应的id值,所以前后端可以使用同一个页面和同一个请求:
servic层:
public void addoneleavebill(LeaveBill leaveBill){
leavebillMapper.addone(leaveBill);
}
//查找
public LeaveBill findLeaveBillById(int id){
QueryWrapper<LeaveBill> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id",id);
return leavebillMapper.selectOne(queryWrapper);
}
//修改
public void xigai(LeaveBill leaveBill){
leavebillMapper.updateById(leaveBill);
}
删除很简单就吧细说了。
3.1.3启动任务,确定请假
controller层:
@RequestMapping("/startProcessInstance")
@ResponseBody
//启动任务
public String startProcessInstance(ParameterBean parameterBean, Authentication authentication){
/*String processDefinitionKey=parameterBean.getProcessDefinitionKey();*/
String leaveBillId= parameterBean.getLeaveBillId();
User user=(User) authentication.getPrincipal();
System.out.println("===user===="+user);
String username=user.getUsername();
workflowService.startProcessInstance("Finance",leaveBillId,username);
return "redirect:/leavebill/list.html";
}
service层:
public void startProcessInstance(String processDefinitionKey, String leaveBillId, String username) {
//1、启动任务需要的key,2、请假单id-businessKey,3、办理人信息-当前登陆人信息
String businessKey = processDefinitionKey + "." + leaveBillId;//LeaveBill.1001
Map<String, Object> variables = new HashMap<>();
variables.put("username", username);
runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
System.out.println("=========启动成功============");
//修改请假单的状态
LeaveBill leaveBill = leaveBillMapper.selectById(leaveBillId);
leaveBill.setState(1);
leaveBillMapper.updateById(leaveBill);
System.out.println("=====请假单状态为1=======");
}
解释一下:因为请假单和activti 之间没有任何联系,现在我们启动的时候认为的给他们之间创建联系就是在启动的时候给activit表添加一个字段businessKey ,通过这个字段的后半部分可以找到对应的请假单信息 因为businessKey是由两部分拼接的 ,前半部分是部署的key ,后半部分是请假单id,所以要找请假单信息可以通过,正在执行的流程实例中获取businessKey,然后截取后半部分,就可以查到请假单信息。反正依然。最后执行启动项目要把请假单的状态进行修改。
3.2创建零散的前端传过来的一些值封装成bean
package com.it.springbootxiangmu.bean;
import lombok.*;
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class ParameterBean {
private String leaveBillId;
private String deploymentName;
private String deploymentId;
private String processDefinitionKey;//流程定义的key,用于启动任务
private String taskId;
private String username;
private String suggest;
private String comment;
private String processInstanceId;
}
可以方便管理和事结构更清晰。
3.3部署和流程实例等信息展示和生成
3.3.1部署
@RequestMapping("/deployment")
/*@ResponseBody*/
public String deployment(@RequestParam("file1") MultipartFile file,
@RequestParam("deploymentName") String deploymentName) throws IOException {
logger.info("=========deployment====初始化成功");
// 获取上传的zip文件的输入流
InputStream inputStream = file.getInputStream();
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
// 使用RepositoryService进行部署
Deployment deployment = repositoryService.createDeployment()
.name(deploymentName)
.addZipInputStream(zipInputStream)
.deploy();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("deployment");
return "redirect:/workflow/deployment.html";
}
要配置文件上传和下载的properties文件:
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=100MB
是文件上传的大小等。
@RequestParam("file1") MultipartFile file:利用这个注解可以取到前端传过来 ,名称为file1的文件 类型为multipartfile 和@RequestParam("deploymentName") String deploymentName 部署名称 然后通过zip压缩 部署文件。
3.3.2部署信息和流程定义信息的查找和显示
这个因为没有对应的实例bean,因为我们使用的是接口,所以我们自己封装一个部署信息和流程定义信息的bean在把查到的信息赋值给对应的bean在返回给页面:
@RequestMapping("/findDeployment")
@ResponseBody
public Map<String,Object> findDeployment(){
List<Deployment> list= workflowService.findeployment();
List<DeploymentDTO> list1=new ArrayList<>();
for (int i=0;i<list.size();i++){
DeploymentDTO deploymentDTO=new DeploymentDTO();
deploymentDTO.setId(list.get(i).getId());
deploymentDTO.setName(list.get(i).getName());
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
deploymentDTO.setDeploymentTime(sdf.format(list.get(i).getDeploymentTime()));
deploymentDTO.setCategory(list.get(i).getCategory());
deploymentDTO.setVersion(list.get(i).getVersion());
list1.add(deploymentDTO);
}
Map<String,Object> result=new HashMap<>();
result.put("code",0);
result.put("msg","执行成功");
result.put("count",list1.size());
result.put("data",list1);
return result;
}
//查询流程定义信息
@RequestMapping("/workflow/findProcessDefinition")
@ResponseBody
public Map<String,Object> findProcessDefinition(){
List<ProcessDefinition> list= workflowService.findProcessDefinition();
List<ProcessDefinitionDTO> list1=new ArrayList<>();
for(int i=0;i<list.size();i++){
ProcessDefinitionDTO pd=new ProcessDefinitionDTO();
pd.setId(list.get(i).getId());
pd.setName(list.get(i).getName());
pd.setVersion(list.get(i).getVersion());
pd.setKey(list.get(i).getKey());
pd.setResourceName(list.get(i).getResourceName());
pd.setDiagramResourceName(list.get(i).getDiagramResourceName());
pd.setDeploymentId(list.get(i).getDeploymentId());
pd.setSuspended(list.get(i).isSuspended());
list1.add(pd);
}
Map<String,Object> result=new HashMap<>();
result.put("code",0);
result.put("msg","执行成功");
result.put("count",list1.size());
result.put("data",list1);
return result;
}
删除的不解释 ,前端传给id就删了但是记得级联删除
@RequestMapping("/deleteDeloyment")
@ResponseBody
public void deleteDeployment(ParameterBean parameterBean){
logger.info("====deleteDeployment=======");
String deploymentId=parameterBean.getDeploymentId();
workflowService.deleteDeployment(deploymentId);
}
3.4请假审批
3.4.1页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>待办事项</title>
<link rel="stylesheet" href="../layui/css/layui.css" media="all">
<script type="text/javascript" src="../js/jquery-1.8.0.min.js"></script>
<style type="text/css">
.layui-form-item{
width:800px;
}
</style>
</head>
<body>
<!-- 审批内容显示div -->
<div id="complete_find" style="width:800px;display:none;">
<blockquote class="layui-elem-quote">审批意见</blockquote>
<form class="layui-form" id="completeForm">
<!-- 请假天数 -->
<div class="layui-form-item">
<label class="layui-form-label">请假天数:</label>
<div class="layui-input-block">
<!--/**
* taskId:q前端传递
* processInstanceId:从前端获取
* leaveBillId:前端传递
* suggest:前端获取,获取用户审批意见 同意或驳回
* comment:审批意见就是弹出框的文本域,
*/-->
<input type="hidden" value="" name="suggest" lay-verify="title" autocomplete="off" class="layui-input">
<input type="hidden" value="" name="processInstanceId" lay-verify="title" autocomplete="off" class="layui-input">
<input type="hidden" value="" name="leaveBillId" lay-verify="title" autocomplete="off" class="layui-input">
<input type="hidden" value="" name="taskId" lay-verify="title" autocomplete="off" class="layui-input">
<input type="text" name="days" lay-verify="title" autocomplete="off" placeholder="请假天数" class="layui-input">
</div>
</div>
<!-- 请假原因 -->
<div class="layui-form-item">
<label class="layui-form-label">请假缘由:</label>
<div class="layui-input-block">
<input type="text" name="content" lay-verify="title" autocomplete="off" placeholder="请假原缘由" class="layui-input">
</div>
</div>
<!-- 请假备注 -->
<div class="layui-form-item">
<label class="layui-form-label">备注信息:</label>
<div class="layui-input-block">
<textarea name="remark" placeholder="备注信息" class="layui-textarea"></textarea>
</div>
</div>
<!-- 审批意见 -->
<div class="layui-form-item">
<label class="layui-form-label">审批意见:</label>
<div class="layui-input-block">
<textarea name="comment" placeholder="审批意见" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">搜索选择框</label>
<div class="layui-input-inline">
<select name="modules" lay-verify="required" lay-search th:each="zhou,str:${users}">
<option th:text="${zhou.username}"></option>
<span>1</span>
</select>
</div>
</div>
<!-- 提交按钮 -->
<div class="layui-form-item">
<div class="layui-input-block" id="completeDiv">
<button type="submit" class="layui-btn" lay-submit="" lay-filter="demo1">保存信息</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
<table id="comment" lay-filter="test1"></table>
</div><!-- complete_find 审批内容显示div -->
<blockquote class="layui-elem-quote">任务审批</blockquote>
<table id="demo" lay-filter="test"></table>
<script src="../layui/layui.js"></script>
<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn layui-btn-xs" lay-event="completeTask">办理任务</a>
</script>
<script>
layui.use(['table','upload','util'], function(){
var table = layui.table;
var upload = layui.upload;
var util=layui.util;
//第一个实例
table.render({
elem: '#demo'
,height: 312
,url: '/findTaskByAssignee' //数据接口
,cols: [ [ //表头
{field: 'id', title: '任务编号', width:'8%', sort: true, fixed: 'left'}
,{field: 'leaveDate', title: '开始时间', width:'17%'}
,{field: 'user_id', title: '请假人', width:'10%', sort: true/*,templet:function(msg){
return msg.leaveBill.user_id;
}*/}
,{field: 'content', title: '请假缘由', width:'25%'}
,{field: 'days', title: '请假天数', width: '10%'}
,{field: 'remark', title: '开始备注', width: '15%'}
,{title: '操作', width: '15%',toolbar: '#barDemo'}
] ]
});
//单元格工具事件
table.on('tool(test)', function(obj){ //注:tool 是工具条事件名,test 是 table 原始容器的属性 lay-filter="对应的值"
var data = obj.data //获得当前行数据 TaskAndLeaveBillVO1
,layEvent = obj.event; //获得 lay-event 对应的值
if(layEvent==='completeTask'){
//通过异步请求获取框子需要的内容:请假单信息+连线信息+批注信息
//办理任务--获取请假单内容
var days=data.days;
var content=data.content;
var remark=data.remark;
//$("input[name='id']").eq(0).val(id);
$("input[name='days']").eq(0).val(days);
$("input[name='content']").eq(0).val(content);
$("textarea[name='remark']").eq(0).val(remark);
//为隐藏域赋值
/**
* taskId:q前端传递
* processInstanceId:从前端获取
* leaveBillId:前端传递
* suggest:前端获取,获取用户审批意见 同意或驳回
* comment:审批意见就是弹出框的文本域,
*/
$("input[name='taskId']").eq(0).val(data.taskId);
$("input[name='processInstanceId']").eq(0).val(data.processInstanceId);
$("input[name='leaveBillId']").eq(0).val(data.leaveBillId);
//办理任务2--查询当前节点的连线细腻
$.get("/getCurrentUserTaskConnection",{"taskId":data.taskId},function(msg){
//通过jquery生成按钮
var btns="";
for(var i=0;i<msg.length;i++){
btns+="<button type='button' onclick=complete('"+msg[i]+"') class='layui-btn' lay-submit='' lay-filter='demo111'>"+msg[i]+"</button>";
}
$("#completeDiv").html(btns);
});
//加载审批意见表信息
/*table.render({
elem: '#comment'
,url: 'leavebill/findComment?processInstanceId='+data.processInstanceId //查询批注信息的数据接口
,page: true //开启分页
,cols: [ [ //表头
{field: 'comment_time', title: '时间', width:'20%', fixed: 'left'}
,{field: 'user_id', title: '审批人', width:'30%' }
,{field: 'message', title: '审批信息', width:'40%' }
] ]
});*/
layer.open({
type: 1,
shadeClose:true,
offset: ['0px'],
size:['500px','800px'],
content: $('#complete_find'), //这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
end:function(){
$('#findResourceContainer').css("display","none")
}
});
}
});
});
/**
* * 接收的参数:
* * taskId:需要接收任务id
* * processInstanceId:流程实例id 判断流程是否结束
* * leaveBillId:请假单id的值
* * comment:获取审批意见
* * suggest:获取用户的审批意见
*/
/**
* taskId:q前端传递
* suggest:前端获取,获取用户审批意见 同意或驳回
* processInstanceId:从前端获取
* leaveBillId:前端传递
* comment:审批意见,需要从前端传递
*/
function complete(suggest){
$("input[name='suggest']").eq(0).val(suggest);//设置隐藏域的值
$.ajax({
type:"get",
url:"/completeTask",
cache:false,
dataType:"text",
data:$("#completeForm").serialize(),//序列化表单
success:function(data){
window.location.reload();
}
});
}
</script>
</body>
</html>
3.4.1显示对应登录人的代办任务
@RequestMapping("/findTaskByAssignee")
@ResponseBody
public Map<String,Object> findLeaveBillAndTask(Authentication authentication, ModelMap map) {
List<users> hhh=usersMapper.selectList(null);
logger.info("======hhh====="+hhh);
map.put("users",hhh);
User user=(User) authentication.getPrincipal();
System.out.println("===user===="+user);
String username=user.getUsername();
List<LeaveBillAndTask> list=workflowService.findLeaveBillAndTask(username);
Map<String,Object> result=new HashMap<>();
result.put("code",0);
result.put("msg","执行成功");
result.put("count",list.size());
result.put("data",list);
return result;
}
然后点击审批按钮,使用jquery给弹出但框子赋值,然后再发送一个异步请求查取节点的连接信息 ,根据连接信息动态的生成按钮,比如是只要审批就行,还是审批同意和驳回操作等。
controller层:
@RequestMapping("/getCurrentUserTaskConnection")
@ResponseBody
public List<String> getCurrentUserTaskConnection(ParameterBean parameterBean){
String taskId=parameterBean.getTaskId();
System.out.println("=====taskid==="+taskId);
return workflowService.getCurrentUserTaskConnection(taskId);
}
servicer层:
public List<String> getCurrentUserTaskConnection(String taskId) {
// 获取当前任务
Task task = taskService.
createTaskQuery().
taskId(taskId).
singleResult();
//获取当前模型
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
// 获取当前节点
UserTask userTask = (UserTask) bpmnModel.getFlowElement(task.getTaskDefinitionKey());
//获取节点出口线段
List<SequenceFlow> list = userTask.getOutgoingFlows();
List<String> shenpiBtn = new ArrayList<>();
if (list.size() <= 1) {
shenpiBtn.add("默认批准");
} else {
for (int i = 0; i < list.size(); i++) {
shenpiBtn.add(list.get(i).getName());
}
}
/*Map<String, GraphicInfo> map=bpmnModel.getLocationMap();
Set<String> sets=map.keySet();
Iterator<String> ites= sets.iterator();
while(ites.hasNext()){
System.out.println(map.get(ites.next()).getX());
}*/
return shenpiBtn;
}
然后通过配节形成操作数:
$.get("/getCurrentUserTaskConnection",{"taskId":data.taskId},function(msg){
//通过jquery生成按钮
var btns="";
for(var i=0;i<msg.length;i++){
btns+="<button type='button' onclick=complete('"+msg[i]+"') class='layui-btn' lay-submit='' lay-filter='demo111'>"+msg[i]+"</button>";
}
$("#completeDiv").html(btns);
});
3.4.2办理
controller层:
//审批任务
@RequestMapping("/completeTask")
@ResponseBody
public void completeTask(ParameterBean parameterBean) {
String taskId=parameterBean.getTaskId();
String username=parameterBean.getUsername();
String suggest=parameterBean.getSuggest();
String processInstanceId=parameterBean.getProcessInstanceId();
String leaveBillId=parameterBean.getLeaveBillId();
String comment=parameterBean.getComment();
workflowService.complete(taskId,username,suggest,comment,processInstanceId,leaveBillId);
}
service层:
public void complete(String taskId, String username,
String suggest, String comment,
String processInstanceId,
String leaveBillId
) {
Map<String, Object> variables = new HashMap<>();
//
variables.put("username", username);
variables.put("suggest", suggest);
variables.put("money", 10000);
//添加批准信息
//taskId,processInstanceId,message
Authentication.setAuthenticatedUserId(username);
taskService.setVariables(taskId, variables);
taskService.addComment(taskId, processInstanceId, comment);
//办理任务
taskService.complete(taskId, variables);
//判断当前任务是否审批结束
//如果任务结束,修改请假单状态1->2
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
if (historicProcessInstance.getEndTime() != null) {
LeaveBill leaveBill = leaveBillMapper.selectById(leaveBillId);
System.out.println("======hhhh====" + leaveBillId);
leaveBill.setState(2);
leaveBillMapper.updateById(leaveBill);
}
}
前端通过序列化表单的手法把整个数据传到后端 controller把办理需要的信息发送给service层进行具体的业务处理。
点击那个按钮就把隐藏域的suggest的值附上,不用对应按钮的值,就不用写特殊的方法了
function complete(suggest){
$("input[name='suggest']").eq(0).val(suggest);//设置隐藏域的值
$.ajax({
type:"get",
url:"/completeTask",
cache:false,
dataType:"text",
data:$("#completeForm").serialize(),//序列化表单
success:function(data){
window.location.reload();
}
});
通过这俩个函数进行办理:
taskService.setVariables(taskId, variables); taskService.addComment(taskId, processInstanceId, comment); //办理任务 taskService.complete(taskId, variables);
然后最后判断是不是最后的任务,(任务结束没有)。在把请假单的状态进行更改。
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
if (historicProcessInstance.getEndTime() != null) {
LeaveBill leaveBill = leaveBillMapper.selectById(leaveBillId);
System.out.println("======hhhh====" + leaveBillId);
leaveBill.setState(2);
leaveBillMapper.updateById(leaveBill);
}
这里采用的看历史流程实例的结束时间有没有,有就是结束了。
4.总结
今天的项目分享见到这里了!要完整代码的可以联系小编!