jbpm的智能选择审批人及人工任务分配的实现思路

一、jbpm中审批人的概念

在工作流中每一个人工任务的办理者被称作审批人,在原生的jbpm定义中,我们可以看到assignee和assignmentHandler这两个标签。

<task name="review" g="96,16,127,52">  
    <assignment-handler class="org.jbpm.examples.task.assignmenthandler.AssignTask">  
      <field name="assignee">  
        <string value="chao.gao" />  
      </field>  
    </assignment-handler>  
    <transition to="wait" />  
  </task>

assignee如果是单用户,可以是字符串;如果是多用户,则是列表或数组;另外也可以使用类似jsp中的正则表达式,进行动态审批人的指定

如果有assignee,而没有分配处理器标签,则引擎自动将该人工任务分配给所有assignee。如有assignmentHandler会按照默认或定制的分配处理器制定的规则进行审批人的智能筛选以及对任务进行动态的指派。

在会签人方案一文中,提到了关于使用assignmentHandler生成会签子任务的实例,在决策里也提到了DefaultDecisionHandler,jbpm使用监听器模式对不同的处理类进行调用。

/**  
     * @author Tom Baeyens  
     */  
    public class AssignTask implements AssignmentHandler {   
         
      private static final long serialVersionUID = 1L;   
      
      String assignee;   
      
      public void assign(Assignable assignable, OpenExecution execution) {   
        assignable.setAssignee(assignee);   
      }   
    }

以上是jbpm审批人的原生概念以及如何将指定或动态指定的用户与人工任务相绑定的理论知识。

下面阐述我们的审批人选择以及分配任务的思路

二、智能选择审批人

审批人设置表结构

数据列表如下

  页面设置:

一般情况下,商用工作流都提供基于组织架构、汇报关系、角色的审批人选择方法,OA中也提供了相应功能。基于组织架构上下级关系,主要有当前部门、当前部门之上级部门、当前部门上级部门之上级部门,基于组织架构的层级关系,主要有一级部门、二级部门、三级部门、子公司等。以上两种规则,除当前部门外还可以通过表单数据指定部门的编码。基于组织架构的成员,可以将本架构内所有成员全部纳入审批人列表。

代码如下:

