分析问题:flowable流程comment表用户是4个小时前的用户

现象:在某个审批方法中,直接使用taskService.addComment方法添加批注。但业务反馈,这条批注信息的提交用户是另一位同事!

js 复制代码
 taskService.addComment(taskId, pi.getProcessInstanceId(), comment.getFullMessage());

分析源码: 发现用户id:userId 是通过Authentication.getAuthenticatedUserId()获取!!!

js 复制代码
 public class AddCommentCmd implements Command<Comment> {

    protected String taskId;
    protected String processInstanceId;
    protected String type;
    protected String message;

    public AddCommentCmd(String taskId, String processInstanceId, String message) {
        this.taskId = taskId;
        this.processInstanceId = processInstanceId;
        this.message = message;
    }

    public AddCommentCmd(String taskId, String processInstanceId, String type, String message) {
        this.taskId = taskId;
        this.processInstanceId = processInstanceId;
        this.type = type;
        this.message = message;
    }

    @Override
    public Comment execute(CommandContext commandContext) {

        TaskEntity task = null;
        // Validate task
        if (taskId != null) {
            task = CommandContextUtil.getTaskService().getTask(taskId);

            if (task == null) {
                throw new FlowableObjectNotFoundException("Cannot find task with id " + taskId, Task.class);
            }

            if (task.isSuspended()) {
                throw new FlowableException(getSuspendedTaskException());
            }
        }

        ExecutionEntity execution = null;
        if (processInstanceId != null) {
            execution = CommandContextUtil.getExecutionEntityManager(commandContext).findById(processInstanceId);

            if (execution == null) {
                throw new FlowableObjectNotFoundException("execution " + processInstanceId + " doesn't exist", Execution.class);
            }

            if (execution.isSuspended()) {
                throw new FlowableException(getSuspendedExceptionMessage());
            }
        }

        String processDefinitionId = null;
        if (execution != null) {
            processDefinitionId = execution.getProcessDefinitionId();
        } else if (task != null) {
            processDefinitionId = task.getProcessDefinitionId();
        }

        if (processDefinitionId != null && Flowable5Util.isFlowable5ProcessDefinitionId(commandContext, processDefinitionId)) {
            Flowable5CompatibilityHandler compatibilityHandler = Flowable5Util.getFlowable5CompatibilityHandler();
            return compatibilityHandler.addComment(taskId, processInstanceId, type, message);
        }
        // 注意此处, userId 是通过Authentication.getAuthenticatedUserId()获取!!!
        String userId = Authentication.getAuthenticatedUserId();
        CommentEntity comment = CommandContextUtil.getCommentEntityManager(commandContext).create();
        comment.setUserId(userId);
        comment.setType((type == null) ? CommentEntity.TYPE_COMMENT : type);
        comment.setTime(CommandContextUtil.getProcessEngineConfiguration(commandContext).getClock().getCurrentTime());
        comment.setTaskId(taskId);
        comment.setProcessInstanceId(processInstanceId);
        comment.setAction(Event.ACTION_ADD_COMMENT);

        String eventMessage = message.replaceAll("\\s+", " ");
        if (eventMessage.length() > 163) {
            eventMessage = eventMessage.substring(0, 160) + "...";
        }
        comment.setMessage(eventMessage);

        comment.setFullMessage(message);

        CommandContextUtil.getCommentEntityManager(commandContext).insert(comment);

        return comment;
    }

    protected String getSuspendedTaskException() {
        return "Cannot add a comment to a suspended task";
    }

    protected String getSuspendedExceptionMessage() {
        return "Cannot add a comment to a suspended execution";
    }
}

继续分析 Authentication ,认证用户 是通过 AuthenticationContext 保存

js 复制代码
    public abstract class Authentication {
    // 认证用户 是通过 AuthenticationContext 保存
    private static AuthenticationContext authenticationContext = new UserIdAuthenticationContext();

    public static void setAuthenticatedUserId(String authenticatedUserId) {
        authenticationContext.setPrincipal(authenticatedUserId == null ? null : new UserIdPrincipal(authenticatedUserId));
    }

    public static String getAuthenticatedUserId() {
        return authenticationContext.getAuthenticatedUserId();
    }

    public static AuthenticationContext getAuthenticationContext() {
        return authenticationContext;
    }

    public static void setAuthenticationContext(AuthenticationContext authenticationContext) {
        Authentication.authenticationContext = authenticationContext;
    }
}

再继续分析AuthenticationContext

js 复制代码
public class UserIdAuthenticationContext implements AuthenticationContext {
    //此处是通过 ThreadLocal 将userid保存起来
    private static ThreadLocal<Principal> authenticatedUserIdThreadLocal = new ThreadLocal();

    public UserIdAuthenticationContext() {
    }

    public String getAuthenticatedUserId() {
        Principal principal = (Principal)authenticatedUserIdThreadLocal.get();
        return principal == null ? null : principal.getName();
    }

    public Principal getPrincipal() {
        return (Principal)authenticatedUserIdThreadLocal.get();
    }

    public void setPrincipal(Principal principal) {
        authenticatedUserIdThreadLocal.set(principal);
    }
}

现在问题已经很清晰了。首先addComment方法添加的认证用户信息并不是通过task获取,而是从ThreadLocal中获取,再回头看代码,Controller层接收任务后,直接添加comment

同时结合日志,执行操作时,当前线程是tomcat线程池中的常驻线程。也就是说,该工作线程并不会因为执行结束而销毁,而且对应的ThreadLocal也一直未被回收。导致4个小时候添加用户时,存到库里的不是操作用户。

解决方法,在addcomment前,手动注入。

js 复制代码
Authentication.setAuthenticatedUserId(task.getAssignee());
相关推荐
程序猿DD1 小时前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
余槐i3 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8264 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
凤山老林5 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne6 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF6 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li7 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
upala7 小时前
【仓颉语言入门 · 第二篇 第 6 课】
后端
罗丹丹Luna7 小时前
移动端显示“已提交”以后:SAP 与低代码集成该怎样暴露真实状态
后端·架构
码事漫谈7 小时前
因为太想讨好你,所以AI会骗你
后端