注意:以下规则是我为了规范流程的处理过程,不是Activiti公司的官方规定。

1、流程启动需要设置启动者,在Demo程序中,“启动者变量”名统一设置为initUserId

  1. 启动时要做的:
  2. identityService.setAuthenticatedUserId(initUserId);
  3. processInstance = runtimeService.startProcessInstanceByKey(流程ID, 业务Key, 变量map);
  4.  
  5. or
  6. startProcessInstanceById(String processDefinitionId, String businessKey, Map variables)
  7.  
  8. 变量map定义的方法:
  9. Map<String ,Object > variables = new HashMap<>();
  10. variables.put("initUserId","wangxin");
  11. variables.put("leaveReason","想休假了");

2、使用el表达式来做流程的动态属性或方法定义

比如完成一个“请假销假”的任务,需要流程发起者销假,销假环节就能找到正确的签收者(activiti:assignee)了:

  1. <startevent id="startevent1" name="Start" activiti:initiator="initUserId"></startevent>
  2. <usertask id="reportBack" name="销假" activiti:assignee="${initUserId}"></usertask>

3、“业务键”定义规则

业务键 = 流程ID + 实体实例ID;
businessKey = procDefId + "." + objId

4、根据“业务键”查询流程实例(反查)

在流程启动的时候,我们已经定义了业务Key,那么只需要反查,即可得到流程实例

  1. //根据业务键获取流程实例
  2. public ProcessInstance getProInstByBusinessKey(String businessKey) {
  3. return runtimeService.createProcessInstanceQuery().processInstanceBusinessKey("LeaveBill.1").singleResult();
  4. }
  5.  
  6. //根据业务键获取任务
  7. public List<Task> getTasksByBusinessKey(String businessKey) {
  8. return taskService.createTaskQuery().processInstanceBusinessKey("LeaveBill.1").list();
  9. }

5、通过流程实例ID获取“业务键”

  1. //1、通过任务对象获取流程实例
  2. ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
  3.  
  4. //2、通过流程实例获取“业务键”
  5. String businessKey = pi.getBusinessKey();

6、取得当前活动节点

  1. String processInstanceId="1401";
  2. // 通过流程实例ID查询流程实例
  3. ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
  4. if(pi!=null){
  5. System.out.println("当前流程节点在:" + pi.getActivityId());
  6. }else{
  7. System.out.println("流程已结束!!");
  8. }

7、查询某人的“候选公共任务”,用于实现“抢签”

“候选公共任务”的认领者即属于一堆候选人其中一个,比如财务审批可以由张三、李四、王五审批,谁批都可以,手快者先认领就是签收者。
这个查询就是把符合条件的候选者的任务查出来,一般可以和“个人任务”合并一起放在“待办任务”菜单里。
也针对于把Task分配给一个角色时,例如部门领导,因为部门领导角色可以指定多个人所以需要先签收再办理,特点:抢占式。

  1. // 创建任务查询对象
  2. TaskQuery taskQuery = taskService.createTaskQuery();
  3. // 配置查询对象
  4. String candidateUser="张三";
  5. taskQuery
  6. // 过滤条件
  7. .taskCandidateUser(candidateUser)
  8. // 排序条件
  9. .orderByTaskCreateTime().desc();
  10. // 执行查询
  11. List<Task> tasks = taskQuery.list();
  12. System.out.println("======================【"+candidateUser+"】的候选公共任务列表=================");
  13. for (Task task : tasks) {
  14. System.out.print("id:"+task.getId()+",");
  15. System.out.print("name:"+task.getName()+",");
  16. System.out.print("createTime:"+task.getCreateTime()+",");
  17. System.out.println("assignee:"+task.getAssignee());
  18. }

8、查询某人的“个人任务”,即签收者(assignee)被明确指定。

