首页 文章 精选 留言 我的

精选列表

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

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("=================================================================="); // } } }

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

SpringBoot2.0高级案例(02) :整合 RocketMQ ,实现请求异步处理

本文源码:GitHub·点这里 || GitEE·点这里 一、RocketMQ 1、架构图片 2、角色分类 (1)、Broker RocketMQ 的核心,接收 Producer 发过来的消息、处理 Consumer 的消费消息请求、消息的持 久化存储、服务端过滤功能等 。 (2)、NameServer 消息队列中的状态服务器,集群的各个组件通过它来了解全局的信息 。类似微服务中注册中心的服务注册,发现,下线,上线的概念。 热备份: NamServer可以部署多个,相互之间独立,其他角色同时向多个NameServer 机器上报状态信息。 心跳机制: NameServer 中的 Broker、 Topic等状态信息不会持久存储,都是由各个角色定时上报并存储到内存中,超时不上报的话, NameServer会认为某个机器出故障不可用。 (3)、Producer 消息的生成者,最常用的producer类就是DefaultMQProducer。 (4)、Consumer 消息的消费者,常用Consumer类 DefaultMQPushConsumer 收到消息后自动调用传入的处理方法来处理,实时性高 DefaultMQPullConsumer 用户自主控制 ,灵活性更高。 3、通信机制 (1)、Broker启动后需要完成一次将自己注册至NameServer的操作;随后每隔30s时间定时向NameServer更新Topic路由信息。 (2)、Producer发送消息时候,需要根据消息的Topic从本地缓存的获取路由信息。如果没有则更新路由信息会从NameServer重新拉取,同时Producer会默认每隔30s向NameServer拉取一次路由信息。 (3)、Consumer消费消息时候,从NameServer获取的路由信息,并再完成客户端的负载均衡后,监听指定消息队列获取消息并进行消费。 二、代码实现案例 1、项目结构图 版本描述 <spring-boot.version>2.1.3.RELEASE</spring-boot.version> <rocketmq.version>4.3.0</rocketmq.version> 2、配置文件 rocketmq: # 生产者配置 producer: isOnOff: on # 发送同一类消息的设置为同一个group,保证唯一 groupName: CicadaGroup # 服务地址 namesrvAddr: 127.0.0.1:9876 # 消息最大长度 默认1024*4(4M) maxMessageSize: 4096 # 发送消息超时时间,默认3000 sendMsgTimeout: 3000 # 发送消息失败重试次数,默认2 retryTimesWhenSendFailed: 2 # 消费者配置 consumer: isOnOff: on # 官方建议:确保同一组中的每个消费者订阅相同的主题。 groupName: CicadaGroup # 服务地址 namesrvAddr: 127.0.0.1:9876 # 接收该 Topic 下所有 Tag topics: CicadaTopic~*; consumeThreadMin: 20 consumeThreadMax: 64 # 设置一次消费消息的条数,默认为1条 consumeMessageBatchMaxSize: 1 # 配置 Group Topic Tag rocket: group: rocketGroup topic: rocketTopic tag: rocketTag 3、生产者配置 /** * RocketMQ 生产者配置 */ @Configuration public class ProducerConfig { private static final Logger LOG = LoggerFactory.getLogger(ProducerConfig.class) ; @Value("${rocketmq.producer.groupName}") private String groupName; @Value("${rocketmq.producer.namesrvAddr}") private String namesrvAddr; @Value("${rocketmq.producer.maxMessageSize}") private Integer maxMessageSize ; @Value("${rocketmq.producer.sendMsgTimeout}") private Integer sendMsgTimeout; @Value("${rocketmq.producer.retryTimesWhenSendFailed}") private Integer retryTimesWhenSendFailed; @Bean public DefaultMQProducer getRocketMQProducer() { DefaultMQProducer producer; producer = new DefaultMQProducer(this.groupName); producer.setNamesrvAddr(this.namesrvAddr); //如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName if(this.maxMessageSize!=null){ producer.setMaxMessageSize(this.maxMessageSize); } if(this.sendMsgTimeout!=null){ producer.setSendMsgTimeout(this.sendMsgTimeout); } //如果发送消息失败,设置重试次数,默认为2次 if(this.retryTimesWhenSendFailed!=null){ producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed); } try { producer.start(); } catch (MQClientException e) { e.printStackTrace(); } return producer; } } 4、消费者配置 /** * RocketMQ 消费者配置 */ @Configuration public class ConsumerConfig { private static final Logger LOG = LoggerFactory.getLogger(ConsumerConfig.class) ; @Value("${rocketmq.consumer.namesrvAddr}") private String namesrvAddr; @Value("${rocketmq.consumer.groupName}") private String groupName; @Value("${rocketmq.consumer.consumeThreadMin}") private int consumeThreadMin; @Value("${rocketmq.consumer.consumeThreadMax}") private int consumeThreadMax; @Value("${rocketmq.consumer.topics}") private String topics; @Value("${rocketmq.consumer.consumeMessageBatchMaxSize}") private int consumeMessageBatchMaxSize; @Resource private RocketMsgListener msgListener; @Bean public DefaultMQPushConsumer getRocketMQConsumer(){ DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName); consumer.setNamesrvAddr(namesrvAddr); consumer.setConsumeThreadMin(consumeThreadMin); consumer.setConsumeThreadMax(consumeThreadMax); consumer.registerMessageListener(msgListener); consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize); try { String[] topicTagsArr = topics.split(";"); for (String topicTags : topicTagsArr) { String[] topicTag = topicTags.split("~"); consumer.subscribe(topicTag[0],topicTag[1]); } consumer.start(); }catch (MQClientException e){ e.printStackTrace(); } return consumer; } } 5、消息监听配置 /** * 消息消费监听 */ @Component public class RocketMsgListener implements MessageListenerConcurrently { private static final Logger LOG = LoggerFactory.getLogger(RocketMsgListener.class) ; @Resource private ParamConfigService paramConfigService ; @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext context) { if (CollectionUtils.isEmpty(list)){ return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } MessageExt messageExt = list.get(0); LOG.info("接受到的消息为:"+new String(messageExt.getBody())); int reConsume = messageExt.getReconsumeTimes(); // 消息已经重试了3次,如果不需要再次消费,则返回成功 if(reConsume ==3){ return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } if(messageExt.getTopic().equals(paramConfigService.rocketTopic)){ String tags = messageExt.getTags() ; switch (tags){ case "rocketTag": LOG.info("开户 tag == >>"+tags); break ; default: LOG.info("未匹配到Tag == >>"+tags); break; } } // 消息消费成功 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } } 6、配置参数绑定 @Service public class ParamConfigService { @Value("${rocket.group}") public String rocketGroup ; @Value("${rocket.topic}") public String rocketTopic ; @Value("${rocket.tag}") public String rocketTag ; } 7、消息发送测试 @Service public class RocketMqServiceImpl implements RocketMqService { @Resource private DefaultMQProducer defaultMQProducer; @Resource private ParamConfigService paramConfigService ; @Override public SendResult openAccountMsg(String msgInfo) { // 可以不使用Config中的Group defaultMQProducer.setProducerGroup(paramConfigService.rocketGroup); SendResult sendResult = null; try { Message sendMsg = new Message(paramConfigService.rocketTopic, paramConfigService.rocketTag, "open_account_key", msgInfo.getBytes()); sendResult = defaultMQProducer.send(sendMsg); } catch (Exception e) { e.printStackTrace(); } return sendResult ; } } 三、项目源码 GitHub·地址 https://github.com/cicadasmile/middle-ware-parent GitEE·地址 https://gitee.com/cicadasmile/middle-ware-parent

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

