用影刀RPA抓取"影刀RPA帮助中心"所有层级类目文档链接,并导出Excel | 网页监听实例
2026/1/8 2:59:26
你提供的这段代码是Activiti工作流中查询指定任务的办理时间轴接口,核心业务需求如下:
taskId),先获取对应的流程实例ID(processInstanceId);TaskInfo对象,包含任务名称、办理人、开始/结束时间、审批意见等核心信息;StringprocessInstanceId=taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();TaskService的taskQuery按taskId查询具体任务,获取该任务所属的流程实例ID;singleResult()可能返回null(如taskId不存在),直接调用getProcessInstanceId()会抛出空指针异常。List<HistoricActivityInstance>history=historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)// 按流程实例过滤.activityType("userTask")// 仅查询用户任务(排除网关、开始/结束节点等).orderByHistoricActivityInstanceStartTime().asc()// 按开始时间升序(时间轴顺序).list();HistoryService的HistoricActivityInstanceQuery查询流程实例的历史活动记录;activityType("userTask"):只保留人工办理的任务,过滤自动节点(如网关、服务任务),符合“办理时间轴”的业务场景;List<TaskInfo>infos=newArrayList<>();SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");history.stream().forEach(h->{TaskInfoinfo=newTaskInfo();info.setProcessInstanceId(h.getProcessInstanceId());info.setStartTime(sdf.format(h.getStartTime()));if(h.getEndTime()!=null){info.setEndTime(sdf.format(h.getEndTime()));}info.setAssignee(h.getAssignee());// 任务办理人info.setTaskName(h.getActivityName());// 任务名称(如“采购经理审批”)// 查询任务审批意见List<Comment>comments=taskService.getTaskComments(h.getTaskId());if(comments.size()>0){info.setComment(comments.get(0).getFullMessage());}infos.add(info);});HistoricActivityInstance对象转换为自定义TaskInfo(前端DTO),屏蔽框架底层字段,只返回业务所需信息;Date类型的开始/结束时间转换为“yyyy-MM-dd HH:mm:ss”字符串,便于前端展示;taskService.getTaskComments(h.getTaskId())查询任务的审批备注(评论),取第一条作为核心意见。原代码存在多处空指针风险,需补充校验:
@ApiOperation("任务办理时间轴")@RequestMapping(value="/history/{taskId}",method=RequestMethod.GET)@ResponseBodypublicList<TaskInfo>history(@PathVariableStringtaskId){// 1. 校验入参if(StringUtils.isBlank(taskId)){returnCollections.emptyList();// 或返回AjaxResult提示参数为空}// 2. 查询任务时校验nullTasktask=taskService.createTaskQuery().taskId(taskId).singleResult();if(task==null){returnCollections.emptyList();// 无对应任务,返回空列表}StringprocessInstanceId=task.getProcessInstanceId();// 3. 查询历史任务List<HistoricActivityInstance>history=historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("userTask").orderByHistoricActivityInstanceStartTime().asc().list();List<TaskInfo>infos=newArrayList<>();SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 4. 遍历历史任务时避免空指针for(HistoricActivityInstanceh:history){if(h==null){continue;}TaskInfoinfo=newTaskInfo();info.setProcessInstanceId(h.getProcessInstanceId());// 时间格式化校验if(h.getStartTime()!=null){info.setStartTime(sdf.format(h.getStartTime()));}if(h.getEndTime()!=null){info.setEndTime(sdf.format(h.getEndTime()));}info.setAssignee(h.getAssignee());info.setTaskName(h.getActivityName());// 审批意见查询(校验taskId非空)if(StringUtils.isNotBlank(h.getTaskId())){List<Comment>comments=taskService.getTaskComments(h.getTaskId());if(comments!=null&&!comments.isEmpty()){info.setComment(comments.get(0).getFullMessage());}}infos.add(info);}returninfos;}taskService.getTaskComments(h.getTaskId())都会发起一次数据库查询,若历史任务较多(如10+),会产生N+1查询问题;// 1. 先收集所有历史任务IDList<String>taskIds=history.stream().filter(h->StringUtils.isNotBlank(h.getTaskId())).map(HistoricActivityInstance::getTaskId).collect(Collectors.toList());// 2. 批量查询评论(需自定义SQL/扩展API,Activiti原生无批量查询评论接口)// 示例:通过自定义Mapper批量查询Map<String,Comment>taskCommentMap=commentMapper.getCommentByTaskIds(taskIds);// 3. 遍历历史任务时直接从Map取值for(HistoricActivityInstanceh:history){// ... 其他赋值逻辑Commentcomment=taskCommentMap.get(h.getTaskId());if(comment!=null){info.setComment(comment.getFullMessage());}}SimpleDateFormat是非线程安全的,若接口高并发调用,可能出现时间格式化错误;DateTimeFormatter(Java 8+)替代:DateTimeFormatterformatter=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// 时间格式化if(h.getStartTime()!=null){info.setStartTime(h.getStartTime().toInstant().atZone(ZoneId.systemDefault()).format(formatter));}if(h.getEndTime()!=null){info.setEndTime(h.getEndTime().toInstant().atZone(ZoneId.systemDefault()).format(formatter));}补充全局异常捕获,避免接口抛出未处理的异常:
@ApiOperation("任务办理时间轴")@RequestMapping(value="/history/{taskId}",method=RequestMethod.GET)@ResponseBodypublicAjaxResulthistory(@PathVariableStringtaskId){// 改为返回统一响应体try{// 核心逻辑(上述优化后的代码)List<TaskInfo>infos=...;returnAjaxResult.success(infos);}catch(Exceptione){log.error("查询任务办理时间轴失败,taskId:{}",taskId,e);returnAjaxResult.error("查询任务办理轨迹失败:"+e.getMessage());}}TaskService/HistoryService(领域层)完成数据查询,符合分层架构规范;TaskInfo封装前端所需字段,实现“领域模型→展示模型”的解耦,避免直接返回框架原生对象。// 扩展:仅查询指定办理人的任务historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("userTask").assignee("zhangsan")// 扩展过滤条件.list();DateTimeFormatter结合Locale);@PreAuthorize),仅允许流程参与人/管理员查询时间轴;log.info("查询任务办理时间轴,taskId:{}",taskId);log.info("查询结果:{}",infos.size());| 维度 | 核心要点 |
|---|---|
| 需求核心 | 按任务ID关联流程实例,查询所有用户任务的历史轨迹,格式化后返回办理时间、办理人、审批意见等信息 |
| 设计亮点 | 聚焦“用户任务”过滤非业务节点,按时间升序保证时间轴顺序,适配前端展示格式 |
| 优化方向 | 1. 补充空指针校验;2. 替换线程不安全的SimpleDateFormat;3. 批量查询评论避免N+1;4. 统一异常处理和日志;5. 添加权限控制 |
| 扩展场景 | 支持多条件过滤、多语言时间格式化、批量返回审批意见 |
该接口是工作流审批轨迹展示的核心接口,优化后可解决空指针、性能、线程安全等问题,同时保持良好的扩展性,适配企业级应用的生产环境需求。