首页 文章 精选 留言 我的

精选列表

搜索[SpringBoot],共4254篇文章
优秀的个人博客,低调大师

Springboot 2.x 如何解决重复提交 (本地锁的实践)

有没有遇到过这种情况:网页响应很慢,提交一次表单后发现没反应,然后你就疯狂点击提交按钮(12306就经常被这样怒怼),如果做过防重复提交还好,否则那是什么级别的灾难就不好说了。。。 本文主要是应用 自定义注解、 spring AOP、· Guava Cache 生成一种本地锁,来达到的防重复提交效果,由于是基于内存的缓存,所以这种实现方式并不适用于分布式服务 Guava是什么? guava包是google嫌弃JAVA自带的类库不好用,自行研发的一套工具包,对JDK工具做了很好的拓展。例如:并发[Concurrency]、缓存[Caches]、 函数式风格[Functional idioms]、 字符串处理[Strings]等等。 一、引入Guava包依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>21.0</version> </dependency> 二、自定义LocalLock注解 自定义一个LocalLock注解用于需要防止重复提交的方法上 /** * 锁的注解 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface LocalLock { /** * @author fly */ String key() default ""; } 注解定义好以后就需要做AOP拦截器切面的具体实现,在 interceptor() 方法上采用的是 Around(环绕增强) ,所有带 LocalLock 注解的都将被切面处理; 既然是缓存,那紧跟的属性一定要有过期时间,通过expireAfterWrite 设置缓存的过期时间,maximumSize设置缓存的个数。 通过在内存中查询key是否存在来判断是否让再次提交,和Redis的setNX方法是一个原理。 那么这个注解该怎么用呢? @Aspect @Configuration public class LockMethodInterceptor { private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder() // 最大缓存 100 个 .maximumSize(1000) // 设置写缓存后 5 秒钟过期 .expireAfterWrite(5, TimeUnit.SECONDS) .build(); @Around("execution(public * *(..)) && @annotation(com.battcn.annotation.LocalLock)") public Object interceptor(ProceedingJoinPoint pjp) { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); LocalLock localLock = method.getAnnotation(LocalLock.class); String key = getKey(localLock.key(), pjp.getArgs()); if (!StringUtils.isEmpty(key)) { if (CACHES.getIfPresent(key) != null) { throw new RuntimeException("请勿重复请求"); } // 如果是第一次请求,就将 key 当前对象压入缓存中 CACHES.put(key, key); } try { return pjp.proceed(); } catch (Throwable throwable) { throw new RuntimeException("服务器异常"); } finally { // TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了 } } /** * key 的生成策略,如果想灵活可以写成接口与实现类的方式(TODO 后续讲解) * * @param keyExpress 表达式 * @param args 参数 * @return 生成的key */ private String getKey(String keyExpress, Object[] args) { for (int i = 0; i < args.length; i++) { keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString()); } return keyExpress; } } 控制层的实现 我们将注解加在控制层方法上,key = "city:arg[0] key自己定义,arg[0]这个匹配规则表示替换成第一个参数。那么就实现city:token在一定时间内不可以重复提交了 @RestController @RequestMapping("/city") public class BookController { @LocalLock(key = "city:arg[0]") @GetMapping public String query(@RequestParam String token) { return "ok- " + token; } } 测试 接下来我们就测试一下,我用的是postman 第一请求正常响应 紧接着请求第二次,返回结果“重复提交”,显然我们实现成功了 整理了几百本各类技术电子书,送给小伙伴们。关注公号回复【666】自行领取。和一些小伙伴们建了一个技术交流群,一起探讨技术、分享技术资料,旨在共同学习进步,如果感兴趣就加入我们吧! 无论你是刚入行、还是已经有几年经验的程序员,相信这份面试提纲都会给你不少助力,长按二维码关注 『 程序员内点事 』 ,回复 『 offer 』 自行领取,祝大家 offer 拿到手软

优秀的个人博客,低调大师

Springboot过滤器和拦截器详解及使用场景

一、过滤器和拦截器的区别 1、过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。 2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。 3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射 4、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。 5、Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理(反射)的方式来执行。 6、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。 过滤器和拦截器非常相似,但是它们有很大的区别 最简单明了的区别就是**过滤器可以修改request,而拦截器不能 过滤器需要在servlet容器中实现,拦截器可以适用于javaEE,javaSE等各种环境 拦截器可以调用IOC容器中的各种依赖,而过滤器不能 过滤器只能在请求的前后使用,而拦截器可以详细到每个方法** 区别很多,大家可以去查下 总的来说 过滤器就是筛选出你要的东西,比如requeset中你要的那部分 拦截器在做安全方面用的比较多,比如终止一些流程 网上有一张图片很不错,这里拷过来给大家看一下 过滤器(Filter) :可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息。 拦截器(Interceptor):可以拿到你请求的控制器和方法,却拿不到请求方法的参数。 切片(Aspect): 可以拿到方法的参数,但是却拿不到http请求和响应的对象 二、过滤器 两种方式: 1、使用spring boot提供的FilterRegistrationBean注册Filter 2、使用原生servlet注解定义Filter 两种方式的本质都是一样的,都是去FilterRegistrationBean注册自定义Filter 方式一: (使用spring boot提供的FilterRegistrationBean注册Filter ) ①、先定义Filter: package com.corwien.filter; import javax.servlet.*; import java.io.IOException; public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // do something 处理request 或response // doFilter()方法中的servletRequest参数的类型是ServletRequest,需要转换为HttpServletRequest类型方便调用某些方法 System.out.println("filter1"); // 调用filter链中的下一个filter HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String ip = request.getRemoteAddr(); String url = request.getRequestURL().toString(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = new Date(); String date = sdf.format(d); System.out.printf("%s %s 访问了 %s%n", date, ip, url); filterChain.doFilter(request, response); } @Override public void destroy() { } } ②、注册自定义Filter @Configuration public class FilterConfig { @Bean public FilterRegistrationBean registrationBean() { ** FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new** **MyFilter());** filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; } } 方式一的①②步骤可以用下面这段代码代替: @Configuration public class FilterConfig { @Bean public **FilterRegistrationBean** registFilter() { **FilterRegistrationBean registration** **= new FilterRegistrationBean(); registration.setFilter(new** **LogCostFilter());** registration.addUrlPatterns("/*"); registration.setName("LogCostFilter"); registration.setOrder(1); return registration; } } public class LogCostFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { long start = System.currentTimeMillis(); filterChain.doFilter(servletRequest,servletResponse); System.out.println("Execute cost="+(System.currentTimeMillis()-start)); } @Override public void destroy() { } 方式二:(使用原生servlet注解定义Filter) // 注入spring容器 @Component // 定义filterName 和过滤的url @WebFilter(filterName = "my2Filter" ,urlPatterns = "/*") public class My2Filter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("filter2"); } @Override public void destroy() { } } 这里直接用@WebFilter就可以进行配置,同样,可以设置url匹配模式,过滤器名称等。这里需要注意一点的是@WebFilter这个注解是Servlet3.0的规范,并不是Spring boot提供的。除了这个注解以外,我们还需在启动类中加另外一个注解:@ServletComponetScan,指定扫描的包。 三、拦截器的配置 实现拦截器可以通过继承HandlerInterceptorAdapter类也可以通过实现HandlerInterceptor这个接口。另外,如果preHandle方法return true,则继续后续处理。 首先我们实现拦截器类: public class LogCostInterceptor implements HandlerInterceptor { long start = System.currentTimeMillis(); @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { start = System.currentTimeMillis(); return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("Interceptor cost="+(System.currentTimeMillis()-start)); } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } } 我们还需要实现HandlerInterceptor这个接口,这个接口包括三个方法,preHandle是请求执行前执行的,postHandler是请求结束执行的,但只有preHandle方法返回true的时候才会执行,afterCompletion是视图渲染完成后才执行,同样需要preHandle返回true,该方法通常用于清理资源等工作。除了实现上面的接口外,我们还需对其进行配置: @Configuration public class InterceptorConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogCostInterceptor()).addPathPatterns("/**"); super.addInterceptors(registry); } } 这里我们继承了WebMVCConfigurerAdapter,这里我们重写了addInterceptors这个方法,进行拦截器的配置,主要配置项就两个,一个是指定拦截器,第二个是指定拦截的URL。 坑坑坑: 拦截器不生效常见问题: 1)是否有加@Configuration 2)拦截路径是否有问题**和* 3)拦截器最后路径一定要 “/**”, 如果是目录的话则是/*/ 总结一下:创建拦截器需要两步: 1、自定义拦截器 2、注册拦截器 四、应用场景 拦截器是在DispatcherServlet这个servlet中执行的,因此所有的请求最先进入Filter,最后离开Filter。其顺序如下。 Filter->Interceptor.preHandle->Handler->Interceptor.postHandle->Interceptor.afterCompletion->Filter 拦截器应用场景 拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括: 登录验证,判断用户是否登录。 权限验证,判断用户是否有权限访问资源,如校验token 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量。 处理cookie、本地化、国际化、主题等。 性能监控,监控请求处理时长等。 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现) 过滤器应用场景 1)过滤敏感词汇(防止sql注入) 2)设置字符编码 3)URL级别的权限访问控制 4)压缩响应信息 看完三件事❤️ 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙: 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。 关注公众号 『java烂猪皮』,不定期分享原创知识。 同时可以期待后续文章ing🚀 作者:Corwien 出处: https://segmentfault.com/a/1190000037755221

优秀的个人博客,低调大师

springboot2.0+activiti 7 整合(三)--创建自己的业务流程

使用activiti框架,首先要创建bpmn流程图,这里有两种选择,一种是ide自带的插件(eclipse就不说了,网上各种说好用的;idea是真心难用,而且是好几年没更新的插件了),一种是用activiti官方提供的工具activiti-app(6.0的版本,我们只用它来画图),把activiti-app.war放在tomcat运行。 ## 1、绘制流程图 访问地址:http://localhost:8080/activiti-app 账号:admin 密码:test先创建一个流程 创建一个请假流程 流程图如下: 关键属性: 每个矩形框就是一个userTask,最关键的属性:Assignments 任务指定人因为没有使用activiti的identity(认证)部分,所有我选择动态传入一个参数进去(动态变量名不能重复): 每个连接线就是一个sequenceFlow,最关键的属性是:Flow condition 顺序流条件;可以设置条件表达式;这里分支的变量名必须一致,比如审核通过为“${audit==1”},审核不通过为${audit==1”} 2、导入流程图 将画好的流程图下载到本地,在processes目录下新建文件(leave.bpmn20.xml),将流程图用文本打开复制进xml中: <?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef"> <process id="leave" name="请假流程" isExecutable="true"> <startEvent id="startEvent1" name="开始"></startEvent> <userTask id="sid-E9DA7720-703C-4AA1-9233-5B4977C4D7FA" name="填写申请" activiti:assignee="${user}"><!--activiti:assignee 任务指定人--> <extensionElements> <modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete> </extensionElements> </userTask> <sequenceFlow id="sid-782B5EB0-C3E4-4EA8-B47C-001493E8CFAA" sourceRef="startEvent1" targetRef="sid-E9DA7720-703C-4AA1-9233-5B4977C4D7FA"></sequenceFlow> <userTask id="sid-F30892E7-AD40-44CE-8FE1-911564290536" name="领导批准" activiti:assignee="${approve}"> <extensionElements> <modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete> </extensionElements> </userTask> <sequenceFlow id="sid-9BAB7E50-7E04-4C87-8448-ECDF79CB4405" sourceRef="sid-E9DA7720-703C-4AA1-9233-5B4977C4D7FA" targetRef="sid-F30892E7-AD40-44CE-8FE1-911564290536"></sequenceFlow> <exclusiveGateway id="sid-C7396408-A226-4385-9E87-C63DA1295BEE"></exclusiveGateway> <sequenceFlow id="sid-4570FB2C-F000-4EB9-961B-795DFD3CD037" sourceRef="sid-F30892E7-AD40-44CE-8FE1-911564290536" targetRef="sid-C7396408-A226-4385-9E87-C63DA1295BEE"></sequenceFlow> <endEvent id="sid-E8EA0FBB-EAE1-4316-BAAE-1465DD35D4EA" name="结束"></endEvent> <!--sequenceFlow 流程分支--> <sequenceFlow id="sid-715DC950-61C4-4AA8-9662-A6FD71611EAA" name="审核不通过" sourceRef="sid-C7396408-A226-4385-9E87-C63DA1295BEE" targetRef="sid-E9DA7720-703C-4AA1-9233-5B4977C4D7FA"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${audit==0}]]></conditionExpression> </sequenceFlow> <sequenceFlow id="sid-EA1D4887-5497-43DE-9D57-A03D3B719681" name="审核通过" sourceRef="sid-C7396408-A226-4385-9E87-C63DA1295BEE" targetRef="sid-E8EA0FBB-EAE1-4316-BAAE-1465DD35D4EA"> <conditionExpression xsi:type="tFormalExpression"><![CDATA[${audit==1}]]></conditionExpression> </sequenceFlow> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_leave"> <bpmndi:BPMNPlane bpmnElement="leave" id="BPMNPlane_leave"> <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1"> <omgdc:Bounds height="30.0" width="30.0" x="90.0" y="150.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-E9DA7720-703C-4AA1-9233-5B4977C4D7FA" id="BPMNShape_sid-E9DA7720-703C-4AA1-9233-5B4977C4D7FA"> <omgdc:Bounds height="80.0" width="100.0" x="165.0" y="125.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-F30892E7-AD40-44CE-8FE1-911564290536" id="BPMNShape_sid-F30892E7-AD40-44CE-8FE1-911564290536"> <omgdc:Bounds height="80.0" width="100.0" x="300.0" y="125.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-C7396408-A226-4385-9E87-C63DA1295BEE" id="BPMNShape_sid-C7396408-A226-4385-9E87-C63DA1295BEE"> <omgdc:Bounds height="40.0" width="40.0" x="450.0" y="145.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="sid-E8EA0FBB-EAE1-4316-BAAE-1465DD35D4EA" id="BPMNShape_sid-E8EA0FBB-EAE1-4316-BAAE-1465DD35D4EA"> <omgdc:Bounds height="28.0" width="28.0" x="540.0" y="151.0"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement="sid-782B5EB0-C3E4-4EA8-B47C-001493E8CFAA" id="BPMNEdge_sid-782B5EB0-C3E4-4EA8-B47C-001493E8CFAA"> <omgdi:waypoint x="120.0" y="165.0"></omgdi:waypoint> <omgdi:waypoint x="165.0" y="165.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-9BAB7E50-7E04-4C87-8448-ECDF79CB4405" id="BPMNEdge_sid-9BAB7E50-7E04-4C87-8448-ECDF79CB4405"> <omgdi:waypoint x="265.0" y="165.0"></omgdi:waypoint> <omgdi:waypoint x="300.0" y="165.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-4570FB2C-F000-4EB9-961B-795DFD3CD037" id="BPMNEdge_sid-4570FB2C-F000-4EB9-961B-795DFD3CD037"> <omgdi:waypoint x="400.0" y="165.20746887966806"></omgdi:waypoint> <omgdi:waypoint x="450.4166666666667" y="165.41666666666666"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-715DC950-61C4-4AA8-9662-A6FD71611EAA" id="BPMNEdge_sid-715DC950-61C4-4AA8-9662-A6FD71611EAA"> <omgdi:waypoint x="470.5" y="145.5"></omgdi:waypoint> <omgdi:waypoint x="470.5" y="26.0"></omgdi:waypoint> <omgdi:waypoint x="215.0" y="26.0"></omgdi:waypoint> <omgdi:waypoint x="215.0" y="125.0"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="sid-EA1D4887-5497-43DE-9D57-A03D3B719681" id="BPMNEdge_sid-EA1D4887-5497-43DE-9D57-A03D3B719681"> <omgdi:waypoint x="489.6144578313253" y="165.3855421686747"></omgdi:waypoint> <omgdi:waypoint x="540.0002509882663" y="165.0838308324056"></omgdi:waypoint> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram> </definitions> 3、配置application.yml spring: datasource: #数据源基本配置 username: root password: root url: jdbc:mysql://localhost/activiti?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&nullCatalogMeansCurrent=true&useSSL=false&useLegacyDatetimeCode=false driver-class-name: com.mysql.cj.jdbc.Driver activiti: # 开启历史库 db-history-used: true history-level: audit 历史信息级别可以配置成以下几种(activiti7感觉默认的是none): none: 忽略所有历史存档。这是流程执行时性能最好的状态,但没有任何历史信息可用。 activity: 保存所有流程实例信息和活动实例信息。 在流程实例结束时, 最后一个流程实例中的最新的变量值将赋值给历史变量。 不会保存过程中的详细信息。 audit: 它保存所有流程实例信息, 活动信息, 保证所有的变量和提交的表单属性保持同步 这样所有用户交互信息都是可追溯的,可以用来审计。 full: 这个是最高级别的历史信息存档,同样也是最慢的。 这个级别存储发生在审核以及所有其它细节的信息, 主要是更新流程变量。 4.开启一个任务 /** * 开启一个请假流程 * @param user 用户key * @param processDefinitionKey 流程图key 每一个流程有对应的一个key这个是某一个流程内固定的写在bpmn内的 */ void startLeaveProcess(String user,String processDefinitionKey){ System.out.println(user+"开启一个请假流程:"+ processDefinitionKey); HashMap<String, Object> variables=new HashMap<>(); variables.put("user", user);//userKey在上文的流程变量中指定了 ProcessInstance instance = runtimeService .startProcessInstanceByKey(processDefinitionKey,variables); System.out.println("流程实例ID:"+instance.getId()); System.out.println("流程定义ID:"+instance.getProcessDefinitionId()); System.out.println("=================================================================="); } 运行结果: 张三开启一个请假流程:leave 流程实例ID:34f2f038-0d07-11ea-b319-9c5c8e7034f6 流程定义ID:leave:1:32f7bc77-0d07-11ea-b319-9c5c8e7034f6 ================================================================== 注意数据库的变化: mysql> select ID_,REV_,PROC_INST_ID_,NAME_,ASSIGNEE_ from act_ru_task; +--------------------------------------+------+--------------------------------------+----------+-----------+ | ID_ | REV_ | PROC_INST_ID_ | NAME_ | ASSIGNEE_ | +--------------------------------------+------+--------------------------------------+----------+-----------+ | 34f8958d-0d07-11ea-b319-9c5c8e7034f6 | 1 | 34f2f038-0d07-11ea-b319-9c5c8e7034f6 | 填写申请 | 张三 | +--------------------------------------+------+--------------------------------------+----------+-----------+ 1 row in set 5、根据执行人查询当前流程 /** * 查询当前任务流程 */ void queryLeaveProcessING(String assignee){ System.out.println(assignee+"查询自己当前的流程:"); List<Task> list = taskService.createTaskQuery()//创建任务查询对象 .taskAssignee(assignee)//指定个人任务查询 .list(); if(list!=null && list.size()>0){ for(Task task:list){ System.out.println("任务ID:"+task.getId()); System.out.println("任务名称:"+task.getName()); System.out.println("任务的创建时间:"+task.getCreateTime()); System.out.println("任务的办理人:"+task.getAssignee()); System.out.println("流程实例ID:"+task.getProcessInstanceId()); System.out.println("执行对象ID:"+task.getExecutionId()); System.out.println("流程定义ID:"+task.getProcessDefinitionId()); Map<String, Object> map = task.getProcessVariables(); for (Map.Entry<String, Object> m : map.entrySet()) { System.out.println("key:" + m.getKey() + " value:" + m.getValue()); } for (Map.Entry<String, Object> m : task.getTaskLocalVariables().entrySet()) { System.out.println("key:" + m.getKey() + " value:" + m.getValue()); } } } System.out.println("=================================================================="); } 运行结果: 张三查询自己当前的流程: 任务ID:34f8958d-0d07-11ea-b319-9c5c8e7034f6 任务名称:填写申请 任务的创建时间:Fri Nov 22 17:05:19 CST 2019 任务的办理人:张三 流程实例ID:34f2f038-0d07-11ea-b319-9c5c8e7034f6 执行对象ID:34f3b38a-0d07-11ea-b319-9c5c8e7034f6 流程定义ID:leave:1:32f7bc77-0d07-11ea-b319-9c5c8e7034f6 ================================================================== 6、提交给领导审核 void completeTask(String approve,String taskId){ System.out.println(approve+":提交自己的流程:"+taskId); //任务ID HashMap<String, Object> variables=new HashMap<>(); variables.put("approve", approve);//userKey在上文的流程变量中指定了 taskService.complete(taskId,variables); System.out.println("完成任务:任务ID:"+taskId); System.out.println("=================================================================="); } 运行结果: 领导李四:提交自己的流程:34f8958d-0d07-11ea-b319-9c5c8e7034f6 完成任务:任务ID:34f8958d-0d07-11ea-b319-9c5c8e7034f6 ================================================================== 数据库: mysql> select ID_,REV_,PROC_INST_ID_,NAME_,ASSIGNEE_ from act_ru_task; +--------------------------------------+------+--------------------------------------+----------+-----------+ | ID_ | REV_ | PROC_INST_ID_ | NAME_ | ASSIGNEE_ | +--------------------------------------+------+--------------------------------------+----------+-----------+ | e60702be-0d08-11ea-8a0a-9c5c8e7034f6 | 1 | 34f2f038-0d07-11ea-b319-9c5c8e7034f6 | 领导批准 | 领导李四 | +--------------------------------------+------+--------------------------------------+----------+-----------+ 1 row in set 7、领导审核通过 void completeTask(String user,String taskId,int audit){ System.out.println(user+":提交自己的流程:"+taskId+" ;是否通过:"+audit); //任务ID HashMap<String, Object> variables=new HashMap<>(); variables.put("audit", audit);//userKey在上文的流程变量中指定了 taskService.complete(taskId,variables); System.out.println("完成任务:任务ID:"+taskId); System.out.println("=================================================================="); } 运行结果: 领导李四:提交自己的流程:e60702be-0d08-11ea-8a0a-9c5c8e7034f6 ;是否通过:1 完成任务:任务ID:e60702be-0d08-11ea-8a0a-9c5c8e7034f6 ================================================================== 数据库: mysql> select ID_,REV_,PROC_INST_ID_,NAME_,ASSIGNEE_ from act_ru_task; Empty set 8、查询历史数据 根据流程ID查询: List<HistoricTaskInstance> list=historyService // 历史相关Service .createHistoricTaskInstanceQuery() // 创建历史活动实例查询 .processInstanceId("34f2f038-0d07-11ea-b319-9c5c8e7034f6") // 执行流程实例id .orderByTaskCreateTime() .asc() .list(); for(HistoricTaskInstance hai:list){ System.out.println("活动ID:"+hai.getId()); System.out.println("流程实例ID:"+hai.getProcessInstanceId()); System.out.println("活动名称:"+hai.getName()); System.out.println("办理人:"+hai.getAssignee()); System.out.println("开始时间:"+hai.getStartTime()); System.out.println("结束时间:"+hai.getEndTime()); System.out.println("=================================================================="); } 运行结果: 活动ID:34f8958d-0d07-11ea-b319-9c5c8e7034f6 流程实例ID:34f2f038-0d07-11ea-b319-9c5c8e7034f6 活动名称:填写申请 办理人:张三 开始时间:Fri Nov 22 17:05:19 CST 2019 结束时间:Fri Nov 22 17:17:25 CST 2019 ================================================================== 活动ID:e60702be-0d08-11ea-8a0a-9c5c8e7034f6 流程实例ID:34f2f038-0d07-11ea-b319-9c5c8e7034f6 活动名称:领导批准 办理人:领导李四 开始时间:Fri Nov 22 17:17:25 CST 2019 结束时间:Fri Nov 22 17:31:03 CST 2019 ================================================================== 查询个人历史记录: List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().taskAssignee("张三").orderByTaskCreateTime().asc().list(); for(HistoricTaskInstance hai:list){ System.out.println("活动ID:"+hai.getId()); System.out.println("流程实例ID:"+hai.getProcessInstanceId()); System.out.println("活动名称:"+hai.getName()); System.out.println("办理人:"+hai.getAssignee()); System.out.println("开始时间:"+hai.getStartTime()); System.out.println("结束时间:"+hai.getEndTime()); System.out.println("=================================================================="); } 运行结果: 活动ID:34f8958d-0d07-11ea-b319-9c5c8e7034f6 流程实例ID:34f2f038-0d07-11ea-b319-9c5c8e7034f6 活动名称:填写申请 办理人:张三 开始时间:Fri Nov 22 17:05:19 CST 2019 结束时间:Fri Nov 22 17:17:25 CST 2019 ================================================================== 数据库: mysql> select ID_,PROC_INST_ID_,NAME_,ASSIGNEE_ from act_hi_taskinst; +--------------------------------------+--------------------------------------+----------+-----------+ | ID_ | PROC_INST_ID_ | NAME_ | ASSIGNEE_ | +--------------------------------------+--------------------------------------+----------+-----------+ | 34f8958d-0d07-11ea-b319-9c5c8e7034f6 | 34f2f038-0d07-11ea-b319-9c5c8e7034f6 | 填写申请 | 张三 | | e60702be-0d08-11ea-8a0a-9c5c8e7034f6 | 34f2f038-0d07-11ea-b319-9c5c8e7034f6 | 领导批准 | 领导李四 | +--------------------------------------+--------------------------------------+----------+-----------+ 2 rows in set 9.完整的测试类代码 package com.example.activitidemo2; import org.activiti.engine.HistoryService; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.engine.history.HistoricTaskInstance; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; import java.util.HashMap; import java.util.List; import java.util.Map; @SpringBootTest class ActivitiDemo2ApplicationTests { @Resource RepositoryService repositoryService; @Resource RuntimeService runtimeService; @Resource TaskService taskService; @Resource HistoryService historyService; @Test void contextLoads() { System.out.println("Number of process definitions : " + repositoryService.createProcessDefinitionQuery().count()); System.out.println("Number of tasks : " + taskService.createTaskQuery().count()); runtimeService.startProcessInstanceByKey("oneTaskProcess"); System.out.println("Number of tasks after process start: " + taskService.createTaskQuery().count()); } @Test void testProcess(){ //张三开启一个请假流程 String user = "张三"; String approve = "领导李四"; // startLeaveProcess(user,"leave"); //张三查询自己流程 // queryLeaveProcessING(user); // 提交给领导李四审核 // String taskId = "34f8958d-0d07-11ea-b319-9c5c8e7034f6"; // completeTask(approve,taskId); //领导李四查询自己的流程 // queryLeaveProcessING(approve); //李四提交自己的流程 completeTask(approve,"e60702be-0d08-11ea-8a0a-9c5c8e7034f6",1); //张三查询自己的历史流程 // queryHistoryTask(userKey); } /** * 开启一个请假流程 * @param user 用户key * @param processDefinitionKey 流程图key 每一个流程有对应的一个key这个是某一个流程内固定的写在bpmn内的 */ void startLeaveProcess(String user,String processDefinitionKey){ System.out.println(user+"开启一个请假流程:"+ processDefinitionKey); HashMap<String, Object> variables=new HashMap<>(); variables.put("user", user);//userKey在上文的流程变量中指定了 ProcessInstance instance = runtimeService .startProcessInstanceByKey(processDefinitionKey,variables); System.out.println("流程实例ID:"+instance.getId()); System.out.println("流程定义ID:"+instance.getProcessDefinitionId()); System.out.println("=================================================================="); } /** * 查询当前任务流程 */ void queryLeaveProcessING(String assignee){ System.out.println(assignee+"查询自己当前的流程:"); List<Task> list = taskService.createTaskQuery()//创建任务查询对象 .taskAssignee(assignee)//指定个人任务查询 .list(); if(list!=null && list.size()>0){ for(Task task:list){ System.out.println("任务ID:"+task.getId()); System.out.println("任务名称:"+task.getName()); System.out.println("任务的创建时间:"+task.getCreateTime()); System.out.println("任务的办理人:"+task.getAssignee()); System.out.println("流程实例ID:"+task.getProcessInstanceId()); System.out.println("执行对象ID:"+task.getExecutionId()); System.out.println("流程定义ID:"+task.getProcessDefinitionId()); Map<String, Object> map = task.getProcessVariables(); for (Map.Entry<String, Object> m : map.entrySet()) { System.out.println("key:" + m.getKey() + " value:" + m.getValue()); } for (Map.Entry<String, Object> m : task.getTaskLocalVariables().entrySet()) { System.out.println("key:" + m.getKey() + " value:" + m.getValue()); } } } System.out.println("=================================================================="); } @Test void completeTask(String approve,String taskId){ System.out.println(approve+":提交自己的流程:"+taskId); //任务ID HashMap<String, Object> variables=new HashMap<>(); variables.put("approve", approve);//userKey在上文的流程变量中指定了 taskService.complete(taskId,variables); System.out.println("完成任务:任务ID:"+taskId); System.out.println("=================================================================="); } @Test void completeTask(String user,String taskId,int audit){ System.out.println(user+":提交自己的流程:"+taskId+" ;是否通过:"+audit); //任务ID HashMap<String, Object> variables=new HashMap<>(); variables.put("audit", audit);//userKey在上文的流程变量中指定了 taskService.complete(taskId,variables); System.out.println("完成任务:任务ID:"+taskId); System.out.println("=================================================================="); } @Test void queryHistoryTask(){ List<HistoricTaskInstance> list=historyService // 历史相关Service .createHistoricTaskInstanceQuery() // 创建历史活动实例查询 .processInstanceId("34f2f038-0d07-11ea-b319-9c5c8e7034f6") // 执行流程实例id .orderByTaskCreateTime() .asc() .list(); for(HistoricTaskInstance hai:list){ System.out.println("活动ID:"+hai.getId()); System.out.println("流程实例ID:"+hai.getProcessInstanceId()); System.out.println("活动名称:"+hai.getName()); System.out.println("办理人:"+hai.getAssignee()); System.out.println("开始时间:"+hai.getStartTime()); System.out.println("结束时间:"+hai.getEndTime()); System.out.println("=================================================================="); } // List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().taskAssignee("张三").orderByTaskCreateTime().asc().list(); // for(HistoricTaskInstance hai:list){ // System.out.println("活动ID:"+hai.getId()); // System.out.println("流程实例ID:"+hai.getProcessInstanceId()); // System.out.println("活动名称:"+hai.getName()); // System.out.println("办理人:"+hai.getAssignee()); // System.out.println("开始时间:"+hai.getStartTime()); // System.out.println("结束时间:"+hai.getEndTime()); // System.out.println("=================================================================="); // } } }

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册