如何利用Activiti 6.0.0与PostgreSQL实现多租户?并搭建Angular自定义UI
Activiti 6.0.0 + PostgreSQL 多租户实现指南(附Angular UI适配)
一、先选对多租户模式
Activiti 6.0.0支持三种多租户方案,结合PostgreSQL的特性,我推荐优先用共享数据库+共享表模式(每个流程实例/任务都带tenant_id字段区分)——这种模式轻量易维护,适合中小规模租户场景。如果租户数据隔离要求极高,再考虑单独表或单独数据库方案,这里重点讲最常用的共享表模式。
二、PostgreSQL数据库准备
- 先创建主数据库(比如
activiti_multi_tenant),直接执行Activiti 6的初始化SQL脚本就行——Activiti 6的默认脚本已经在核心表(ACT_RE_PROCDEF、ACT_RU_EXECUTION、ACT_RU_TASK等)中内置了tenant_id字段,不用额外手动添加。 - 要是选单独表模式,需要为每个租户创建带前缀的表(比如
TENANT_A_ACT_RE_PROCDEF),PostgreSQL完美支持表前缀,这时候初始化脚本要动态替换表前缀。 - 单独数据库模式的话,给每个租户建独立的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
相关产品推荐
相关产品推荐

