From 83c9fdf65bd76fad66a67956a8a0912df55c4bbd Mon Sep 17 00:00:00 2001 From: snowgitea Date: Wed, 1 Jul 2026 17:55:54 +0800 Subject: [PATCH] chore: src/modules/base/db/tenant.ts --- src/modules/base/db/tenant.ts | 213 ++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 src/modules/base/db/tenant.ts diff --git a/src/modules/base/db/tenant.ts b/src/modules/base/db/tenant.ts new file mode 100644 index 0000000..5b121c5 --- /dev/null +++ b/src/modules/base/db/tenant.ts @@ -0,0 +1,213 @@ +import { EventSubscriberModel } from '@midwayjs/typeorm'; +import { + DeleteQueryBuilder, + EntitySubscriberInterface, + InsertQueryBuilder, + SelectQueryBuilder, + UpdateQueryBuilder, +} from 'typeorm'; +import * as _ from 'lodash'; +import { + App, + ASYNC_CONTEXT_KEY, + ASYNC_CONTEXT_MANAGER_KEY, + AsyncContextManager, + Config, + IMidwayApplication, + IMidwayContext, + Inject, +} from '@midwayjs/core'; +import { Utils } from '../../../comm/utils'; +import { CoolUrlTagData, TagTypes } from '@cool-midway/core'; + +/** + * 不操作租户 + * @param ctx + * @param func + */ +export const noTenant = async (ctx, func) => { + let result; + const tenantId = ctx?.admin?.tenantId; + if (tenantId) { + ctx.admin.tenantId = null; + result = await func(); + ctx.admin.tenantId = tenantId; + } else { + result = await func(); + } + return result; +}; + +@EventSubscriberModel() +export class TenantSubscriber implements EntitySubscriberInterface { + @App() + app: IMidwayApplication; + + @Inject() + ctx: IMidwayContext; + + @Inject() + coolUrlTagData: CoolUrlTagData; + + @Config('cool.tenant') + tenant: { + // 是否开启多租户 + enable: boolean; + // 需要过滤多租户的url + urls: string[]; + }; + + // 系统接口不过滤 + ignoreUrls = [ + '/admin/base/open/login', + '/admin/base/comm/person', + '/admin/base/comm/permmenu', + '/admin/dict/info/data', + ]; + + // 不进行租户过滤的用户 + ignoreUsername = []; + + @Inject() + utils: Utils; + + /** + * 获取所有忽略的url + */ + getAllIgnoreUrls() { + const adminIgnoreUrls = this.coolUrlTagData.byKey( + TagTypes.IGNORE_TOKEN, + 'admin' + ); + const appIgnoreUrls = this.coolUrlTagData.byKey( + TagTypes.IGNORE_TOKEN, + 'app' + ); + this.ignoreUrls = [ + ...this.ignoreUrls, + ...adminIgnoreUrls, + ...appIgnoreUrls, + ]; + // 去重 + this.ignoreUrls = _.uniq(this.ignoreUrls); + return this.ignoreUrls; + } + + /** + * 检查是否需要租户 + */ + checkHandler() { + const ctx = this.getCtx(); + if (!ctx) return false; + const url = ctx?.url; + if (!url) return false; + if (this.tenant?.enable) { + const isNeedTenant = this.tenant.urls.some(pattern => + this.utils.matchUrl(pattern, url) + ); + return isNeedTenant; + } + return false; + } + + /** + * 获取ctx + */ + getCtx(): any { + try { + const contextManager: AsyncContextManager = this.app + .getApplicationContext() + .get(ASYNC_CONTEXT_MANAGER_KEY); + return contextManager.active().getValue(ASYNC_CONTEXT_KEY); + } catch (error) { + return null; + } + } + + /** + * 从登录的用户中获取租户ID + * @returns string | undefined + */ + getTenantId(): number | undefined { + let ctx, url, tenantId; + ctx = this.getCtx(); + if (!ctx || !this.checkHandler()) return undefined; + url = ctx?.url; + // 忽略用户 + if (this.ignoreUsername.includes(ctx?.admin?.username)) { + return undefined; + } + // 忽略系统接口 + if ( + this.getAllIgnoreUrls().some(pattern => this.utils.matchUrl(pattern, url)) + ) { + return undefined; + } + if (_.startsWith(url, '/admin/')) { + tenantId = ctx?.admin?.tenantId; + } else if (_.startsWith(url, '/app/')) { + tenantId = ctx?.user?.tenantId; + } + if (tenantId && url) { + return tenantId; + } + return undefined; + } + + /** + * 查询时添加租户ID条件 + * @param queryBuilder + */ + afterSelectQueryBuilder(queryBuilder: SelectQueryBuilder) { + if (!this.tenant?.enable) return; + const tenantId = this.getTenantId(); + if (tenantId) { + queryBuilder.andWhere( + `${ + queryBuilder.alias ? queryBuilder.alias + '.' : '' + }tenantId = '${tenantId}'` + ); + } + } + + /** + * 插入时添加租户ID + * @param queryBuilder + */ + afterInsertQueryBuilder(queryBuilder: InsertQueryBuilder) { + if (!this.tenant?.enable) return; + const tenantId = this.getTenantId(); + if (tenantId) { + const values = queryBuilder.expressionMap.valuesSet; + if (Array.isArray(values)) { + queryBuilder.values(values.map(item => ({ ...item, tenantId }))); + } else if (typeof values === 'object') { + queryBuilder.values({ ...values, tenantId }); + } + } + } + + /** + * 更新时添加租户ID和条件 + * @param queryBuilder + */ + afterUpdateQueryBuilder(queryBuilder: UpdateQueryBuilder) { + if (!this.tenant?.enable) return; + const tenantId = this.getTenantId(); + if (tenantId) { + queryBuilder.andWhere(`tenantId = '${tenantId}'`); + } + } + + /** + * 删除时添加租户ID和条件 + * @param queryBuilder + */ + afterDeleteQueryBuilder(queryBuilder: DeleteQueryBuilder) { + if (!this.tenant?.enable) return; + const tenantId = this.getTenantId(); + if (tenantId) { + queryBuilder.andWhere(`tenantId = '${tenantId}'`); + } + } +}