江苏省网站建设_网站建设公司_API接口_seo优化
2026/1/7 23:52:25 网站建设 项目流程

一、接口核心需求分析

你提供的这段代码是Activiti工作流中查询指定任务的办理时间轴接口,核心业务需求如下:

  1. 关联查询:根据传入的任务ID(taskId),先获取对应的流程实例ID(processInstanceId);
  2. 历史轨迹查询:查询该流程实例下所有“用户任务(userTask)”的历史执行记录,按任务开始时间升序排列;
  3. 数据格式化:将历史任务记录转换为前端易展示的TaskInfo对象,包含任务名称、办理人、开始/结束时间、审批意见等核心信息;
  4. 结果返回:返回结构化的任务办理时间轴列表,支撑前端展示流程审批轨迹。

二、代码逐段解析(核心逻辑+设计思路)

1. 流程实例ID关联
StringprocessInstanceId=taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
  • 设计思路
    • 通过TaskServicetaskQuerytaskId查询具体任务,获取该任务所属的流程实例ID;
    • 这是“任务→流程实例→历史任务”关联的核心步骤,因为历史任务需按流程实例维度查询。
  • 风险点singleResult()可能返回null(如taskId不存在),直接调用getProcessInstanceId()会抛出空指针异常。
2. 历史用户任务查询
List<HistoricActivityInstance>history=historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)// 按流程实例过滤.activityType("userTask")// 仅查询用户任务(排除网关、开始/结束节点等).orderByHistoricActivityInstanceStartTime().asc()// 按开始时间升序(时间轴顺序).list();
  • 设计思路
    • 借助HistoryServiceHistoricActivityInstanceQuery查询流程实例的历史活动记录;
    • 核心过滤条件activityType("userTask"):只保留人工办理的任务,过滤自动节点(如网关、服务任务),符合“办理时间轴”的业务场景;
    • 排序规则:按任务开始时间升序,保证时间轴从流程发起→后续审批的顺序展示。
3. 历史数据转换为前端DTO
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);});
  • 设计思路
    • 数据适配:将Activiti原生的HistoricActivityInstance对象转换为自定义TaskInfo(前端DTO),屏蔽框架底层字段,只返回业务所需信息;
    • 时间格式化:将Date类型的开始/结束时间转换为“yyyy-MM-dd HH:mm:ss”字符串,便于前端展示;
    • 审批意见关联:通过taskService.getTaskComments(h.getTaskId())查询任务的审批备注(评论),取第一条作为核心意见。

三、核心问题与优化方案

1. 空指针异常(最高优先级优化)

原代码存在多处空指针风险,需补充校验:

@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;}
2. 性能优化
  • 问题:遍历历史任务时,每次调用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());}}
3. 时间格式化优化
  • 问题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));}
4. 异常处理

补充全局异常捕获,避免接口抛出未处理的异常:

@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());}}

四、设计维度温习

1. 架构设计
  • 分层设计:接口属于“应用层”,依赖Activiti的TaskService/HistoryService(领域层)完成数据查询,符合分层架构规范;
  • DTO设计:通过TaskInfo封装前端所需字段,实现“领域模型→展示模型”的解耦,避免直接返回框架原生对象。
2. 扩展性设计
  • 多维度过滤:可扩展支持按“办理人、任务状态、时间范围”过滤历史任务,例如:
    // 扩展:仅查询指定办理人的任务historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("userTask").assignee("zhangsan")// 扩展过滤条件.list();
  • 多语言适配:时间格式化可扩展支持国际化(如DateTimeFormatter结合Locale);
  • 审批意见扩展:原代码仅取第一条评论,可扩展返回所有评论,或按评论类型过滤(如“审批意见/备注”)。
3. 安全与规范
  • 权限控制:建议添加权限校验(如@PreAuthorize),仅允许流程参与人/管理员查询时间轴;
  • 日志规范:添加入参、出参日志,便于问题排查:
    log.info("查询任务办理时间轴,taskId:{}",taskId);log.info("查询结果:{}",infos.size());

五、核心总结

维度核心要点
需求核心按任务ID关联流程实例,查询所有用户任务的历史轨迹,格式化后返回办理时间、办理人、审批意见等信息
设计亮点聚焦“用户任务”过滤非业务节点,按时间升序保证时间轴顺序,适配前端展示格式
优化方向1. 补充空指针校验;2. 替换线程不安全的SimpleDateFormat;3. 批量查询评论避免N+1;4. 统一异常处理和日志;5. 添加权限控制
扩展场景支持多条件过滤、多语言时间格式化、批量返回审批意见

该接口是工作流审批轨迹展示的核心接口,优化后可解决空指针、性能、线程安全等问题,同时保持良好的扩展性,适配企业级应用的生产环境需求。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询