企业级 SpringBoot 教程-sprinboot整合elk,搭建实时日志平台

elk 简介 Elasticsearch是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。 Logstash是一个完全开源的工具,他可以对你的日志进行收集、过滤,并将其存储供以后使用(如,搜索)。 Kibana 也是一个开源和免费的工具,它Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助您汇总、分析和搜索重要数据日志。 elk下载安装 elk下载地址:https: //www. elastic. co/downloads/ 建议在 linux上运行,elk在windows上支持得不好,另外需要jdk1.8 的支持,需要提前安装好jdk. 下载完之后: 安装,以logstash为例子: cd /usr/local/ mkdir logstash tar -zxvf logstash-5.3.2.tar.gz mv logstash-5.3.2 /usr/local/logstash 配置、启动 Elasticsearch 打开Elasticsearch的配置文件: vim config/elasticsearch.yml 修改配置: network.host=localhost network.port=9200 它默认就是这个配置,没有特殊要求,在本地不需要修改。 启动Elasticsearch ./bin/elasticsearch 启动成功,访问localhost:9200,网页显示: { "name" : "56IrTCM", "cluster_name" : "elasticsearch", "cluster_uuid" : "e4ja7vS2TIKI1BsggEAa6Q", "version" : { "number" : "5.2.2", "build_hash" : "f9d9b74", "build_date" : "2017-02-24T17:26:45.835Z", "build_snapshot" : false, "lucene_version" : "6.4.1" }, "tagline" : "You Know, for Search" } 配置、启动 logstash 在 logstash的主目录下: vim config/log4j_to_es.conf 修改 log4j_to_es.conf 如下: input { log4j { mode => "server" host => "localhost" port => 4560 } } filter { #Only matched data are send to output. } output { elasticsearch { action => "index" #The operation on ES hosts => "localhost:9200" #ElasticSearch host, can be array. index => "applog" #The index to write data to. } }Spring Cloud大型企业分布式微服务云架构源码一七九一七四三三八零哦 修改完配置后启动: ./bin/logstash -f config/log4j_to_es.conf

资源下载

更多资源
优质分享Android(本站安卓app)

优质分享Android(本站安卓app)

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Oracle Database,又名Oracle RDBMS

Oracle Database,又名Oracle RDBMS

Oracle Database,又名Oracle RDBMS,或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是目前世界上流行的关系数据库管理系统,系统可移植性好、使用方便、功能强,适用于各类大、中、小、微机环境。它是一种高效率、可靠性好的、适应高吞吐量的数据库方案。

Apache Tomcat7、8、9(Java Web服务器)

Apache Tomcat7、8、9(Java Web服务器)

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

Java Development Kit(Java开发工具)

Java Development Kit(Java开发工具)

JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。