比如销假人被变量明确指定了:
<usertask id="reportBack" name="销假" activiti:assignee="${initUserId}"></usertask>

  1. // 创建任务查询对象
  2. TaskQuery taskQuery = taskService.createTaskQuery();
  3. // 配置查询对象
  4. // String assignee="user";
  5. String assignee="李四";
  6. taskQuery
  7. // 过滤条件
  8. .taskAssignee(assignee)
  9. // 分页条件
  10. // .listPage(firstResult, maxResults)
  11. // 排序条件
  12. .orderByTaskCreateTime().desc();
  13. // 执行查询
  14. List<Task> tasks = taskQuery.list();
  15. System.out.println("======================【"+assignee+"】的代办任务列表=================");
  16. for (Task task : tasks) {
  17. System.out.print("id:"+task.getId()+",");
  18. System.out.print("name:"+task.getName()+",");
  19. System.out.print("createTime:"+task.getCreateTime()+",");
  20. System.out.println("assignee:"+task.getAssignee());
  21. }

9、任务认领,通过认领,把“候选公共任务”变成指定用户的“个人任务”

  1. // claim 认领
  2. String taskId="1404";
  3. String userId="李四";
  4. // 让指定userId的用户认领指定taskId的任务
  5. taskService.claim(taskId, userId);

10、结合Form表单提交(办理)任务

  1. String formId = request.getParameter("formId");
  2. String procInstId = request.getParameter("procInstId"); //流程实例ID
  3.  
  4. Map<String, String[]> flowData = new HashMap<String, String[]>();
  5.  
  6. //将表单提交数据注入表单变量
  7. flowData = request.getParameterMap();
  8.  
  9. formHelper.submitTaskFormData(request.getParameter("taskId"), flowData);
  10.  
  11. // 完成任务
  12. taskService.complete(taskId );

11、任务动态分配定制处理,比如寻找“某人的直属领导”

Activiti的签收人中只有候选人、候选组、分配人的概念,如果要实现更业务相关的签收逻辑,需要扩展监听器
比如MyLeaderHandler,即扩展实现了TaskListener接口:

  1. <userTask id="task1" name="My task" >
  2. <extensionElements>
  3. <activiti:taskListener event="create" class="org.activiti.MyLeaderHandler" />
  4. </extensionElements>
  5. </userTask>
  1. //动态实现任务分配
  2. public class MyLeaderHandler implements TaskListener {
  3.  
  4. public void notify(DelegateTask delegateTask) {
  5.  
  6. LeaderService ls =....
  7. String userLeader = ls.findLeaderbyUserId(XXXXXXX);
  8. delegateTask.setAssignee(userLeader);
  9. delegateTask.addCandidateUser(XXX);
  10. delegateTask.addCandidateGroup(XXXX);
  11. ...
  12. }
  13. }

还有一种更方便的方法,即通过el表达式:

可以使用表达式把任务监听器设置为spring代理的bean, 让这个监听器监听任务的创建事件。
下面的例子中,执行者会通过调用ldapService这个spring bean的findManagerOfEmployee方法获得。 
流程变量emp会作为参数传递给bean。

<userTask id="task" name="My Task" activiti:assignee="${ldapService.findManagerForEmployee(emp)}"/>

也可以用来设置候选人和候选组:

<userTask id="task" name="My Task" activiti:candidateUsers="${ldapService.findAllSales()}"/>

ps:注意方法返回类型只能为String或Collection<String> (对应候选人和候选组):

  1. public class FakeLdapService {
  2.  
  3. public String findManagerForEmployee(String employee) {
  4. return "Kermit";
  5. }
  6.  
  7. public List<String> findAllSales() {
  8. return Arrays.asList("kermit", "gonzo", "fozzie");
  9. }
  10. }

12、会签任务,即多实例

例如,一个任务必须所有领导都通过了才往下走。

activiti其实已经非常优雅的实现了,网上有一些繁琐的实现,其实完全没有必要,比如下面:

http://jee-soft.cn/htsite/html/fzyyj/jsyj/2012/08/08/1344421504026.html

正确的打开方式是通过在Task节点增加multiInstanceCharacteristics节点,设置 collection和 elementVariable属性

例子:

可以指定一个(判断完成)表达式,只有true的情况下全部实例完成,流程继续往下走。

如果表达式返回true,所有其他的实例都会销毁,多实例节点也会结束。 这个表达式必须定义在completionCondition子元素中。

  1. <userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
  2. <multiInstanceLoopCharacteristics isSequential="false"
  3. activiti:collection="assigneeList" activiti:elementVariable="assignee" >
  4. <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
  5. </multiInstanceLoopCharacteristics>
  6. </userTask>

