首页 文章 精选 留言 我的

精选列表

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

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

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

SpringCloud SpringBoot mybatis分布式微服务云架构开发Web应用

静态资源访问在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。 默认配置Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则: /static/public/resources/META-INF/resources举例:我们可以在src/main/resources/目录下创建static,在该位置放置一个图片文件。启动程序后,尝试访问http://localhost:8080/D.jpg。如能显示图片,配置成功。 渲染Web页面在之前的示例中,我们都是通过@RestController来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢? 模板引擎在动态HTML实现上Spring Boot依然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板引擎下,我们可以很快的上手开发动态网站。 Spring Boot提供了默认配置的模板引擎主要有以下几种: ThymeleafFreeMarkerVelocityGroovyMustacheSpring Boot建议使用这些模板引擎,避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性,具体可见后文:支持JSP的配置 当你使用上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在后续各模板引擎的配置属性中查询并修改。 Thymeleaf Thymeleaf是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它是一个开源的Java库,基于Apache License 2.0许可,由Daniel Fernández创建,该作者还是Java加密库Jasypt的作者。 Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可以使用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。 示例模板: <table> <thead> <tr> <th th:text="#{msgs.headers.name}">Name</td> <th th:text="#{msgs.headers.price}">Price</td> </tr> </thead> <tbody> <tr th:each="prod : ${allProducts}"> <td th:text="${prod.name}">Oranges</td> <td th:text="${#numbers.formatDecimal(prod.price,1,2)}">0.99</td> </tr> </tbody> </table> 可以看到Thymeleaf主要以属性的方式加入到html标签中,浏览器在解析html时,当检查到没有的属性时候会忽略,所以Thymeleaf的模板可以通过浏览器直接打开展现,这样非常有利于前后端的分离。 在Spring Boot中使用Thymeleaf,只需要引入下面依赖,并在默认的模板路径src/main/resources/templates下编写模板文件即可完成。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> 完整项目的源码来源 技术支持1791743380

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

SpringCloud Finchley 实战入门(基于springBoot 2.0.3)【八 config 微服务配置中心】

spring cloud config spring cloud config是spring cloud团队创建的一个全新的项目,用来为分布式系统中的基础设施和微服务应用提供集中化的配置支持。他分为服务端和客户端两部分。其中服务端也称为分布式式配置中心,它是一个独立的微服务应用,用来连接配置仓库的并为客户端提供获取配置信息,加密、解密等访问接口。而客户端则是微服务架构中的各个微服务应用和基础设施。 入门实例 这里我们会创建一个基于Git存储的分布式配置中心,并且在客户端演示如何通过配置指定的微服务应用所属的配置中心,以及让其能够通过配置中心获取配置信息并绑定到代码中。 说先我们在github上面先创建一个配置文件存储的仓库"config-repo",创建完成后新建spring_cloud_in_action/config-repo的目录。 在config-repo目录下面新建"didispace.properties"、 "didispace-dev.properties"、"didispace-prod.properties"、"didispace-test.properties"; 然后在这4个文件中均设置一个from属性,并且为每个配置文件设置不同的值: from=git-default-2.0 from=git-dev-2.0 from=git-prod-2.0 from=git-test-2.0 1532672579949.png 对应对应github上面的目录: 1532672654269.png 到这里,我们配置文件的仓库就已经配置好了。我们继续回到spring cloud的项目中; 建一个spring boot的module,并且命名为"";创建的过程中,我们选择eureka-server和config的组件依赖。 1532672208369.png pom.xml如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>eureka-config-git</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eureka-config-git</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> application.yml配置: spring: application: name: config-server-git cloud: config: server: git: uri: https://github.com/ChinPangLung/config-repo #git仓库的地址;file://${user.name}/config-repo这是本地文件路径配置的方式,只限于用于开发使用 searchPaths: spring_cloud_in_action/config-repo username: XXXXXXX #这里对应仓库地址的账号密码 password: XXXXXXX discovery: enabled: true server: port: 7001 eureka: client: serviceUrl: defaultZone: http://peer1:8762/eureka/,http://peer2:8763/eureka/ spring.cloud.config.server.git.uri 表示我们上面再GitHub上创建的config-repo仓库地址 spring.cloud.config.server.git.searchPaths 表示配置文件存放查询的地址 spring.cloud.config.server.git.username 表示我们的github的账号 (大家修改成自己对应的账号密码就行了) spring.cloud.config.server.git.password 表示我们github的密码 项目的主类添加@EnableConfigServer注解 @SpringBootApplication @EnableConfigServer public class EurekaConfigGitApplication { public static void main(String[] args) { SpringApplication.run(EurekaConfigGitApplication.class, args); } } 到这里我们的配置服务中心就已经基本的搭建完成,在eureka注册中心,启动的情况下,我们运行该项目,如果没有报错,那就说明我们的配置是没有问题的。 完成了这些准备工作之后,我们就可以通过浏览器、POSTMAN或CURL等工具直接来访问到我们的配置内容了。访问配置信息的URL与配置文件的映射关系如下: /{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties 上面的url会映射{application}-{profile}.properties对应的配置文件,其中{label}对应Git上不同的分支,默认为master。我们可以尝试构造不同的url来访问不同的配置内容,比如,要访问master分支,didisapce应用的dev环境,就可以访问这个url:http://localhost:7001/didispace/dev/master 可以得到以下的返回: 1532674051553.png 完成了上述的验证后,我们就可以创建一个客户端来映射服务配置中心的配置了。 创建一个spring boot的module,命名为"eureka-config-client" 1532674210897.png <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>eureka-config-client</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eureka-config-client</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> application.yml配置: spring: application: name: config-client server: port: 7002 创建一个"bootstrap.yml"文件,配置如下: spring: cloud: config: profile: dev #dev开发环境配置文件,test测试环境配置文件,pro正式环境文件 label: master #git仓库对应的分支名称 config-label-test # uri: http://localhost:7001/ #config-server name: didispace discovery: serviceId: config-server-git #基于服务注册发现的方式查找 enabled: true eureka: client: serviceUrl: defaultZone: http://peer1:8762/eureka/,http://peer2:8763/eureka/ #注册中心eureka.client.service-url.defaultZone = 一定要放在boostrap文件中,不然启动会报错 在项目的中类中写一个from接口: @SpringBootApplication @RestController @RefreshScope public class EurekaConfigClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaConfigClientApplication.class, args); } @Value("${from}") private String from; @RequestMapping(value = "from") public String getFrom() { return this.from; } } 启动项目,然后访问http://localhost:7002/from可以得到以下的返回: 1532674897020.png 这个数据就是config-client项目获取服务配置中心里对应指向的GitHub仓库配置文件的值。 而且我们观察"eureka-config-git"项目控制台的输出 1532675044524.png 我们可以知道,服务配置中心在获取GitHub上面的配置文件返回config-client时,同时会缓存一分数据在本地的。这样就算因为其他的问题导致配置服务中心获取不到GitHub仓库的数据时,config-client也能有缓存的数据返回。 这样我们就完成了分布式配置中心的搭建了。其实上面的config-client我们在实际的开发中,就是相当于一个具体的微服务实例应用了。 github 项目源码 下面一篇我们将搭建spring cloud bus 服务总线。

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

