当前位置: 首页 > news >正文

flowable工作流看这一篇就够了(进阶篇 下)

目录

三、多人会签

3.1、多实例介绍

3.2、基本应用

案例一(静态指定数量)

案例二(动态数量和指派审批人)

案例三(表达式方式)

案例四(Java方法控制完成条件)

3.3、服务任务

3.4、子流程应用

四、动态表单

4.1、FlowableUI持久化

4.2、表单管理

创建表单

表单部署

流程启动绑定

表单数据查询

4.3、流程单个节点绑定

4.4、outcome

五、任务的回退

5.1、串行的回退

5.2、并行的回退(回退到单一节点)

5.3、并行的回退(单一节点回退到并行网关)

5.4、子流程回退

5.5、流程的撤销


三、多人会签

3.1、多实例介绍

多实例活动是为业务流程中的某个步骤定义重复的一种方式。在编程概念中,多实例与 for each 结构相匹配:它允许对给定集合中的每个项目按顺序或并行地执行某个步骤或甚至一个完整的子流程。

多实例是一个有额外属性(所谓的 “多实例特性”)的常规活动,它将导致该活动在运行时被多次执行。以下活动可以成为多实例活动。

  • Service Task 服务任务

  • Send Task 发送任务

  • User Task 用户任务

  • Business Rule Task 业务规则任务

  • Script Task 脚本任务

  • Receive Task 接收任务

  • Manual Task 手动任务

  • (Embedded) Sub-Process (嵌入)子流程

  • Call Activity 发起活动

  • Transaction Subprocess 事务子流程

网关或事件不能成为多实例。

如果一个活动是多实例的,这将由活动底部的三条短线表示。三条垂直线表示实例将以并行方式执行,而三条水平线表示顺序执行。

按照规范的要求,每个实例所创建的执行的每个父执行将有以下变量:

  • nrOfInstances: 实例的总数量

  • nrOfActiveInstances: 当前活动的,即尚未完成的实例的数量。对于一个连续的多实例,这将永远是1。

  • nrOfCompletedInstances: 已经完成的实例的数量。

这些值可以通过调用 “execution.getVariable(x) “方法检索。

此外,每个创建的执行将有一个执行本地变量(即对其他执行不可见,也不存储在流程实例级别)。

  • loopCounter: 表示该特定实例的for each循环中的索引

为了使一个活动成为多实例,活动xml元素必须有一个multiInstanceLoopCharacteristics子元素。

<multiInstanceLoopCharacteristics isSequential="false|true">...
</multiInstanceLoopCharacteristics>

isSequential属性表示该活动的实例是按顺序执行还是并行执行。

3.2、基本应用

多实例应用中我们需要指的具体生成几个实例任务。指派的方式可以通过loopCardinality属性来指的。通过loopCardinality来指定既可以是固定值也可以指定表达式(只要结果是整数即可)

<multiInstanceLoopCharacteristics isSequential="false"><loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
或者
<multiInstanceLoopCharacteristics isSequential="false"><loopCardinality>${num}</loopCardinality>
</multiInstanceLoopCharacteristics>

案例一(静态指定数量)

部署启动:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("multiInstance-demo1.bpmn20.xml").name("multiInstance-demo1").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** start process*/
@Test
void startFlow() {runtimeService.startProcessInstanceById("multiInstance-demo1:1:85c57d01-a718-11ee-b365-1a473d673661");
}

我们先审批一个任务:

/*** 审批*/
@Test
public void completeTask() {taskService.complete("c92542cb-a718-11ee-bb4d-1a473d673661");
}

审批结束task表还剩两个任务。

我们依次都审批一下:

现在task表进行到了“多实例-串行”任务

我们审批一下:

审批结束后,发现走到了“多实例-串行”的第二个任务。

我们依次将第二和第三任务都审批完成,最后流程结束。

案例二(动态数量和指派审批人)