这里例子中,会为assigneeList集合的每个元素创建一个并行的实例。 当60%的任务完成时,其他任务就会删除,流程继续执行。

会签环节中涉及的几个默认的自带流程变量:

  • 1. nrOfInstances 该会签环节中总共有多少个实例
  • 2. nrOfActiveInstances 当前活动的实例的数量,即还没有 完成的实例数量。
  • 3. nrOfCompletedInstances 已经完成的实例的数量

实现会签人员分配

  1. public class AssgineeMultiInstancePer implements JavaDelegate {
  2. @Override
  3. public void execute(DelegateExecution execution) throws Exception {
  4. System.out.println("设置会签环节的人员.");
  5. execution.setVariable("pers", Arrays.asList("张三", "李四", "王五", "赵六"));
  6. }
  7. }

设置完成会签条件:

  1. public class MulitiInstanceCompleteTask implements Serializable {
  2. private static final long serialVersionUID = 1L;
  3. public boolean completeTask(DelegateExecution execution) {
  4. System.out.println("总的会签任务数量:" + execution.getVariable("nrOfInstances") + "当前获取的会签任务数量:" + execution.getVariable("nrOfActiveInstances") + " - " + "已经完成的会签任务数量:" + execution.getVariable("nrOfCompletedInstances"));
  5. System.out.println("I am invoked.");
  6. return false;
  7. }
  8. }

官方网站专门有一节(8.5.14. Multi-instance)详细介绍:
http://www.activiti.org/userguide/index.html#bpmnMultiInstance

也可以看:
http://itindex.net/detail/54540-activiti
https://my.oschina.net/winHerson/blog/139267
http://itindex.net/detail/51509-activiti-%E6%8C%82%E8%B5%B7-%E6%A0%B8%E5%BF%83
http://blog.csdn.net/chq1988/article/details/41513451
https://github.com/bluejoe2008/openwebflow

13、加签、减签任务,非常符合中国式流程

Activiti其实已经非常优雅的实现了,网上有一些繁琐的实现,其实完全没有必要

实际上一行代码就能实现:

  1. //人员加签:
  2.  
  3. runtimeService.addUserIdentityLink(String processInstanceId, String userId, String identityLinkType)
  4.  
  5. //还可以这样实现,其实是addUserIdentityLink的一个继承,只是类型是IdentityLinkType.CANDIDATE,即参与者
  6. runtimeService.addParticipantUser(String processInstanceId, String userId)
  7.  
  8. //组加签:
  9.  
  10. runtimeService.addParticipantGroup(String processInstanceId, String groupId)
  11.  
  12. runtimeService.deleteParticipantUser(String processInstanceId, String userId)
  13.  
  14. //人员、组减签:
  15.  
  16. runtimeService.deleteParticipantUser(String processInstanceId, String userId)
  17.  
  18. runtimeService.deleteParticipantGroup(String processInstanceId, String groupId)

值得注意的是,认证链类型IdentityLinkType是枚举,有5个:

  • ASSIGNEE
  • CANDIDATE
  • OWNER
  • PARTICIPANT
  • STARTER

使用这里的ASSIGNEE和OWNER还可以有更加丰富的想象力。

14、实现自定义的用户和组织

http://www.verydemo.com/demo_c128_i11037.html
https://my.oschina.net/winHerson/blog/118172
http://rongjih.blog.163.com/blog/static/335744612012631112640968/
http://www.kafeitu.me/activiti/2012/04/23/synchronize-or-redesign-user-and-role-for-activiti.html

15、实现自定义表单

这个想法挺好:
http://blog.csdn.net/tuzongxun/article/details/51093881
但距离实用还差很远,实现产品化也是实现代价非常高,还是使用jsp + bootstrip作为前端最实际,也更容易扩展

笔者正好也做过动态自定义表单:

昕友silverlight表单设计器的使用 (原创 Form Designer)

积累过一些技术和思想。

16、外置表单

在实际的开发中,实际上其自身的内置表单无任何意义,而Activiti还不具备所见即所得的自动表单设计器(毕竟是开源产品),但另一方面这也带来了一个好处:即可以随心所欲的设计表单前端,可以满足任务的可控性和可变性,非常强大,而不是限制在厂商的表单技术里动弹不得,外置表单可以用到的技术:JSP、Spring MVC视图、Struct视图。

