怎么给做的网站做百度搜索/怎么创建一个网址
原项目中用到了工作流引擎,使用若依框架开发,
原二开使用项目:https://gitee.com/y_project/RuoYi-Vue
基于activiti7地址:https://gitee.com/smell2/ruoyi-vue-activiti
导入模块到原二开项目中
使用步骤
- admin导入bpmn文件,或者绘制工作流,并激活。
- post为普通员工进入我的审批可以查看所有审批(经销商角色)
- 数据会存入历史表和任务表
- 历史表展现全部任务数据,task会展示对应岗位的相应条数据
- 登录商管账号,进入代办任务
- 进行审批,通过则状态变为“待财务审核”,进入下一节点;不通过则状态变为“审核失败”,流转结束
- 登录财务账号,进入代办任务
- 进行审批,通过则状态变为“审核成功”,流转结束;不通过则状态变为“审核失败”,流转结束
- 经销商可以查看审批,流转完成
注意事项
工作流程菜单在开发工具查看(没有的话需要事先插入db和相关代码),在线绘制进行bpmn格式的流程图,部署流程导入已经存在的bpmn文件,根据流程key,在java中进行声明和调用该key即可正式使用该工作流
后端相关
activiti7相关依赖
<dependency><groupId>org.activiti</groupId><artifactId>activiti-spring-boot-starter</artifactId><version>7.1.0.M4</version>
</dependency>
<dependency><groupId>org.activiti.dependencies</groupId><artifactId>activiti-dependencies</artifactId><version>7.1.0.M4</version><type>pom</type>
</dependency>
解决依赖冲突
activiti7新版本会和mybatis和spring security起冲突,如果项目使用shiro等安全框架会有更大的适配问题
原项目有spring security的情况下需加上配置文件属性
main:allow-bean-definition-overriding: true
activity配置文件属性如下,包括检测流程定义,自动更新和生成db系统表,历史数据级别和使用等
activiti:check-process-definitions: falsedatabase-schema-update: truehistory-level: fulldb-history-used: true
db依赖问题或mybatis版本冲突解决方法如下
<exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions>
数据源配置,给url加上如下后缀属性
%2B8&nullCatalogMeansCurrent=true
完整url格式如下:
url: jdbc:mysql://localhost:99999/test11111111?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
在使用mysql-connect 8.+以上版本的时候需要添加***nullCatalogMeansCurrent=true***参数,否则在使用mybatis-generator生成表对应的xml等时会扫描整个服务器里面的全部数据库中的表,而不是扫描对应数据库的表。因此mysql会扫描所有的库来找表,如果其他库中有相同名称的表,activiti就以为找到了,本质上这个表在当前数据库中并不存在。
接口
流程部署
可以将bpmn文件放在resource下的processes目录下,activiti启动的时候会自动加载该目录下的bpmn文件,或者通过调用接口方式部署:
- 上传文件部署
@PostMapping("/uploadFileAndDeployment")
public BaseResponse uploadFileAndDeployment(@RequestParam("processFile")MultipartFile processFile,@RequestParam(value = "processName",required = false) String processName){String originalFilename = processFile.getOriginalFilename();String extension = FilenameUtils.getExtension(originalFilename);if (processName != null){processName = originalFilename;}try {InputStream inputStream = processFile.getInputStream();Deployment deployment = null;if ("zip".equals(extension)){// 压缩包部署方式ZipInputStream zipInputStream = new ZipInputStream(inputStream);deployment = repositoryService.createDeployment().addZipInputStream(zipInputStream).name(processName).deploy();}else if ("bpmn".equals(extension)){// bpmn文件部署方式deployment = repositoryService.createDeployment().addInputStream(originalFilename,inputStream).name(processName).deploy();}return BaseResponse.success(deployment);} catch (IOException e) {e.printStackTrace();}return BaseResponse.success();
}
- 上传BPMN内容字符串部署
@PostMapping("/postBPMNAndDeployment")
public BaseResponse postBPMNAndDeployment(@RequestBody AddXMLRequest addXMLRequest){Deployment deploy = repositoryService.createDeployment()// .addString 第一次参数的名字如果没有添加.bpmn的话,不会插入到 ACT_RE_DEPLOYMENT 表中.addString(addXMLRequest.getProcessName()+".bpmn", addXMLRequest.getBpmnContent()).name(addXMLRequest.getProcessName()).deploy();return BaseResponse.success(deploy);
}
- 获取流程资源文件
@GetMapping("/getProcessDefineXML")
public void getProcessDefineXML(String deploymentId, String resourceName, HttpServletResponse response){try {InputStream inputStream = repositoryService.getResourceAsStream(deploymentId,resourceName);int count = inputStream.available();byte[] bytes = new byte[count];response.setContentType("text/xml");OutputStream outputStream = response.getOutputStream();while (inputStream.read(bytes) != -1) {outputStream.write(bytes);}inputStream.close();} catch (Exception e) {e.toString();}
}
流程实例
- 启动
@PostMapping("/startProcess")
public BaseResponse startProcess(String processDefinitionKey, String instanceName,@AuthenticationPrincipal LocalUserDetail userDetail){ProcessInstance processInstance = null;try{StartProcessPayload startProcessPayload = ProcessPayloadBuilder.start().withProcessDefinitionKey(processDefinitionKey).withBusinessKey("businessKey").withVariable("sponsor",userDetail.getUsername()).withName(instanceName).build();processInstance = processRuntime.start(startProcessPayload);}catch (Exception e){System.out.println(e);return BaseResponse.error("开启失败:"+e.getLocalizedMessage());}return BaseResponse.success(processInstance);
}
- 挂起
@PostMapping("/suspendInstance/{instanceId}")
public BaseResponse suspendInstance(@PathVariable String instanceId){ProcessInstance processInstance = processRuntime.suspend(ProcessPayloadBuilder.suspend().withProcessInstanceId(instanceId).build());return BaseResponse.success(processInstance);
}
- 激活
@PostMapping("/resumeInstance/{instanceId}")
public BaseResponse resumeInstance(@PathVariable String instanceId){ProcessInstance processInstance = processRuntime.resume(ProcessPayloadBuilder.resume().withProcessInstanceId(instanceId).build());return BaseResponse.success(processInstance);
}
任务数据
- 通过taskid完成任务
@PostMapping("/completeTask/{taskId}")
public BaseResponse completeTask(@PathVariable String taskId){Task task = taskRuntime.task(taskId);if (task.getAssignee()==null){// 说明任务需要拾取taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(taskId).build());}taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(taskId).build());return BaseResponse.success();
}
- 获取自己的任务(与鉴权机制挂钩)
@GetMapping("/getTasks")
public BaseResponse getTasks(){Page<Task> taskPage = taskRuntime.tasks(Pageable.of(0, 100));List<Task> tasks = taskPage.getContent();List<TaskVO> taskVOS = new ArrayList<>();for (Task task : tasks) {TaskVO taskVO = TaskVO.of(task);ProcessInstance instance = processRuntime.processInstance(task.getProcessInstanceId());taskVO.setInstanceName(instance.getName());taskVOS.add(taskVO);}return BaseResponse.success(taskVOS);
}
历史数据
- 查询
public List<HistoricActivityInstanceVO> getProcessHistoryByBusinessKey(String businessKey) {ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(businessKey).singleResult();List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(instance.getId()).orderByHistoricActivityInstanceStartTime().asc().list();List<HistoricActivityInstanceVO> historicActivityInstanceVOList = new ArrayList<>();historicActivityInstanceList.forEach(historicActivityInstance -> historicActivityInstanceVOList.add(VOConverter.getHistoricActivityInstanceVO(historicActivityInstance)));return historicActivityInstanceVOList;
}
- 详情查询
HistoricDetailQuery historicDetailQuery = historyService.createHistoricDetailQuery();
List<HistoricDetail> historicDetails = historicDetailQuery.processInstanceId(instanceId).orderByTime().list();
for (HistoricDetail hd: historicDetails) {System.out.println("流程实例ID:"+hd.getProcessInstanceId());System.out.println("活动实例ID:"+hd.getActivityInstanceId());System.out.println("执行ID:"+hd.getTaskId());System.out.println("记录时间:"+hd.getTime());
}
- 历史流程实例查询
HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
List<HistoricProcessInstance> processInstances = historicProcessInstanceQuery.processDefinitionId(processDefinitionId).list();
for (HistoricProcessInstance hpi : processInstances) {System.out.println("业务ID:"+hpi.getBusinessKey());System.out.println("流程定义ID:"+hpi.getProcessDefinitionId());System.out.println("流程定义Key:"+hpi.getProcessDefinitionKey());System.out.println("流程定义名称:"+hpi.getProcessDefinitionName());System.out.println("流程定义版本:"+hpi.getProcessDefinitionVersion());System.out.println("流程部署ID:"+hpi.getDeploymentId());System.out.println("开始时间:"+hpi.getStartTime());System.out.println("结束时间:"+hpi.getEndTime());
}
- 任务历史查询(某一次流程的执行经历的多少任务)
HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
List<HistoricTaskInstance> taskInstances = historicTaskInstanceQuery.taskId(taskId).list();
for (HistoricTaskInstance hti : taskInstances) {System.out.println("开始时间:"+hti.getStartTime());System.out.println("结束时间:"+hti.getEndTime());System.out.println("任务拾取时间:"+hti.getClaimTime());System.out.println("删除原因:"+hti.getDeleteReason());
}
github例子:https://github.com/Activiti/activiti-examples
鉴权机制
官方security轮子
@Component
public class SecurityUtil {// 模拟调用了SpringSecurity 登录鉴权private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);@Autowiredprivate UserDetailsService userDetailsService;public void logInAs(String username) {UserDetails user = userDetailsService.loadUserByUsername(username);if (user == null) {throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");}logger.info("> Logged in as: " + username);SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return user.getAuthorities();}@Overridepublic Object getCredentials() {return user.getPassword();}@Overridepublic Object getDetails() {return user;}@Overridepublic Object getPrincipal() {return user;}@Overridepublic boolean isAuthenticated() {return true;}@Overridepublic void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {} @Overridepublic String getName() {return user.getUsername();}}));org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);}
}
与原项目spring security整合
新api包括taskRuntime和processRuntime都会强制使用security,源码使用了如下注解:
@PreAuthorize(“hasRole(‘ACTIVITI_USER’)”)
直接使用接口会导致无权限不允许访问
activiti7中对原有的一些接口做了二次封装,从而进一步简化了用户的使用流程。
通过查看这个两个API的实现类源码来看,调用的话需要调用的用户含有ACTIVITI_USER角色权限。所以,如果没有使用SpringSecurity的话,这两个API便不能直接调用。
- 先在用户验证处理中插入GROUP_的岗位post和加入ACTIVITI_USER的role
public UserDetails createLoginUser(SysUser user)
{Set<String> postCode = sysPostService.selectPostCodeByUserId(user.getUserId());postCode = postCode.parallelStream().map( s -> "GROUP_" + s).collect(Collectors.toSet());postCode.add("ROLE_ACTIVITI_USER");List<SimpleGrantedAuthority> collect = postCode.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList());return new LoginUser(user, permissionService.getMenuPermission(user), collect);//return new LoginUser(user, permissionService.getMenuPermission(user));
}
- 在login controller中,每次登录的时候鉴权
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
最后将信息放入token中处理
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
{tokenService.verifyToken(loginUser);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
- SecurityConfig也别忘记配
httpSecurity// CSRF禁用,因为不使用session.csrf().disable()// 认证失败处理类.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login 验证码captchaImage 允许匿名访问.antMatchers("/ssologin/token", "/ssologin","/login", "/captchaImage","/getssourl","/ssologin").anonymous().antMatchers(HttpMethod.GET,"/*.html","/**/*.html","/**/*.css","/**/*.js").permitAll().antMatchers("/processDefinition/**").permitAll().antMatchers("/activitiHistory/**").permitAll().antMatchers("/profile/**").anonymous().antMatchers("/common/download**").anonymous().antMatchers("/common/download/resource**").anonymous().antMatchers("/swagger-ui.html").anonymous().antMatchers("/swagger-resources/**").anonymous().antMatchers("/webjars/**").anonymous().antMatchers("/*/api-docs").anonymous().antMatchers("/druid/**").anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
查询当前用户任务
- taskservice方法
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.根据流程定义的key,负责人assignee来实现当前用户的任务列表查询Task task = taskService.createTaskQuery().processDefinitionKey("holiday").taskAssignee(SecurityUtils.getUsername() //通过鉴权拿到当前角色).singleResult();
//4.任务列表的展示System.out.println("流程实例ID:"+task.getProcessInstanceId());System.out.println("任务ID:"+task.getId()); //5002System.out.println("任务负责人:"+task.getAssignee());System.out.println("任务名称:"+task.getName());
- taskruntime方法(新版本api)
通过taskRuntime.tasks获取任务列表并分页,在实例化一条工作流后,activiti会将数据存到ACT_RU_TASK和ACT_RU_IDENTITYLINK表中,IDENTITYLINK通过task_id作为外键关联TASK表,IDENTITYLINK根据用户身份鉴别相应的角色,通过GROUP_ID筛选出对应数据,TYPE_字段则显示该角色是参与者还是贡献者,db如下:
若衣源码:
@Override
public Page<ActTaskDTO> selectProcessDefinitionList(PageDomain pageDomain) { Page<ActTaskDTO> list = new Page<ActTaskDTO>();org.activiti.api.runtime.shared.query.Page<Task> pageTasks = taskRuntime.tasks(Pageable.of((pageDomain.getPageNum() - 1) * pageDomain.getPageSize(), pageDomain.getPageSize()));List<Task> tasks = pageTasks.getContent();int totalItems = pageTasks.getTotalItems();list.setTotal(totalItems);if (totalItems != 0) {Set<String> processInstanceIdIds = tasks.parallelStream().map(t -> t.getProcessInstanceId()).collect(Collectors.toSet());List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery().processInstanceIds(processInstanceIdIds).list();List<ActTaskDTO> actTaskDTOS = tasks.stream().map(t -> new ActTaskDTO(t, processInstanceList.parallelStream().filter(pi -> t.getProcessInstanceId().equals(pi.getId())).findAny().get())).collect(Collectors.toList());list.addAll(actTaskDTOS);}return list;
}
实际项目遇到的问题及解决方法
Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘VERSION_’ in ‘field list’
Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column ‘PROJECT_RELEASE_VERSION_’ in ‘field list’
原因:
创建表缺少VERSION_字段
添加两个字段。
新版bug问题解决
alter table ACT_RE_DEPLOYMENT add column PROJECT_RELEASE_VERSION_ varchar(255) DEFAULT NULL;
alter table ACT_RE_DEPLOYMENT add column VERSION_ varchar(255) DEFAULT NULL;
前端相关
基于BPMN2.0的工作流
demo实例https://demo.bpmn.io/s/start
节点如下,教程https://www.jianshu.com/p/a8a21870986a
审批流程图:
bpmnjs引入
导入相关代码,包括我的审批,代办任务,历史流程等的表单设计和模块划分,其他的调整等