多租户文档
概述
系统基于字段隔离的多租户架构设计,使用tenant_id
作为租户标识符,实现在单一数据库实例中为多个租户提供隔离的数据存储和服务。
基于字段隔离的多租户架构是一种成本效益高的解决方案,适用于中小型应用。通过合理的设计和实现,可以在保证数据隔离的同时,实现资源的共享和高效利用。关键是要在应用层的各个层面都严格实施租户隔离策略,确保数据安全和系统稳定性。
架构模式
字段隔离(共享数据库,共享表结构)
字段隔离是一种多租户架构模式,所有租户共享同一个数据库实例和表结构,通过在每个表中添加tenant_id
字段来区分不同租户的数据。
+-----------------+ +-----------------+ +-----------------+
| 租户 A 数据 | | 租户 B 数据 | | 租户 C 数据 |
| tenant_id=1 | | tenant_id=2 | | tenant_id=3 |
+-----------------+ +-----------------+ +-----------------+
\ / \ / \ /
\ / \ / \ /
+-------------------------------------------------------------+
| 共享数据库表结构 |
| (所有数据存储在同一表中,通过tenant_id区分) |
+-------------------------------------------------------------+
安装部署方式
因为使用的是数据表字段隔离的方式,所以安装部署方式与相关配置和授权版操作完全一致。
数据库设计
基础表结构
所有业务表都需要包含tenant_id
字段作为租户标识:
-- 用户表示例
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_tenant_id (tenant_id),
UNIQUE KEY uk_tenant_username (tenant_id, username),
UNIQUE KEY uk_tenant_email (tenant_id, email)
);
-- 订单表示例
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL,
order_no VARCHAR(32) NOT NULL,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status TINYINT NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_tenant_id (tenant_id),
INDEX idx_tenant_user (tenant_id, user_id),
UNIQUE KEY uk_tenant_order_no (tenant_id, order_no)
);
租户表
-- 租户信息表
CREATE TABLE tenants (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
code VARCHAR(50) NOT NULL UNIQUE, #域名或租户代码
status TINYINT NOT NULL DEFAULT 1,
settings JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
应用层实现
1. 中间件处理租户ID
在路由文件中,分组路由加上service.Middleware().Tenant
中间件处理
group.Middleware(service.Middleware().Ctx, service.Middleware().Tenant)
2. 服务层实现
logic
文件中结构体包含filter.TenantFilter
结构体
type sDemoGenTenant struct {
filter.TenantFilter
}
查询数据:
func (s *sDemoGenTenant) List(ctx context.Context, req *model.DemoGenTenantSearchReq) (listRes *model.DemoGenTenantSearchRes, err error) {
...
m := s.Model(ctx, dao.DemoGenTenant.Ctx(ctx), s.WithWhere())
...
err = m.Page(req.PageNum, req.PageSize).Order(order).Scan(&res)
...
}
若需要表别名处理,添加s.WithAlias("别名")
参数:
func (s *sDemoGenTenant) List(ctx context.Context, req *model.DemoGenTenantSearchReq) (listRes *model.DemoGenTenantSearchRes, err error) {
...
m := s.Model(ctx, dao.DemoGenTenant.Ctx(ctx), s.WithWhere(),s.WithAlias("demo"))
...
err = m.Page(req.PageNum, req.PageSize).Order(order).Scan(&res)
...
}
新增数据:
func (s *sDemoGenTenant) Add(ctx context.Context, req *model.DemoGenTenantAddReq) (err error) {
...
s.Model(ctx, dao.DemoGenTenant.Ctx(ctx), s.WithHook()).
Insert(data)
//注意:data中不需要处理tenant_id字段,hook中自动处理
...
}
修改数据:
func (s *sDemoGenTenant) Edit(ctx context.Context, req *model.DemoGenTenantEditReq) (err error) {
...
s.Model(ctx, dao.DemoGenTenant.Ctx(ctx), s.WithWhere()).
WherePri(req.Id).Update(data)
...
}
删除数据:
func (s *sDemoGenTenant) Delete(ctx context.Context, ids []uint) (err error) {
...
_,err=s.Model(ctx, dao.DemoGenTenant.Ctx(ctx), s.WithWhere()).
Delete(dao.DemoGenTenant.Columns().Id+" in (?)", ids)
...
}
代码生成支持
代码生成支持多租户功能,只需要在表中存在tenant_id
字段,便会自动生成租户处理功能。
安全考虑
1. 数据隔离
- 所有数据库查询,修改,删除必须包含
tenant_id
条件WithWhere
方法自动添加过滤条件 - 新增数据写入
tenant_id
,WithHook
自动处理 - 使用中间件自动注入租户上下文
- 在数据访问层强制验证租户权限
性能优化
1. 数据库索引
-- 为所有表的tenant_id字段创建索引
CREATE INDEX idx_users_tenant_id ON users(tenant_id);
CREATE INDEX idx_orders_tenant_id ON orders(tenant_id);
-- 创建复合索引优化查询
CREATE INDEX idx_users_tenant_username ON users(tenant_id, username);
CREATE INDEX idx_orders_tenant_user ON orders(tenant_id, user_id);
3. 缓存策略
租户级别的缓存
使用租户ID作为缓存键的前缀
系统使用
不同租户下的用户只能访问自己归属的租户相关数据,不能跨租户访问数据(租户管理员除外),租户管理员可以选择登录不同租户来设置租户相关数据(比如设置不同租户的字典,参数等相关信息)
字典和参数分为全局和租户,全局:所有租户可以使用;租户:不同租户的字典和参数相互隔离,示例如下:
如:租户A只能使用全局和租户A的字典:
租户B只能使用全局和租户B的字典:
租户管理员在角色授权时,选择是租户管理员则可以在不同租户中切换维护租户数据:
注意:普通管理人员不要给租户维护相关的权限(这个权限应该只是给系统维护人员或运维人员)
最后编辑:管理员 更新时间:2025-10-17 15:36