Activiti提供的集成方式异常简单而优雅,只需要注意4点:

  • 1、在BPMN文件的任务节点设置FormKey属性,即<userTask activiti:formKey="XXX">...,这里XXX可以是Jsp页面,也可以是Struct Action,也可以是Spring Controller名,以便对应表单ID,真的方便的想哭了;
  • 2、在流程运行时,根据TaskId获取FormKey:
    1. StartFormData FormService.getStartFormData(String processDefinitionId)
      //Or
      TaskFormdata FormService.getTaskFormData(String taskId)
    2. String formKey = formData.getFormKey()
      ...
  • 3、然后在Spring 控制器里重定向传递:XXXX?fromKey=XXX
  • 4、Form数据提交:
  1. Map map=request.getParameterMap();
  2. Set set=map.keySet();//所有参数名的set集
  3. Iterator it=set.iterator();
  4. while(it.hasNext()){
  5. String s=(String)it.next();//枚举出参数
  6. String values[]=request.getParameterValues(s);//取得对应的参数值返回的数组
  7. 然后干你要干的操作,比如
  8. for(int i=0;i<values.length;i++){
  9. out.println(values[i]);
  10. }
  11. }
  12.  
  13. 提交:

ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties)
void FormService.submitStartFormData(String taskId, Map<String,String> properties)

17、表单属性和表单变量的关联关系

实际上在业务开发中可以不需要用到表单属性,只用表单变量即可,需要注意表单变量的作用域,有两个

  • 流程实例内全局可用
  • 仅任务内可用

在Activit内部,定义了流程属性,会自动增加流程变量,也可以手动设置关联, 手动流程属性和变量的关联关系举例:

1、属性speaker 和 变量SpeakerName 相互关联

  <activiti:formProperty id="speaker" name="Speaker" variable="SpeakerName" type="string" />

2、和bean关联

  <activiti:formProperty id="street" expression="#{address.street}" required="true" />

form property的5种类型:

  • string (org.activiti.engine.impl.form.StringFormType

  • long (org.activiti.engine.impl.form.LongFormType)

  • enum (org.activiti.engine.impl.form.EnumFormType)

  • date (org.activiti.engine.impl.form.DateFormType)

  • boolean (org.activiti.engine.impl.form.BooleanFormType)

获取表单属性的办法:

    1. List<FormProperty> formService.getStartFormData(String processDefinitionId).getFormProperties()
    2. List<FormProperty> formService.getTaskFormData(String taskId).getFormProperties()

FormProperty实际是一个接口,容许去你去自由扩展,具体定义:

  1. public interface FormProperty {
  2. /** the key used to submit the property in {@link FormService#submitStartFormData(String, java.util.Map)}
  3. * or {@link FormService#submitTaskFormData(String, java.util.Map)} */
  4. String getId();
  5. /** the display label */
  6. String getName();
  7. /** one of the types defined in this interface like e.g. {@link #TYPE_STRING} */
  8. FormType getType();
  9. /** optional value that should be used to display in this property */
  10. String getValue();
  11. /** is this property read to be displayed in the form and made accessible with the methods
  12. * {@link FormService#getStartFormData(String)} and {@link FormService#getTaskFormData(String)}. */
  13. boolean isReadable();
  14. /** is this property expected when a user submits the form? */
  15. boolean isWritable();
  16. /** is this property a required input field */
  17. boolean isRequired();
  18. }

获取表单属性的名称:

  1. formProperty.getType().getName()

获取表单属性的值:

  1. formProperty.getType().getValue()

获取某一属性,比如XML定义:

<activiti:formProperty id="start" type="date" datePattern="dd-MMM-yyyy" />

  1. formProperty.getType().getInformation("datePattern")

获取枚举值:

  1. formProperty.getType().getInformation("values")

18、自定义“待办列表”的一个方法

Activit内置的待办任务查询类似:

  1. List<Task> tasks = taskService.createTaskQuery()
  2. .taskAssignee("kermit")
  3. .processVariableValueEquals("orderId", "0815")
  4. .orderByDueDate().asc()
  5. .list();

如果要直接SQL查询,可以这样:

  1. List<Task> tasks = taskService.createNativeTaskQuery()
  2. .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}")
  3. .parameter("taskName", "gonzoTask")
  4. .list();
  5.  
  6. long count = taskService.createNativeTaskQuery()
  7. .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, "
  8. + managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
  9. .count();

