You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用Activiti 6.0.0与PostgreSQL实现多租户?并搭建Angular自定义UI

Activiti 6.0.0 + PostgreSQL 多租户实现指南(附Angular UI适配)

一、先选对多租户模式

Activiti 6.0.0支持三种多租户方案,结合PostgreSQL的特性,我推荐优先用共享数据库+共享表模式(每个流程实例/任务都带tenant_id字段区分)——这种模式轻量易维护,适合中小规模租户场景。如果租户数据隔离要求极高,再考虑单独表或单独数据库方案,这里重点讲最常用的共享表模式。

二、PostgreSQL数据库准备

  1. 先创建主数据库(比如activiti_multi_tenant),直接执行Activiti 6的初始化SQL脚本就行——Activiti 6的默认脚本已经在核心表(ACT_RE_PROCDEF、ACT_RU_EXECUTION、ACT_RU_TASK等)中内置了tenant_id字段,不用额外手动添加。
  2. 要是选单独表模式,需要为每个租户创建带前缀的表(比如TENANT_A_ACT_RE_PROCDEF),PostgreSQL完美支持表前缀,这时候初始化脚本要动态替换表前缀。
  3. 单独数据库模式的话,给每个租户建独立的PostgreSQL数据库,后续配置多数据源即可。

三、Activiti 6核心配置(Java后端部分)

1. 流程引擎配置(共享表模式)

在Spring环境下,配置SpringProcessEngineConfiguration时开启多租户支持:

@Bean
public ProcessEngineConfiguration processEngineConfiguration(DataSource dataSource) {
    SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
    config.setDataSource(dataSource);
    config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
    // 开启多租户,指定数据库中租户ID的列名
    config.setTenantIdColumn("tenant_id");
    config.setMultiTenancyEnabled(true);
    // 配置租户ID获取策略:从线程上下文取,适配Web请求场景
    config.setTenantIdProvider(new TenantIdProvider() {
        @Override
        public String getTenantId(CommandContext commandContext) {
            // 从自定义的上下文Holder中取租户ID,后续请求拦截器会把租户ID存到这里
            return TenantContextHolder.getTenantId();
        }
    });
    return config;
}

2. 租户上下文管理

写一个工具类来管理线程中的租户ID,避免请求之间的租户信息串混:

public class TenantContextHolder {
    private static final ThreadLocal<String> tenantIdHolder = new ThreadLocal<>();

    public static void setTenantId(String tenantId) {
        tenantIdHolder.set(tenantId);
    }

    public static String getTenantId() {
        return tenantIdHolder.get();
    }

    public static void clear() {
        tenantIdHolder.remove();
    }
}

3. 请求拦截器处理租户ID

在Spring MVC拦截器中,从请求头(或参数)提取租户ID,存入上下文Holder:

public class TenantInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求头取租户ID,Angular端请求时会带上这个头
        String tenantId = request.getHeader("X-Tenant-ID");
        if (StringUtils.isNotEmpty(tenantId)) {
            TenantContextHolder.setTenantId(tenantId);
        } else {
            // 没有租户ID直接返回403,也可以根据业务逻辑设默认租户
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Tenant ID is required");
            return false;
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 请求结束后清空上下文,防止内存泄漏
        TenantContextHolder.clear();
    }
}

记得把这个拦截器注册到Spring的配置类里生效。

4. 部署流程时绑定租户ID

部署流程定义时必须指定租户ID,这样后续生成的流程实例会自动关联该租户:

@Autowired
private RepositoryService repositoryService;

public void deployProcessForTenant(String tenantId, InputStream processXml) {
    repositoryService.createDeployment()
            .addInputStream("leave-process.bpmn", processXml)
            .tenantId(tenantId) // 关键步骤:给流程部署绑定租户ID
            .deploy();
}

四、Angular UI适配要点

1. 全局HTTP拦截器

写一个Angular拦截器,给所有请求自动带上租户ID请求头——租户ID可以存在localStorage里,用户选择租户后存入:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class TenantInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const tenantId = localStorage.getItem('currentTenantId');
    if (tenantId) {
      const modifiedReq = req.clone({
        setHeaders: {
          'X-Tenant-ID': tenantId
        }
      });
      return next.handle(modifiedReq);
    }
    return next.handle(req);
  }
}

在app.module.ts的providers中注册这个拦截器。

2. 租户选择界面

用户登录后,展示租户选择下拉框,选择后把租户ID存入localStorage,后续请求就会自动带上:

<div class="tenant-selector">
  <label>选择租户:</label>
  <select (change)="onTenantSelected($event)">
    <option *ngFor="let tenant of tenantList" [value]="tenant.id">{{tenant.name}}</option>
  </select>
</div>
onTenantSelected(event: Event) {
  const selectedTenantId = (event.target as HTMLSelectElement).value;
  localStorage.setItem('currentTenantId', selectedTenantId);
  // 刷新页面或重新加载数据,确保后续请求用新的租户ID
  window.location.reload();
}

3. 数据展示自动隔离

因为后端已经通过TenantIdProvider自动过滤了当前租户的数据,Angular只需要正常调用接口即可,比如获取当前租户的待办任务:

getMyTasks(): Observable<Task[]> {
  return this.http.get<Task[]>('/api/activiti/tasks');
}

后端返回的就是当前租户下的任务列表,不用额外加过滤条件。

五、关键注意事项

  • 流程部署必须绑定租户ID,否则会变成无租户流程,所有租户都能访问(除非业务允许这种情况)。
  • 共享表模式下,给PostgreSQL核心表的tenant_id字段加索引,比如ACT_RU_TASK、ACT_RU_EXECUTION,提升查询性能。
  • 除了租户隔离,还要结合Activiti的用户组权限,确保租户内的用户只能看到自己权限范围内的任务和流程。
  • 如果用单独数据库模式,需要配置多数据源并动态切换,复杂度会高很多,适合超大规模租户场景。

内容的提问来源于stack exchange,提问作者Amit Bhoyar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 11:05:12