springCloud Finchley 实战入门(基于springBoot 2.0.3)【七 Fegin 声明式服务调用】

Fegin 介绍 spring cloud fegin ,他是基于Netflix Fegin实现得,整合了spring cloud Ribbon和spring cloud Hystrix,除了整合这两者得功能外,他还提供了了一种声明式得Web服务客户端定义方式。 快速入门 创建一个spring boot项目命名为"eureka-bussniss-service-user-client-fegin",选择server和fegin的依赖; 1532587428278.png application.yml相关配置: spring: application: name: service-user-client-fegin server: port: 9001 eureka: client: serviceUrl: defaultZone: http://peer1:8762/eureka/,http://peer2:8763/eureka/ feign: hystrix: enabled: true 项目主类EurekaBussnissServiceUserClientFeginApplication,添加@EnableEurekaClient和@EnableFeignClients注解 @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class EurekaBussnissServiceUserClientFeginApplication { public static void main(String[] args) { SpringApplication.run(EurekaBussnissServiceUserClientFeginApplication.class, args); } } 我们以eureka-bussniss-service-user的服务例。在fegin项目中定义一个接口"UserFeignClient" /** * 标明feign调用的微服务名称 */ //通过@ FeignClient(“服务名”),来指定调用哪个服务。 // 比如在代码中调用了service-hi服务的“/hi”接口,还可以使用url参数指定一个URL // fallback 出现错误回调类 @FeignClient(name = "service-user",fallback = UserFallback.class) public interface UserFeignClient { /** * 对应service-user微服务中的URL * @return */ @GetMapping("/listUsers") String listUsers(); } 接着,创建一个Controller来实现对Fegin客户端的调用,使用@Autowired自动注入上面的“UserFeignClient ”实例。并且通过方法实现对service-user的服务发起调用。 @RestController public class UserFeignApi { /** * 将刚才定义的userFeignClient注入,并当成普通的接口调用即可 */ @Autowired private UserFeignClient userFeignClient; @GetMapping("/listUsersByFeign") public String ListUsers(){ String users = this.userFeignClient.listUsers(); return users; } } 方式测试fegin的负载均衡以及服务容错保护的功能,我们创建了一个UserFallback实现UserFeignClient。实现接口请求错误的反馈 @Component public class UserFallback implements UserFeignClient { @Override public String listUsers() { return "服务调用失败"; } } 包路径截图 1532588267196.png 到这里就已经基本完成了,依次启动service-user(8802,8803两个端口)、fegin。 1532588691914.png 访问fegin项目的地址http://localhost:9001/listUsersByFeign 1532588711363.png 1532588720488.png 重复请求,会发现接口数据是依次返回8802和8803的数据的。 现在把其中一个service-user服务停掉(8803),一开始当请求负载到8803这个服务实例时,因为当前的服务已经停掉了,所以接口响应显示了我们实现了UserFallback 类的返回。 1532588846177.png 再过几秒钟后,我们继续请求会发现,接口已经全部负载到了8802的服务实例了。 1532588992210.png 所以可以看到fegin已经实现了Ribbon和Hystrix的功能了。 github 项目源码 下一篇我们将实现spring cloud config分布式配置中心的组件。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

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

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等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册