19、深入理解流程变量

首先需要明确一些容易混淆的概念。

"execution是什么?"
就是流程实例的当前执行节点;

"execution node和process instance的区别是什么?"
执行节点和流程实例的关系非常密切,可以想象一个树形结构,流程实例(process instance)是主干,由无数的执行树枝(execution)组成的;
一个重要的公式:process instance id = the root execution id
但一旦流程不是一条主干线一直到结束,而是分裂为多个分支了,那么就有多个execution id,可以看成树枝。

"activity、task、job的区别是什么?"

  • Activity = 这个是模型定义BPMN的一个基本元素(注意这个不是运行时的概念),比如Start、user task、连接线......
  • task = 这个是运行时的概念,就是任务的意思,比如用户任务、服务任务......
  • job = 一个定时器任务

设置流程变量(RuntimeService)的方法:

  1. void setVariable(String executionId, String variableName, Object value);
  2. void setVariableLocal(String executionId, String variableName, Object value);
  3. void setVariables(String executionId, Map<String, ? extends Object> variables);
  4. void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);

读取流程变量的方法:

  1. //读取变量(基于TaskService)
  2. Map<String, Object> getVariables(String executionId);
  3. Map<String, Object> getVariablesLocal(String executionId);
  4. Map<String, Object> getVariables(String executionId, Collection<String> variableNames);
  5. Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
  6. Object getVariable(String executionId, String variableName);
  7. <T> T getVariable(String executionId, String variableName, Class<T> variableClass);
  8.  
  9. //读取变量(基于execution 执行分支对象)
  10. execution.getVariables();
  11. execution.getVariables(Collection<String> variableNames);
  12. execution.getVariable(String variableName);
  13. execution.setVariables(Map<String, object> variables);
  14. execution.setVariable(String variableName, Object value);

Activiti5.17版本之后的新API

由于历史版本原因,在执行任何上述方法的时候,activiti默认是将所有的变量从数据库取出来,意味着数据库存有10个变量,现在你需要取出名叫myVariable的变量,但是其余的9个也会被取出来并且缓存起来。这并不是很差,因为后期你取变量就不会再从数据库取出,当然如果你有大量的变量或者在查询方面你想进一步控制数据库,这时全部取出就不怎么合适了。从Activiti5.17版本开始,新添加了的方法支持是否全部查询输出到缓存:如果是true则全部抓取

  1. Map<String, Object> getVariables(Collection<String> variableNames, boolean fetchAllVariables);
  2. Object getVariable(String variableName, boolean fetchAllVariables);
  3. void setVariable(String variableName, Object value, boolean fetchAllVariables);

