wangzhibo
7 天以前 665ba0f9ae078c2859209451a163d10515171d11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
---
description: 多租户(Tenant)
globs: 
---
# 多租户(v8.0新增)
 
多租户(Multi-tenancy)是一种软件架构模式,允许单个应用实例服务多个租户(客户组织)。每个租户的数据是相互隔离的,但共享同一个应用程序代码和基础设施。
 
 
## 主要特点
 
- **数据隔离**: 确保不同租户之间的数据严格分离,互不可见
- **资源共享**: 多个租户共享同一套应用程序代码和基础设施
- **独立配置**: 每个租户可以有自己的个性化配置和定制化需求
- **成本优化**: 通过资源共享降低运营和维护成本
 
## 实现
 
### 1、数据隔离
 
多租户的数据隔离有许多种方案,但最为常见的是以列进行隔离的方式。Cool Admin 通过在`BaseEntity`中加入指定的列(租户ID `tenantId`)对数据进行隔离。
 
::: tip 小贴士
 
v8.0之后,`BaseEntity`已经从`cool-midway/core`中移动至`src/modules/base/entity/base.ts`,方便开发者扩展定制
 
:::
 
 
`src/modules/base/entity/base.ts`
```ts
import {
  Index,
  UpdateDateColumn,
  CreateDateColumn,
  PrimaryGeneratedColumn,
  Column,
} from 'typeorm';
import { CoolBaseEntity } from '@cool-midway/core';
 
/**
 * 实体基类
 */
export abstract class BaseEntity extends CoolBaseEntity {
  // 默认自增
  @PrimaryGeneratedColumn('increment', {
    comment: 'ID',
  })
  id: number;
 
  @Index()
  @CreateDateColumn({ comment: '创建时间' })
  createTime: Date;
 
  @Index()
  @UpdateDateColumn({ comment: '更新时间' })
  updateTime: Date;
 
  @Index()
  @Column({ comment: '租户ID', nullable: true })
  tenantId: number;
}
 
```
 
### 2、条件注入
 
Cool 改造了 `typeorm`的 `Subscriber`,新增了以下四种监听:
 
```ts
/**
 * 当进行select的QueryBuilder构建之后触发
 */
afterSelectQueryBuilder?(queryBuilder: SelectQueryBuilder<any>): void;
 
/**
 * 当进行insert的QueryBuilder构建之后触发
 */
afterInsertQueryBuilder?(queryBuilder: InsertQueryBuilder<any>): void;
 
/**
 * 当进行update的QueryBuilder构建之后触发
 */
afterUpdateQueryBuilder?(queryBuilder: UpdateQueryBuilder<any>): void;
 
/**
 * 当进行delete的QueryBuilder构建之后触发
 */
afterDeleteQueryBuilder?(queryBuilder: DeleteQueryBuilder<any>): void;
```
 
在`src/modules/base/db/tenant.ts`中,通过`tenantId`进行条件注入,从而实现数据隔离。
 
## 使用
 
### 1、开启多租户
 
框架默认关闭多租户,需要手动开启,在`src/config/config.default.ts`中开启多租户
 
```ts
cool: {
    // 是否开启多租户
    tenant: {
      // 是否开启多租户
      enable: true,
      // 需要过滤多租户的url, 支持通配符,如/admin/**/* 表示admin模块下的所有接口都进行多租户过滤
      urls: [],
    },
  }
```
tenant
### 2、代码中使用
 
只要开启了多租户,并配置了`urls`,那么框架会自动注入`tenantId`,开发者原本的代码不需要做任何修改,框架会自动进行数据隔离。
 
#### Controller
 
@CoolController的`add`、`delete`、`update`、`info`、`list`、`page`方法都支持过滤多租户。
 
 
#### Service
 
`Service`中使用多租户,以下是一个完整的示例,包含有效和无效的情况,开发者需要结合实际业务进行选择。
 
```ts
import { Inject, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { DemoGoodsEntity } from '../entity/goods';
import { UserInfoEntity } from '../../user/entity/info';
import { noTenant } from '../../base/db/tenant';
 
/**
 * 商品服务
 */
@Provide()
export class DemoTenantService extends BaseService {
  @InjectEntityModel(DemoGoodsEntity)
  demoGoodsEntity: Repository<DemoGoodsEntity>;
 
  @Inject()
  ctx;
 
  /**
   * 使用多租户
   */
  async use() {
    await this.demoGoodsEntity.createQueryBuilder().getMany();
    await this.demoGoodsEntity.find();
  }
 
  /**
   * 不使用多租户(局部不使用)
   */
  async noUse() {
    // 过滤多租户
    await this.demoGoodsEntity.createQueryBuilder().getMany();
    // 被noTenant包裹,不会过滤多租户
    await noTenant(this.ctx, async () => {
      return await this.demoGoodsEntity.createQueryBuilder().getMany();
    });
    // 过滤多租户
    await this.demoGoodsEntity.find();
  }
 
  /**
   * 无效多租户
   */
  async invalid() {
    // 自定义sql,不进行多租户过滤
    await this.nativeQuery('select * from demo_goods');
    // 自定义分页sql,进行多租户过滤
    await this.sqlRenderPage('select * from demo_goods', {});
  }
}
 
```