private Set<String> getAssigneeByAssignSetting(FlowRunTime flowRunTimeInfo,
			UserAssignEntity assignSetting) {
    	Set<String> candidateUserIdList = new HashSet<String>();

    	if("Y".equals(assignSetting.getIsForm())){
    		String fieldValue = getFiledValueByAssignSetting(assignSetting, flowRunTimeInfo);
    		if(StringUtils.isNotEmpty(fieldValue) && assignSetting.getFieldName().contains(",")){
    			String[] staffIds = fieldValue.split("[,]");
    			for(String staffId : staffIds){
    				if(StringUtils.isNotEmpty(staffId)){
	    				StaffinfoEntity staffinfoEntity = staffInfoService.queryStaffInfoByStaffId(staffId);
	    				if(staffinfoEntity != null && StringUtils.isNotEmpty(staffinfoEntity.getStaffCode())){
	    					candidateUserIdList.add(staffinfoEntity.getStaffCode());
	    				}else {
	    					throw new UserAssignException("通过表单选人出现错误,请检查!");
	    				}
    				}
    			}
    		}else  {
	    	    StaffinfoEntity staffinfoEntity = staffInfoService.queryStaffInfoByStaffId(fieldValue);
	    	    if(StringUtils.isEmpty(fieldValue)){
	    	    	throw new UserAssignException("通过表单选人出现错误,请检查!");
	    	    }
	   	        OrgEntity org = orgService.queryOrgByCode(fieldValue);
	   	        if(!"ShanghaiHQ".equals(fieldValue)){
	   	        	List<HrbpEntity> hrbpEntityList = hrbpService.queryHrbpByDqCode(fieldValue);
	   	        	if(!CollectionUtils.isEmpty(hrbpEntityList)){
	   	        		for(HrbpEntity hrbp : hrbpEntityList){
	   	        			String staffCode = staffInfoService.queryStaffByStaffId(hrbp.getHrbpId()).getStaffCode();
	   	        			if(StringUtils.isNotEmpty(staffCode) && !candidateUserIdList.contains(staffCode)){
	   	        				candidateUserIdList.add(staffCode);
	   	        			}   	        
	   	        		}
	   	        	}
	   	        }
	    		if(staffinfoEntity != null){
					if (UserAssignConstants.USERASSIGN_LEADER_OF_LASTSTEP.equals(assignSetting.getChooseRule()) || 
						UserAssignConstants.USERASSIGN_LEADER_OF_STARTASSIGNEE.equals(assignSetting.getChooseRule())) {
						String directleaderCode = staffInfoService.queryLeader(fieldValue).getDirectLeaderCode();
						if(StringUtils.isNotEmpty(directleaderCode)){
						StaffinfoEntity directLeader = staffInfoService.queryStaffInfoByStaffId(directleaderCode);
							if (directLeader != null) {
								candidateUserIdList.add(directLeader.getStaffCode());
							}
						} 
					}else if(UserAssignConstants.USERASSIGN_LEADER_OF_LEADER.equals(assignSetting.getChooseRule())){
						String indirectleaderCode = staffInfoService.queryLeader(fieldValue).getLeapfrogLeaderCode();  
						StaffinfoEntity indirectLeader = staffInfoService.queryStaffInfoByStaffId(indirectleaderCode);
						if(indirectLeader != null){
							candidateUserIdList.add(indirectLeader.getStaffCode());
						}
					} else if(UserAssignConstants.USERASSIGN_LEADER_OF_LEADER_OF_LEADER.equals(assignSetting.getChooseRule())){
						String indirectleaderCode = staffInfoService.queryLeader(fieldValue).getLeapfrogLeaderCode();  //越级主管
						if(StringUtils.isNotEmpty(indirectleaderCode)){
							StaffinfoEntity indirectLeader = staffInfoService.queryStaffInfoByStaffId(indirectleaderCode);
							if(indirectLeader != null){
								String inIndirectLeaderStaffId = staffInfoService.queryLeader(indirectLeader.getStaffId()).getDirectLeaderCode();
								if(StringUtils.isNotEmpty(inIndirectLeaderStaffId)){
									StaffinfoEntity inIndirectLeader = staffInfoService.queryStaffInfoByStaffId(inIndirectLeaderStaffId);
									if(inIndirectLeader != null){
										candidateUserIdList.add(inIndirectLeader.getStaffCode());
									}
								}
							}
						}
					}
	    			if(UserAssignConstants.USERASSIGN_STARTER.equals(assignSetting.getChooseRule())){
	    					candidateUserIdList.add(staffinfoEntity.getStaffCode());
	    			}
	  
	    		}
			   if(org != null){
			    	if(UserAssignConstants.USERASSIGN_LEADER_OF_ORG.equals(assignSetting.getChooseRule())){//当前部门的领导:
			            if (org != null && !StringUtils.isBlank(org.getManagerCode())) {
			                String managerCode = org.getManagerCode();
			                candidateUserIdList.add(managerCode);
			            }
			    	} else if(UserAssignConstants.USERASSIGN_LEADER_OF_LEADER_OF_ORG.equals(assignSetting.getChooseRule())){//申请部门的上级领导:
			    	    if (org != null && !StringUtils.isBlank(org.getParentCode())) {
			    	        OrgEntity orgParent  = orgService.queryOrgByCode(org.getParentCode());
			    	        if (orgParent!=null && !StringUtils.isBlank(orgParent.getManagerCode())) {
			    	            String managerCode = orgParent.getManagerCode();
			    	            candidateUserIdList.add(managerCode);
			                }
			            }
			    	} else if(UserAssignConstants.USERASSIGN_LEADER_OF_LEADER_OF_LEADER_OF_ORG.equals(assignSetting.getChooseRule())){//申请部门的上级之上级领导:
			             if (org != null && !StringUtils.isBlank(org.getParentCode())) {
			                 OrgEntity orgtwo  = orgService.queryOrgByCode(org.getParentCode());
			                 if (orgtwo!=null && !StringUtils.isBlank(orgtwo.getManagerCode())) {
			                     OrgEntity orgParent = orgService.queryOrgByCode(orgtwo.getParentCode());
			                     if (orgParent!=null && !StringUtils.isBlank(orgParent.getManagerCode())) {
			                         String managerCode = orgParent.getManagerCode();
			                         candidateUserIdList.add(managerCode);
			                    }
			                 }
			             }
			    	} else if(UserAssignConstants.USERASSIGN_LEADER_OF_PLATFORM_OR_COMPANY.equals(assignSetting.getChooseRule())){//申请部门的子公司领导:子公司领导
			    		OrgEntity orgPlatform = orgService.queryPlatformEntityByDepCode(fieldValue);
			    		if (orgPlatform != null && !StringUtils.isBlank(orgPlatform.getManagerCode())) {
			    		    String managerCode = orgPlatform.getManagerCode();
			    		    candidateUserIdList.add(managerCode);
			            }
			    
			    	} else if(UserAssignConstants.USERASSIGN_LEADER_OF_THIRD_ORG.equals(assignSetting.getChooseRule())){//三级部门
			    
						if (OrgConstants.LEVEL_THIRD_ORG.equals(org.getBranchOrg())) {
							if (StringUtils.isNotEmpty(org.getManagerCode())) {
								String managerCode = org.getManagerCode();
								candidateUserIdList.add(managerCode);
							}
						} else if(OrgConstants.LEVEL_SECOND_ORG.equals(org.getBranchOrg()) || OrgConstants.LEVEL_FIRST_ORG.equals(org.getBranchOrg()) 
			            		|| OrgConstants.LEVEL_PLATFORM.equals(org.getBranchOrg())){

			            	if(StringUtils.isNotEmpty(org.getManagerCode())){
			            		candidateUserIdList.add(org.getManagerCode());
			            	}
			            
			    		}//如果是一级部门却要找三级部门,只能提示找不到;
			    	} 
			    	else if(UserAssignConstants.USERASSIGN_LEADER_OF_SECOND_ORG.equals(assignSetting.getChooseRule())){//二级部门
			    			if(OrgConstants.LEVEL_SECOND_ORG.equals(org.getBranchOrg())){
			    				String managerCode = org.getManagerCode();
			    				if(!StringUtils.isBlank(managerCode)){
			    					candidateUserIdList.add(managerCode);
			    				}
			        		}else if(OrgConstants.LEVEL_THIRD_ORG.equals(org.getBranchOrg())){
			        			if (!StringUtils.isBlank(org.getParentCode())) {
			        				OrgEntity orgParent  = orgService.queryOrgByCode(org.getParentCode());
			             	        if (orgParent!=null && !StringUtils.isBlank(orgParent.getManagerCode())) {
			             	            String managerCode = orgParent.getManagerCode();
			             	            candidateUserIdList.add(managerCode);
			                         }
			                     }
			        		} else if(OrgConstants.LEVEL_FIRST_ORG.equals(org.getBranchOrg()) || OrgConstants.LEVEL_PLATFORM.equals(org.getBranchOrg())){
			        			if(StringUtils.isNotEmpty(org.getManagerCode())){
				            		candidateUserIdList.add(org.getManagerCode());
				            	}
			        		}
			    	} else if(UserAssignConstants.USERASSIGN_LEADER_OF_FIRST_ORG.equals(assignSetting.getChooseRule())){//一级部门
			    			if(OrgConstants.LEVEL_FIRST_ORG.equals(org.getBranchOrg())){
			    				String managerCode = org.getManagerCode();
			    				if(!StringUtils.isBlank(managerCode)){
			    					candidateUserIdList.add(managerCode);
			    				}
			        		}else if(OrgConstants.LEVEL_SECOND_ORG.equals(org.getBranchOrg())){
			        			if (!StringUtils.isBlank(org.getParentCode())) {
			        				OrgEntity orgParent  = orgService.queryOrgByCode(org.getParentCode());
			             	        if (orgParent!=null && !StringUtils.isBlank(orgParent.getManagerCode())) {
			             	            String managerCode = orgParent.getManagerCode();
			             	            candidateUserIdList.add(managerCode);
			                         }
			                     }
			        		} else if(OrgConstants.LEVEL_THIRD_ORG.equals(org.getBranchOrg())){
			        			if (!StringUtils.isBlank(org.getParentCode())) {
			        				OrgEntity orgParent  = orgService.queryOrgByCode(org.getParentCode());
			        				if (orgParent != null && !StringUtils.isBlank(orgParent.getParentCode())) {
			        					OrgEntity orgParentPa  = orgService.queryOrgByCode(orgParent.getParentCode());
			             	        if (orgParentPa!=null && !StringUtils.isBlank(orgParentPa.getManagerCode())) {
			             	            String managerCode = orgParentPa.getManagerCode();
			             	            candidateUserIdList.add(managerCode);
			                         }
			                     }
			        			}
			        		} else if(OrgConstants.LEVEL_PLATFORM.equals(org.getBranchOrg())){
			        			if(StringUtils.isNotEmpty(org.getManagerCode())){
				            		candidateUserIdList.add(org.getManagerCode());
				            	}
			                } 
			       } 
			   }
		 }
    }

根据汇报关系,目前有发起人本人,发起人直接主管、越级主管、第二越级主管。同样,除发起人外,还可以通过表单数据指定的工号或用户名找出其汇报关系。

基于角色,同基于组织架构的成员规则相同,即将该角色下的所有成员加入审批人列表

当部门和角色均全选时,将按照其交集不空,输出交集;否则输出全集的逻辑进一步筛选。

代码如下:

 if(candidateUserIdList.isEmpty()){
        if (assignSetting != null){
            //获取直接指定的用户
            if (StringUtils.isNotEmpty(assignSetting.getUserId())) {
                String[] userIds = assignSetting.getUserId().split("[,]");
                for (String user : userIds) { 
                    candidateUserIdList.add(user);
                }
            }
            
            //通过角色和部门选择用户
            Set<String> roleAndDeptSet = getRoleAndDept(assignSetting);
            if(!CollectionUtils.isEmpty(roleAndDeptSet)){
            	 candidateUserIdList.addAll(roleAndDeptSet);
            }
            String startAssignee = "";
            if(StringUtils.isNotEmpty(flowRunTimeInfo.getProcessExecutionId())){
            	startAssignee = processExecutionService.queryById(flowRunTimeInfo.getProcessExecutionId()).getCreateUserCode();
            } else {
            	startAssignee = OAUserContext.getUserCode();
            }

            HashSet<String> allUser = chooseCandidateUser(startAssignee, assignSetting, candidateUserIdList);
            if(!CollectionUtils.isEmpty(allUser)){
            	candidateUserIdList = allUser;
            }
        }
    }

如汇报关系与组织架构的基准并非来自发起人或发起人所属组织时,isForm(即来自表单)需勾选且需填写字段名称。该字段名称所代表的用户或组织即作为智能选人规则的基准。目前情况下,如表单字段值有多个,则只代表直接输出字段所指定的用户,如果无法筛选出用户,则审批人列表显示为空。

代码如下:

  private String getFiledValueByAssignSetting(UserAssignEntity assignSetting, FlowRunTime flowRunTime) {
		String fieldName_ = assignSetting.getFieldName();
        String[] fieldNames = fieldName_.split("[,]");
	    String formData = flowRunTime.getFormData();
	    String formDefineId = flowRunTime.getFormDefineId();
	    String fieldValue = "";
	    for(String fieldName : fieldNames){
		    List<Map<String, Object>> filedValuelist = formDataService.resolveFormData(formDefineId, formData);
		    if(filedValuelist != null && filedValuelist.size() > 0){
		    	Map<String, String> valueMap = new HashMap<String, String>();
		    	for(Map<String, Object> map : filedValuelist){
		    		for(String key : map.keySet()){
		    			if(FormDefineConstants.FORMDEFINE_MAP.equals(key)){
		    				valueMap = (Map<String, String>)map.get(key);
		    			}
		    		}
		    	}
		    	if(!valueMap.isEmpty()){
		    		String value = valueMap.get(fieldName);
		    		if(StringUtils.isNotEmpty(value)){
			    		if("".equals(fieldValue) ){
			    			fieldValue = value;
			    		} else {
			    			fieldValue = fieldValue + "," + value;
			    		}
		    		}
		    	}
		    } else {
		    	throw new UserAssignException(UserAssignConstants.USERASSIGN_EXCEPTION_FORM_ERROR);
		    }
	    }
	    return fieldValue;
	}

另外,我们还提供了直接指定审批人的方法,对一些特殊无法通过以上逻辑筛选,但审批人又固定的即可直接指定审批人。

三、对智能选人规则及任务分配的展望

通过以上分析也可以看出,审批人可以在流程运行期间通过某些规则直接指定,所以可以将其称为建模期(或定义期)指派。即通过流程定义进行建模的期间,直接为流程中的活动指派参与者的集合。而在运行期间才能指定审批人(3.1、3.2)或进行分配任务(3.3、3.4、3.5)的则被称为运行期规则。

3.1同前驱

  顾名思义,就指的是在流程的某个环节的办理人,与当前流程当前环节的某个前驱节眯是同一个人,这也与工作流“保持熟悉(Retain familiar)”原则相切合。

同前驱的选人规则目前无法通过建模期指派实现,比如以下场景:

1.招聘需求有“编制管理员审核”(审批候选人可能有多个)以及“招聘管理员签收”环节,其中要求“招聘管理员签收”环节必须与其前驱节点“编制管理员审核”确定;

2.档案借阅的借出环节与还进环节也要求必须是同一个人。

目前我们已经就这种需求形成两种方案:一种是杜提出的在前驱中后置将某代表其同后继的隐藏工号字段填充,后继则使用表单字段选人将审批人选出;一种是我提出的用同前驱节点及execution id遍历历史任务表,将前驱节点历史审批人拿出做为后继节点审批人。

3.2基于历史、基于能力、随机或循环

以上的三种选人规则,是一种自学习自组织的选人方法。如通过以上选人得出的任选审批人为多个,那么可以使用以上规则筛选为一个或将多人按以规则排序。

  基于历史的是一种笼统的概念,又可以细分为当前任务最少、完成历史任务最多等规则;基于能力则可细分为当前列表中职务最大、层级最高、办理任务耗时最少等;随机则是指列表元素的先后顺序或指定用户是随机的,循环是指列表元素的先后顺序或指定是循环呈现的。

3.3委托\代理、移交\转交

委托是一种运行期间的智能选人模式。对于一个人工任务,在当前活动的参与者出差或休假期间,会用到委托功能。针对委托目前形成的方案是委托模块(委托表除用作工作流选人外,还可能用作),委托表结构如下:

委托人在休假期间新建一个委托,标识委托人、委托时间以及委托的流程(有一些重要流程可能不允许委托)。在每次审批人选出后需要遍历委托表中的有效数据,如当前审批人存在有效委托,则将审批人替换为被委托人,显示名为“被委托人(委托人代理)”,向委托人和被委托人均发送邮件,同时在我的工作中增加“代理工作”模块,将我被代理审批的工作展示出来。

另外的一个委托实现方案来自《流程的永恒之道》一书,即通过另外一个工作流请假单或工作代理单的方式将代理数据写入。代理数据等同于上文中的委托数据。

关于移交\转交:在当前审批者可以使用工作流功能,但由于职责变化或其他原因,将本任务直接转与其他用户。移交\转交功能是下文强行更改审批人的特例。

某用户将属于自己的且正在执行的任务移交给其他人执行。移交模式与委托模式的区别在于,委托是在事前,而移交模式是在事后。

3.4加签

  对于会签节点,存在这样一种可能,会签的目前某参与者或者管理员认为有必要临时将某用户加入会签人列表并为其下发会签任务。加签的实现方案即是:将当前子任务的父任务取出,基于父任务生成一个新的子任务,然后将该子任务分配给该临时用户,并发送提醒邮件。

3.5强行更改审批人

用于某些原因(如当前审批人离职或休假),在运行期间需要将某审批单改分配给另外的用户。逻辑如下:

 /** 
     * 
     * @author chao.gao
     * @date 2014-12-26 上午9:43:40
     * @see com.gaochao.oa.module.bpm.workflow.api.server.service.IProcessActivityService#changePeople(com.gaochao.oa.module.bpm.workflow.api.shared.dto.FlowRunTime)
     * @param flowRunTime
     * @return
     */
    @Override
    public boolean changePeople(FlowRunTime flowRunTime,TaskVo task) {
    	if(StringUtils.isNotEmpty(flowRunTime.getCurrentUser())){
        	ProcessExecutionEntity processExecutionEntity = processExecutionService.queryProcessExecutionByPkValue(flowRunTime.getPkValue());
        
        	List<Task> list = jbpmOperatorService.getTasksByProcessInstanceId(processExecutionEntity.getProcessInstanceId());
        	processExecutionService.changePeople(flowRunTime);    		

    		MailSettingEntity mailSetting = mailSettingService.queryMailSetting();    			

    		Map<String, List<MailblackwhitelistEntity>> mailBlackWhiteListMap = mailblackwhitelistService.queryMailBlackWhiteList();
    		//设置流程处理ID
    		flowRunTime.setProcessExecutionId(processExecutionEntity.getId());
    
        	for(int i=0;i<list.size();i++){
        		Task task1 = list.get(i);
        		task.setTaskId(task1.getId());
        		processTaskService.changePeople(task);
        		//设置任务Id
        		flowRunTime.setNextTaskId(task1.getId());
        		//经办人
        		final String assign = flowRunTime.getCurrentUser();
				jbpmOperatorService.assignTask(task1.getId(), assign);
        
        		//发送邮件
        		EmailUtil.sendMail2Proxy(assign, flowRunTime, mailSetting, mailBlackWhiteListMap);
        	}
    	}else{
            throw new ProcessActivityException("没有选择人员");
        }
        
        return true;
    }
时间: 2024-11-07 00:12:39

jbpm的智能选择审批人及人工任务分配的实现思路的相关文章

不改路由表实现智能选择线路,提升访问国内外网站速度

互联网发明以后,我们可以很容易的去访问世界各地的知识资源.但是受限于网络环境的原因,部分资源我们去访问的时候会很慢,或者访问不了,这时候我们可以通过跳板.隧道.虚拟私有网络等形式去进行访问.在使用虚拟私有网络的过程中,大部分软件会通过修改网关地址将所有数据都通过虚拟私有网络进行传递,然而我们很多时候访问某些特定资源,比如本地资源时并不希望数据通过虚拟私有网络,这怎么办呢? 池建强大哥在这篇博文<VPN – 长城内外,惟余莽莽>里提到了用路由表解决这个问题.可是改路由表这么麻烦的事情,想想都觉得

Jbpm自由流的实现

一.需求定义: 1.允许向已办理过的任意节点流转:从历史任务表中读取以前办理过的任务,从当前任务指向该历史任务节点,并创建该历史节点的新任务,并将原历史任务的审批人分配给现历史任务; 如ABCDE五个节点,目前停留在D节点,点击按钮,跳出包含A.B.C.D下拉菜单的页面,例如选择B,创建D到B的流向,创建基于B的新任务,将历史任务B的审批人分配给新任务B(也应允许重新为新任务B分配审批人),同时向该审批人发送邮件,删除D到B的流向: 2.在历史任务表中应体现以上过程,保留流转的历史痕迹: 3.该

智能投顾的发展现状和未来发展趋势

2017-3-6 10:05  来自: 36kr  "These violent delights have violent ends"(狂暴的欢愉必将有狂暴的结局)--<西部世界>里这句出自莎翁笔下的经典台词,用来形容近几年国内P2P市场的火爆与乱象恰到好处.从余额宝引发的欢愉,到招财宝"侨兴债"违约带来的阵痛,P2P从最初的野蛮生长到最近的反思整顿,完成了一个周期,市场反复教育投资者:收益和风险正相关,只有根据自己的风险偏好进行分散配置才是健康的理财

“头雁”效应AI智博会 2019北京第四 2019第四届国际人工智能产品展览会

人工智能是引领新一轮科技革命和产业变革的战略性技术,具有溢出带动性很强的"头雁"效应.当前,我国人工智能产业发展环境日益完善,关键技术产品研发应用进程加快,产业规模不断壮大,但是,我国人工智能在基础支撑.技术研发.政策环境和人才供给等方面仍存在亟待解决的问题.人工智能技术发展和应用将对政府管理.经济发展和社会稳定乃至全球治理产生深远影响. 2019北京国际人工智能展览会作为北京智博会的专题展,作为中国最具影响力.规模和水平的国际品牌展,智能制造解决方案.人工智能技术应用最集中的展示.交

智能算法|有哪些以动物命名的算法?

黄梅时节家家雨,青草池塘处处蛙. 有约不来过夜半,闲敲棋子落灯花. 鱼群算法?鸟群算法?蝙蝠算法?蚁群算法?病毒算法?...what?这些是什么沙雕算法? 别看这些算法名字挺接地气的,实际上确实很接地气... 以动物命名的算法可远不止这些,俗话说得好,只要脑洞大,就能玩出新花样,这句话在启发式算法界绝对名副其实!然鹅什么是启发式算法呢? 启发式算法:一个基于直观或经验构造的算法,在可接受的花费(指计算时间和空间)下给出待解决组合优化问题每一个实例的一个可行解,该可行解与最优解的偏离程度一般不能被

智能语音人机交互产业链及关键技术分析

人机交互是一门计算机科学,主要研究关于设计.评价和实现供人们使用的交互计算系统以及相关现象的科学.人机交互的发展经历了以下几个阶段:手工作业阶段.作业控制语言与交互命令语言阶段.图形用户界面(GUI)阶段.网络用户界面,目前已经发展到多通道.多媒体的智能人机交互阶段.其中,语音人机交互是当前多通道.多媒体智能人机交互的主要方式.特别是苹果Siri.科大讯飞语点的出现,让智能语音人机交互技术实现了新的跨越,得到了社会各界的广泛关注. 一.智能语音人机交互产业发展现状 什么是智能语音人机交互技术?简

solr4.x配置IK2012FF智能分词+同义词配置

本文配置环境:solr4.6+ IK2012ff +tomcat7 在Solr4.0发布以后,官方取消了BaseTokenizerFactory接口,而直接使用Lucene Analyzer标准接口TokenizerFactory.因此IK分词器2012 FF版本也取消了org.wltea.analyzer.solr.IKTokenizerFactory类. 这里IK的文档给了一个solr的配置如下: <fieldType name="text" class="solr

AI赋能 引领智能家居交互方式和商业模式改变

家庭的智能化以用户体验为核心,旨在让用户在家庭场景中像呼吸空气一样享受智能化应用,其基础还是家庭终端产品.而家庭网关不仅成为衔接内外的枢纽,更是整个家庭网络最重要的控制中心.因此,家庭网关的智能化也成为实现智慧家庭的必要条件之一. 智能家居市场规模巨大是公认的事实,我们可以从一些行业数据中拼凑出这个"规模"的大概值.引用市场研究公司statista在2016年发布的一组数据:去年我国智能家居市场规模达403.40亿元,同比增长41%,预计到2018年,我国智能家居市场规模将达到1300

文本内容超出宽度智能换行

style='word-break: keep-all;' 添加上面样式后的效果:红框内 去掉上面style样式后的效果如下图: 各取所需,如果宽度不够显示所有文字,则智能选择上一个空格的地方"换行"显示   还适用于日期展示,年月日与时间的展示