A011 Activiti工作流程开发的一些统一规则和实现原理(完整版)的更多相关文章

  1. Liferay7 BPM门户开发之11: Activiti工作流程开发的一些统一规则和实现原理(完整版)

    注意:以下规则是我为了规范流程的处理过程,不是Activiti公司的官方规定. 1.流程启动需要设置启动者,在Demo程序中,“启动者变量”名统一设置为initUserId 启动时要做的: ident ...

  2. python开发_xml.dom_解析XML文档_完整版_博主推荐

    在阅读之前,你需要了解一些xml.dom的一些理论知识,在这里你可以对xml.dom有一定的了解,如果你阅读完之后. 下面是我做的demo 运行效果: 解析的XML文件位置:c:\\test\\hon ...

  3. git-flow工作流程

    什么是 git-flow? 一旦安装安装 git-flow,你将会拥有一些扩展命令.这些命令会在一个预定义的顺序下自动执行多个操作.是的,这就是我们的工作流程! git-flow 并不是要替代 Git ...

  4. 基于Activiti的流程应用开发平台JSAAS-WF V5.3

    第1章 产品概述及体系架构 1.1.概述 红迅JSAAS-WF工作流平台V5是广州红迅软件有限公司面向合作伙伴以及有IT运维团队中大型企业提供新一代的流程管理产品,它基于流行的JAVA开源技术上构建, ...

  5. Git代码分支开发工作流程

    本文的工作流程,有一个共同点:都采用"功能驱动式开发"(Feature-driven development,简称FDD). 它指的是,需求是开发的起点,先有需求再有功能分支(fe ...

  6. git的介绍、git的功能特性、git工作流程、git 过滤文件、git多分支管理、远程仓库、把路飞项目传到远程仓库(非空的)、ssh链接远程仓库,协同开发

    Git(读音为/gɪt/)是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理. [1] 也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码 ...

  7. 流程开发Activiti 与SpringMVC整合实例

    流程(Activiti) 流程是完成一系列有序动作的概述.每一个节点动作的结果将对后面的具体操作步骤产生影响.信息化系统中流程的功能完全等同于纸上办公的层级审批,尤其在oa系统中各类电子流提现较为明显 ...

  8. Git 分支-利用分支进行开发的工作流程

    3.4 Git 分支 - 利用分支进行开发的工作流程 利用分支进行开发的工作流程 现在我们已经学会了新建分支和合并分支,可以(或应该)用它来做点什么呢?在本节,我们会介绍一些利用分支进行开发的工作流程 ...

  9. 【嵌入式开发】 Bootloader 详解 ( 代码环境 | ARM 启动流程 | uboot 工作流程 | 架构设计)

    作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42462795 转载请著名出处 相关资源下载 :  -- u-boo ...

随机推荐

  1. lsnrctl start错误Linux Error: 29: Illegal seek (翻译:非法谋取)

    现在,想不起来为什么ORACLE的监听,怎么就突然无法起来了呢. 好吧,问题反正就是发生了. lsnrctl start 遇到如下错误, LSNRCTL for Linux: Version 10.2 ...

  2. 连接mysql时报:message from server: "Host '192.168.76.89' is not allowed to connect to this MySQL server 处理方案

    1.先用localhost方式连接到MySQL数据库,然后使用MySQL自带的数据库mysql; use mysql: 2.执行:select host from user where user = ...

  3. Libvirt磁盘加密

    Libvirt加密磁盘使用 创建加密磁盘 进入libvirt默认存储池目录 # cd /var/lib/libvirt/images 创建加密磁盘 # qemu-img convert -O qcow ...

  4. 使用js页面添加或删除标签

    // 添加var container = document.getElementById('divAudio');container.appendChild(audio); // 删除var cont ...

  5. Solr 6.7学习笔记(02)-- 配置文件 managed-schema (schema.xml)(3)

         5. <fieldType> fieldType主要定义了一些字段类型,其name属性值用于前面<field>中的type属性的值.e.g. <fieldTyp ...

  6. bzoj 3944: Sum(杜教筛)

    3944: Sum Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 4930  Solved: 1313[Submit][Status][Discuss ...

  7. 对zabbix监控磁盘性能的补充

    原因 在上一篇文章中,我写了完整的磁盘监控步骤,希望对大家有所帮助.但是这里还需要作出一点补充. 根据上一篇文章的内容,我是使用iostat命令不停的收集磁盘的信息,然后写入到/tmp/iostat_ ...

  8. 全网排名第一的免费开源ERP Odoo Git源代码部署教程

    文/开源智造联合创始人老杨 本文来自<开源自主OdooERP部署架构指南>试读:第三章-Git源代码部署 .书籍尚未出版,请勿转载.欢迎您反馈阅读意见. 我们将从git源代码部署Odoo ...

  9. C#网络编程学习(6)---序列化和反序列化

    1.什么是序列化和反序列化 当客户端和服务器进行远程连接时,互相可以发送各种类型的数据.但都要先把这些对象转换为字节序列,才能在网络上进行传输. 序列化:就是发送方 把对象转换为字节序列的过程. 反序 ...

  10. .NET Memcached Client 扩展获取所有缓存Key

    .NET Memcached Client默认实现中并没有获取所有已经缓存Key的方法,但在业务中有时候需求中需要通过正则删除符合条件的缓存内容,所以就要通过读取已经缓存Key进行相关的匹配,然后删除 ...