部署并启动:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("multiInstance-demo2.bpmn20.xml").name("multiInstance-demo2").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** start process*/
@Test
void startFlow() {Map<String,Object> map = new HashMap<>();map.put("users", Arrays.asList("张三","李四","王五"));runtimeService.startProcessInstanceById("multiInstance-demo2:1:9e822c93-a774-11ee-92a1-1a473d673661",map);
}

发现有三个并行的用户任务。

案例三(表达式方式)

上面的例子我们一旦设置了3个用户并行节点,那就必须3个都审批才能流程结束,那怎么才能灵活点呢?比如3个节点里有1个用户审批就算流程结束。

${nrOfCompletedInstances/nrOfInstances >= 0.5 }

这句代码的意思是:审批完成数量如果大于等于0.5,就流程结束。我们设置张三、李四、王五三个用户并行节点,其实只要张三和李四审批通过,流程就结束了,因为3分之2大于0.5。

部署并启动:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("multiInstance-demo3.bpmn20.xml").name("multiInstance-demo3").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** start process*/
@Test
void startFlow() {Map<String,Object> map = new HashMap<>();map.put("users", Arrays.asList("张三","李四","王五"));runtimeService.startProcessInstanceById("multiInstance-demo3:1:47da4ebf-a777-11ee-b7e9-1a473d673661",map);
}

我们将其中两个审批:

/*** 审批*/
@Test
public void completeTask() {taskService.complete("a3b2a811-a779-11ee-92e7-1a473d673661");
}

三个用户任务两个审批通过流程就结束了。

案例四(Java方法控制完成条件)

创建Java类:

/*** 动态处理会签 完成条件*/
@Component("multiInstanceDelegate")
public class MultiInstanceDelegate {public boolean completeInstanceTask(DelegateExecution execution) {// 获取当前多实例中的相关的参数// 总得流程实例数量Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");// 当前活跃的实例数量【没有审批的数量】Integer nrOfActiveInstances = (Integer) execution.getVariable("nrOfActiveInstances");// 当前已经审批的数量Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances");System.out.println("nrOfInstances = " + nrOfInstances);System.out.println("nrOfActiveInstances = " + nrOfActiveInstances);System.out.println("nrOfCompletedInstances = " + nrOfCompletedInstances);return nrOfCompletedInstances > nrOfActiveInstances;}}

在xml中绑定类:

部署并启动:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("multiInstance-demo4.bpmn20.xml").name("multiInstance-demo4").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** start process*/
@Test
void startFlow() {Map<String,Object> map = new HashMap<>();map.put("users", Arrays.asList("张三","李四","王五"));runtimeService.startProcessInstanceById("multiInstance-demo3:2:1b63c2dd-a77c-11ee-b233-1a473d673661",map);
}

我们审批两个:

3.3、服务任务

上面的案例都是在用户任务中实现。我们也可以在Service Task来实现。具体如下:

部署并启动:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("multiInstance-demo5.bpmn20.xml").name("multiInstance-demo5").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** start process*/
@Test
void startFlow() {runtimeService.startProcessInstanceById("multiInstance-demo5:1:917cf491-a77d-11ee-be15-1a473d673661");
}

将此用户任务审批:

/*** 审批*/
@Test
public void completeTask() {taskService.complete("a90a0247-a77d-11ee-b929-1a473d673661");
}

3.4、子流程应用

多实例的场景也可以在子流程中来使用,具体我们通过案例来讲解。本质上和我们前面介绍的是差不多的。

部署并启动:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("multiInstance-demo6.bpmn20.xml").name("multiInstance-demo6").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** start process*/
@Test
void startFlow() {runtimeService.startProcessInstanceById("multiInstance-demo6:1:ad738209-a77e-11ee-a6e4-1a473d673661");
}

审批一下用户任务1:

结果出现了并行的两个子流程。

四、动态表单

在实际的工作流审批中我们肯定 需要携带相关的数据的:

  1. 业务数据--流程实例绑定业务主键

  2. 表单数据--动态表单动态绑定

涉及到业务主键操作的方法:

 // 在启动流程实例的时候绑定业务主键 1001231是我们自己随便设置的
runtimeService.startProcessInstanceById(processId,"1001231");
// 也可以通过runtimeService来动态的更新runtimeService.updateBusinessKey();
// 通过 ProcessInstance 来获取对应的业务主键
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId("ffe242db-9720-11ee-80a7-c03c59ad2248").singleResult();
String businessKey = processInstance.getBusinessKey();

4.1、FlowableUI持久化

为了更好的介绍表单的内容我们先把FlowableUi的数据持久化到MySQL数据库中。我们来看看应该要如何来修改。先找到flowable-default.properties这个属性文件。来修改数据库的配置信息,webapps\flowable-ui\WEB-INF\classes在这个目录下

然后我们还需要添加MySQL的驱动到lib目录中:

然后重启FlowableUI服务即可。

数据库有87张表。

4.2、表单管理

创建表单

我们拿一个文本、一个多行文本、一个日期:

分别对这三个组件编辑:

我们看数据库:

我们创建的表单在act_de_model表中。

其中model_editor_json字段是我们的表单组件json。

表单部署

通过FlowableUI我们创建的Form表单,然后我们来看看应该要如何来部署表单,以及和流程关联后做流程操作以及相关的流程数据的查询出来。我们先通过单元测试来看看

部署这块我们需要通过FormRepositoryService来处理。

我们现在resources下创建.form文件:

然后将我们刚才绘制的表单数据,act_de_model表中的model_editor_json字段值都复制到此文件中。

部署:

/*** 1.部署流程* 2.部署表单* 3.启动带有表单的流程-->创建了对应的流程实例*/
@Test
public void deployFormFlow(){FormDeployment deploy = formRepositoryService.createDeployment().addClasspathResource("first.form").name("报销表单").deploy();System.out.println("deploy.getId() = " + deploy.getId());
}

表单部署会涉及到的几张表结构。

表名作用
act_fo_form_definitionForm表单定义表
act_fo_form_deploymentForm表单部署表
act_fo_form_resourceForm表单资源表

其实我们还有另一种方式来部署,采用addString方法:

@Autowired
FormRepositoryService formRepositoryService;/*** 1.部署流程* 2.部署表单* 3.启动带有表单的流程-->创建了对应的流程实例*/
@Test
public void deployFormFlow(){// 1.获取需要部署的form文件String json = "{\"name\":\"报销流程表单\",\"key\":\"expenseAccountForm\",\"version\":0,\"fields\":[{\"fieldType\":\"FormField\",\"id\":\"amount\",\"name\":\"报销金额\",\"type\":\"integer\",\"value\":null,\"required\":true,\"readOnly\":false,\"overrideId\":true,\"placeholder\":\"0\",\"layout\":null},{\"fieldType\":\"FormField\",\"id\":\"reason\",\"name\":\"报销原因\",\"type\":\"text\",\"value\":null,\"required\":false,\"readOnly\":false,\"overrideId\":true,\"placeholder\":null,\"layout\":null},{\"fieldType\":\"FormField\",\"id\":\"expenseDate\",\"name\":\"报销日期\",\"type\":\"date\",\"value\":null,\"required\":false,\"readOnly\":false,\"overrideId\":true,\"placeholder\":null,\"layout\":null}],\"outcomes\":[]}";FormDeployment deploy = formRepositoryService.createDeployment().addString("报销流程表单.form", json).name("报销表单").deploy();System.out.println("deploy.getId() = " + deploy.getId());
}

这里我们就直接从数据库中把表单的JSON数据拷贝出来存储在String类型中,然后通过formRepositoryService来实现部署操作,此次需要注意如果通过非xxx.form 文件的方式部署,我们添加ResourceName资源名称的时候,必须要加上.form后缀,不然部署会失败。

流程启动绑定

然后我们来看看在具体流程中是如何和表单关联起来的。先来看看在表单起始的时候就绑定。

然后保存流程即可,注意保存成功后在act_de_relation中会生成一条流程表单的对应管理,对应的在act_de_model中会生成一条刚刚创建的流程信息如下:

对应的对应关系:

部署并启动:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("报销流程.bpmn20.xml").name("报销流程").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** 启动流程*/
@Test
public void startFlowWithForm() {Map<String,Object> map = new HashMap<>();map.put("amount",10000);map.put("reason","维护客户关系");map.put("bxDate","2023-05-06");runtimeService.startProcessInstanceWithForm("bxtask:1:eaca369b-a7af-11ee-9cdc-1a473d673661",null,map,"报销流程");
}

表单数据查询

/*** 获取流程绑定的表单数据*/
@Test
public void getTaskFormInfo() {// 流程定义ID 对应task表PROC_DEF_ID_字段String proDefId = "bxtask:1:eaca369b-a7af-11ee-9cdc-1a473d673661";// 流程实例ID 对应task表PROC_INST_ID_字段String proInsId = "0967c741-a7b1-11ee-a085-1a473d673661";FormInfo startFormModel = runtimeService.getStartFormModel(proDefId, proInsId);System.out.println("startFormModel.getKey() = " + startFormModel.getKey());System.out.println("startFormModel.getName() = " + startFormModel.getName());System.out.println("startFormModel.getDescription() = " + startFormModel.getDescription());// 获取表单对应的数据SimpleFormModel formModel = (SimpleFormModel) startFormModel.getFormModel();List<FormField> fields = formModel.getFields();for (FormField field: fields) {System.out.println("field.getId() = " + field.getId());System.out.println("field.getName() = " + field.getName());System.out.println("field.getValue() = " + field.getValue());}}

4.3、流程单个节点绑定

单个节点绑定其实就是在用户任务上关联Form表单,具体如下:

部署并启动:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("form-demo2.bpmn20.xml").name("form-demo2").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** start process*/
@Test
void startFlow() {Map<String,Object> map = new HashMap<>();map.put("amount",999);map.put("reason","我想报销");map.put("bxDate","2023-05-06");runtimeService.startProcessInstanceById("form-demo2:1:7fa62223-a7b4-11ee-8ab4-1a473d673661",map);
}

当我们再调用如下方法查询表单对应的数据时:

/*** 获取流程绑定的表单数据*/
@Test
public void getTaskFormInfo() {// 流程定义ID 对应task表PROC_DEF_ID_字段String proDefId = "form-demo2:1:7fa62223-a7b4-11ee-8ab4-1a473d673661";// 流程实例ID 对应task表PROC_INST_ID_字段String proInsId = "c611efbb-a7b4-11ee-a580-1a473d673661";FormInfo startFormModel = runtimeService.getStartFormModel(proDefId, proInsId);System.out.println("startFormModel.getKey() = " + startFormModel.getKey());System.out.println("startFormModel.getName() = " + startFormModel.getName());System.out.println("startFormModel.getDescription() = " + startFormModel.getDescription());// 获取表单对应的数据SimpleFormModel formModel = (SimpleFormModel) startFormModel.getFormModel();List<FormField> fields = formModel.getFields();for (FormField field: fields) {System.out.println("field.getId() = " + field.getId());System.out.println("field.getName() = " + field.getName());System.out.println("field.getValue() = " + field.getValue());}}

我们将代码改为:

/*** 获取绑定在单独节点上的表单数据*/
@Test
public void getTaskFormNodeInfo() {// 参数为task表的主键IDFormInfo taskFormModel = taskService.getTaskFormModel("c6180a43-a7b4-11ee-a580-1a473d673661");System.out.println("startFormModel.getKey() = " + taskFormModel.getKey());System.out.println("startFormModel.getName() = " + taskFormModel.getName());System.out.println("startFormModel.getDescription() = " + taskFormModel.getDescription());// 获取表单对应的数据SimpleFormModel formModel = (SimpleFormModel) taskFormModel.getFormModel();List<FormField> fields = formModel.getFields();for (FormField field: fields) {System.out.println("field.getId() = " + field.getId());System.out.println("field.getName() = " + field.getName());System.out.println("field.getValue() = " + field.getValue());}
}

我们将用户任务1审批,现在应该到了用户任务2:

我们用用户任务2的ID再次查询表单数据。

因为我们只在用户任务1节点上绑定了表单。

4.4、outcome

outcome用来指定表单审批对应的结果。表单审批完成后根据outcome的信息会在act_ru_variable中会生成一个form_表单ID_outcome这样一个流程变量,那么我们就可以根据这个流程变量来对应的路由到相关的流程中。比如:form_formbx2_outcome

注意:表单ID我们不用使用 -拼接。

我们先在表单上定义两个结果,分别是接受和拒绝:

全部开始节点绑定表单:

排他网关两侧设置条件:

先部署表单:

/*** 1.部署流程* 2.部署表单* 3.启动带有表单的流程-->创建了对应的流程实例*/
@Test
public void deployFormFlow(){FormDeployment deploy = formRepositoryService.createDeployment().addClasspathResource("first.form").name("报销表单").deploy();System.out.println("deploy.getId() = " + deploy.getId());
}

部署并启动流程:

/*** Deploy*/
@Test
void testDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("form-demo3.bpmn20.xml").name("form-demo3").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** 启动流程*/
@Test
public void startFlowWithForm() {Map<String,Object> map = new HashMap<>();map.put("amount",666);map.put("reason","我要报销");map.put("bxDate","2023-12-31");runtimeService.startProcessInstanceWithForm("form-demo3:1:3c1352cf-a7c1-11ee-a135-1a473d673661",null,map,"报销流程");
}

五、任务的回退

5.1、串行的回退

我们先从最简单的串行流程来分析,案例如下:

部署并启动:

/*** 部署流程*/
@Test
void backDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("back-demo1.bpmn20.xml").name("back-demo1").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** 启动流程*/
@Test
public void startBackFlow() {runtimeService.startProcessInstanceById("back-demo1:1:1f66c13f-a7de-11ee-b6b2-1a473d673661");
}

审批一直到用户4:

/*** 审批*/
@Test
public void completeBackTask() {taskService.complete("5ca9e3c9-a7de-11ee-a405-1a473d673661");
}

任务回退,从用户4退回到用户3:

/*** 任务回退*/
@Test
public void backFlow() {runtimeService.createChangeActivityStateBuilder().processInstanceId("5ca5c514-a7de-11ee-a405-1a473d673661")//第一个参数:后面的ID,这里是用户4  第二个参数:前面的ID,这里是用户3.moveActivityIdTo("sid-E1666002-4CD3-49CD-9008-19A90600C1E0","sid-89C7AE8A-A433-47AA-A76C-A16EB6AB9B9A").changeState();
}

注意:moveActivityIdTo方法的参数是xml文件里<userTask>的id属性值。

当然,moveActivityIdTo方法也不光只能从后往前回退,也可以从前跳到后面某节点。

5.2、并行的回退(回退到单一节点)

接下来我们在并行的场景中来看看各种回退的场景。具体案例流程如下:

部署并启动:

/*** 部署流程*/
@Test
void backDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("back-demo2.bpmn20.xml").name("back-demo2").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** 启动流程*/
@Test
public void startBackFlow() {runtimeService.startProcessInstanceById("back-demo2:1:f0ec9dae-a7e2-11ee-b807-1a473d673661");
}

现在走到“用户审批”:

审批:

将行政副总和业务负责人两个节点同时退回到用户审批节点:

/*** 任务回退*/
@Test
public void backFlow() {runtimeService.createChangeActivityStateBuilder().processInstanceId("532fa4ac-a7e3-11ee-8e15-1a473d673661")// 因为是并行网关,所以要将usertask3和usertask2同时退回到usertask1节点.moveActivityIdsToSingleActivityId(Arrays.asList("usertask3","usertask2"),"usertask1").changeState();
}

我们重新多层审批,直到最后走到“总经理”节点:

5.3、并行的回退(单一节点回退到并行网关)

我们现在从“总经理”节点回退到“业务副总”和“行政副总”节点:

/*** 任务回退*/
@Test
public void backFlow() {runtimeService.createChangeActivityStateBuilder().processInstanceId("532fa4ac-a7e3-11ee-8e15-1a473d673661").moveSingleActivityIdToActivityIds("usertask5",Arrays.asList("usertask2","usertask4")).changeState();
}

5.4、子流程回退

最后我们来看看带有子流程的场景下如果有回退的情况应该要如何来处理,案例如下:

部署并启动流程:

/*** 部署流程*/
@Test
void backDeploy() {Deployment deploy = repositoryService.createDeployment().addClasspathResource("back-demo3.bpmn20.xml").name("back-demo3").deploy();System.out.println("deploy.getId() = " + deploy.getId());System.out.println("deploy.getName() = " + deploy.getName());
}/*** 启动流程*/
@Test
public void startBackFlow() {runtimeService.startProcessInstanceById("back-demo3:1:76eba5cb-a7e8-11ee-abd5-1a473d673661");
}

审批:

/*** 审批*/
@Test
public void completeBackTask() {taskService.complete("b86015f1-a7e8-11ee-88e0-1a473d673661");
}

从子流程“用户2”回退到用户1:

/*** 任务回退*/
@Test
public void backFlow() {runtimeService.createChangeActivityStateBuilder().processInstanceId("b85826ac-a7e8-11ee-88e0-1a473d673661").moveActivityIdTo("user2","user1").changeState();
}

5.5、流程的撤销

​流程的撤销一般是流程的发起人感觉没有必要再做流程审批的推进了。想要结束流程的审批操作。这个时候我们可以直接通过runtimeService.deleteProcessInstance()方法来实现相关的流程撤销操作。

比如我们上面的并行案例中。进入到并行节点后想要结束:

部署并启动...

审批:

现在我们进行流程的撤销:

/*** 流程撤销*/
@Test
public void deleteProcessInstance(){runtimeService.deleteProcessInstance("aedd050c-a7ea-11ee-af22-1a473d673661","我是删除原因");
}

刚才的流程已经没有了。

相关文章:

  • Web常用的编码和解码技术
  • 原型继承在 JavaScript 中是如何工作
  • 回首2023: 程序员跳出舒适圈
  • python如何读取被压缩的图像
  • 亲爱的程序猿们,元旦快乐!
  • 1.3MySQL中的自连接
  • 【Linux】Shell
  • nodejs业务分层如何写后端接口
  • Docker 安装 Nacos
  • C Primer Plus 第6版 编程练习 chapter 12
  • 机器学习之人工神经网络(Artificial Neural Networks,ANN)
  • 在Spring Boot中使用Redis
  • Solidworks学习笔记
  • 工具--Git详解
  • 【Qt-QString】
  • [iOS]Core Data浅析一 -- 启用Core Data
  • Asm.js的简单介绍
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • HomeBrew常规使用教程
  • java 多线程基础, 我觉得还是有必要看看的
  • Java多线程(4):使用线程池执行定时任务
  • Java基本数据类型之Number
  • java正则表式的使用
  • nodejs调试方法
  • python 学习笔记 - Queue Pipes,进程间通讯
  • spring boot 整合mybatis 无法输出sql的问题
  • underscore源码剖析之整体架构
  • 聊聊redis的数据结构的应用
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 延迟脚本的方式
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • ionic异常记录
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #宝哥教你#查看jquery绑定的事件函数
  • $nextTick的使用场景介绍
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (7)摄像机和云台
  • (数据结构)顺序表的定义
  • (算法)Travel Information Center
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (杂交版)植物大战僵尸
  • (转)Scala的“=”符号简介
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (自适应手机端)响应式服装服饰外贸企业网站模板
  • .Mobi域名介绍
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .NET关于 跳过SSL中遇到的问题
  • .net获取当前url各种属性(文件名、参数、域名 等)的方法
  • .NET命令行(CLI)常用命令
  • .NET设计模式(11):组合模式(Composite Pattern)