多租户下的系统基础表设计

多租户下的系统基础表设计

在设计 多租户进销存系统(SaaS) 时,核心是 租户隔离 + 权限控制 + 组织结构。
一般推荐的设计是 “租户 → 机构 → 角色 → 用户” 的层级结构,同时所有业务数据都带 tenant_id。

租户表(Tenant)

sys_tenant
------
id              bigint PK
tenant_code     varchar(50) unique -- 租户编码
tenant_name     varchar(200)  -- 租户名称
contact_name    varchar(100)  -- 联系人姓名
contact_phone   varchar(50)   -- 联系人电话
contact_email   varchar(100)  -- 联系人邮箱
expire_time     datetime      -- 过期时间

status          int           -- 状态, 1启用, 0禁用
remark          varchar(255)  -- 备注

created_at      datetime      -- 创建时间
updated_at      datetime      -- 更新时间

说明:

  • 一个租户 = 一个企业
  • 所有业务表都要带 tenant_id 字段,用来标识当前数据所属的租户

业务表统一规范:

  • id
  • tenant_id
  • created_by
  • created_at
  • updated_by
  • updated_at
  • is_deleted
  • org_id

status 字段用来标识当前数据是否有效。

系统表通常只需要表达 是否可用,状态很少变化。这些对象只有两件事:是否可用,是否禁用。用户有时需要 锁定状态,如密码输错次数过多。

因此,状态设计:

  • 0:禁用
  • 1:正常
  • 2: 锁定

业务表的 status 设计,业务单据通常有 生命周期。
例如订单:

草稿 → 提交 → 审核 → 完成 → 作废

如果用一个简单 status:

  • 0
  • 1
  • 2
  • 3

别人几个月后根本看不懂。
所以业务表推荐:用 业务状态枚举。
例如订单:

DRAFT
SUBMITTED
APPROVED
FINISHED
CANCELLED

示例:

status 含义
DRAFT 草稿
SUBMITTED 已提交
APPROVED 已审核
FINISHED 完成
CANCELLED 作废

优点:

  • 可读性强
  • 调试方便
  • API清晰

成熟系统一般这样设计:

status        业务状态
is_deleted    逻辑删除

因此:
系统表 vs 业务表总结:

类型 status设计
系统表 0禁用 1启用
用户表 0禁用 1正常 2锁定
业务表 业务枚举字符串

机构表(Organization)

sys_organization
-------------
id            bigint PK
tenant_id     bigint

org_code      varchar(50) unique -- 机构编码
org_name      varchar(200) -- 机构名称

pid     bigint -- 父节点
path          varchar(500) -- 层级路径
org_type      varchar(50)   -- 机构类型,如: company/department/store

sort          varchar(50) -- 排序
status        int -- 状态, 1启用, 0禁用
remark        varchar(200) -- 备注
is_deleted    int -- 逻辑删除, 1删除, 0未删除
created_at      datetime
updated_at      datetime

说明:

tenant
  └── 总公司
    ├── 财务部
    ├── 销售部
    └── 门店A
  • 机构表中,org_type 字段用来标识当前机构的类型,如:company/department/store
  • 机构表中,pid 字段用来标识当前机构的父节点,path 字段用来标识当前机构的层级路径

path 字段用来标识当前机构的层级路径。

id path
1 1
2 1/2
3 1/3
4 1/2/4
5 1/2/5
6 1/3/6

如果用户机构:org_id = 2, 查询:

SELECT id
FROM sys_organization
WHERE path LIKE '1/2/%'
   OR id = 2;
  • 优点:查询非常快, SQL简单
  • 缺点:移动机构需要更新 path

ERP 中 机构移动很少,所以这是一个很好的方案。

用户表(User)

sys_user
-----
id            bigint PK
tenant_id     bigint

username      varchar(100)     -- 用户名
password      varchar(255)     -- 密码哈希
salt          varchar(50)     -- 密码盐

real_name     varchar(100)     -- 真实姓名
nickname      varchar(100)     -- 昵称
gender        varchar(10)      -- 性别
avatar        varchar(200)    -- 头像
mobile         varchar(50)     -- 手机号
email         varchar(100)    -- 邮箱

org_id        bigint          -- 机构ID
position_id   bigint          -- 岗位ID
login_count   int             -- 登录次数
last_login_time datetime        -- 最后登录时间
last_login_ip  varchar(50)     -- 最后登录IP

is_super      int           -- 是否超级管理员, 1超级管理员, 0普通用户
is_deleted       int            -- 逻辑删除, 1删除, 0未删除
status        int            -- 状态, 1启用, 0禁用

remark        varchar(200) -- 备注
created_at      datetime -- 创建时间
updated_at      datetime -- 更新时间

说明:

  • 用户表中,tenant_id 字段用来标识当前用户所属的租户
  • org_id 字段用来标识当前用户所属的机构

is_super 表示:

系统超级管理员,不受任何权限控制

if user.is_super:
    允许所有操作
else:
    按 RBAC 权限判断

避免误操作:

  • 如果超级管理员只是角色, 管理员可能在 UI 中误删:
  • 结果:系统没有管理员
  • 这个字段通常:不允许 UI 修改,只能数据库修改,安全性更高。

is_super 的作用:

  • 1️⃣ 绕过权限系统
  • 2️⃣ 防止系统锁死
  • 3️⃣ 提高权限判断性能
  • 4️⃣ 防止误删管理员角色
  • 5️⃣ 系统逃生通道

用户有时需要 锁定状态,如密码输错次数过多。

因此,状态设计:

  • 0:禁用
  • 1:正常
  • 2: 锁定

角色表(Role)

角色是租户级的。

sys_role
-----
id           bigint PK
tenant_id    bigint

role_code    varchar(50) unique -- 角色编码
role_name    varchar(200) -- 角色名称
role_type    varchar(50) -- 角色类型

data_scope   varchar(50)   -- 数据权限

sort         varchar(50) -- 排序
status       int -- 状态, 1启用, 0禁用
is_deleted   int -- 逻辑删除, 1删除, 0未删除

created_at  datetime -- 创建时间
updated_at  datetime -- 更新时间

常见角色:

  • 管理员
  • 采购
  • 销售
  • 仓库
  • 财务

角色通常需要:data_scope,例如:

  • ALL 全部数据
  • ORG 本机构
  • ORG_CHILD 本机构及下级
  • SELF 仅自己
  • CUSTOM 指定机构

SQL示例:

1、data_scope = ALL 时;

WHERE tenant_id = ?

2、data_scope = ORG 时;

WHERE tenant_id = ?
AND org_id = current_org

3、data_scope = ORG_CHILD 时;

WHERE tenant_id = ?
AND org_id IN (子机构列表)

4、data_scope = SELF 时;

WHERE tenant_id = ?
AND created_by = current_user

5、data_scope = CUSTOM 时;

WHERE tenant_id = ?
AND org_id IN (role_org)

ERP 实际 SQL 拼接

SELECT *
FROM sales_order
WHERE tenant_id = ?
AND (
      created_by = :user_id
   OR org_id IN (:org_ids)
)

岗位表(Position)

sys_position
----------
id bigint PK
tenant_id bigint

position_code varchar(50) -- 岗位编码
position_name varchar(200) -- 岗位名称

org_id bigint -- 所属机构

status int -- 状态, 1启用, 0禁用
created_at datetime -- 创建时间
updated_at datetime -- 更新时间

角色主要解决 权限问题, 岗位主要解决 组织职责问题。岗位通常是 组织结构的一部分。

岗位通常是“一人一岗”(主岗位):

  • 在很多 ERP / OA / HR 系统里,岗位通常设计为“一人一个主岗位”,因此直接在用户表中放 position_id,而不是做多对多。

优点:

  • 表结构简单
  • 查询快
  • UI简单
  • 符合大多数企业组织结构

有些企业确实存在 兼职岗位:

  • 兼职职责用 角色 解决。

权限表(Permission)

权限通常是菜单 + 按钮。

sys_permission
-----------
id bigint PK
system_code varchar(50) -- 系统类型

perm_code varchar(50) PK -- 权限编码
perm_name varchar(200) -- 权限名称
perm_type  varchar(50)   -- 权限类型,如: menu/button/api

pid bigint -- 父节点
path varchar(500) -- 层级路径
api_path varchar(200) -- API路径
scope varchar(50) -- 权限范围,如:SYSTEM/TENANT
module_code varchar(50) -- 模块编码
resource_code varchar(50) -- 资源编码
action_code varchar(50) -- 操作编码

sort  varchar(50) -- 排序
status int -- 状态, 1启用, 0禁用

created_at datetime -- 创建时间
updated_at datetime -- 更新时间

说明:
perm_type 枚举:

  • menu:菜单
  • button:按钮
  • api:API

例如:

perm_code perm_type
user:add 按钮
user:delete 按钮
/api/user/list API

这样可以:

  • 控制前端菜单
  • 控制按钮
  • 控制接口权限

scope 权限作用范围:

  • SYSTEM:系统级
  • TENANT:租户级

例如:

perm_code scope
tenant:create SYSTEM
user:add TENANT

权限表通常 不带 tenant_id:

sys_permission   全局
sys_menu         全局

权限只负责“动作”, 如下面示例:

  • 查询(可选)
  • 新增
  • 编辑
  • 删除
  • 审核
  • 反审核
  • 导出
  • 打印
  • 作废
  • 关闭
  • 红冲
  • 过账

module_code + resource_code + action_code

这是 工业级权限编码拆分设计,后期非常好用。

例如:

module_code resource_code action_code perm_code
sys user view sys:user:view
sys user add sys:user:add
order sales_order approve order:sales_order:approve

比单纯 perm_code 更利于:

  • 代码生成
  • 权限树归类
  • 批量授权
  • 模块迁移

权限编码必须 统一规范:
推荐:模块:资源:操作

例如:

user:list
user:add
user:update
user:delete

order:create
order:approve
order:cancel

最终模型:

Menu (导航)
Permission (功能)

User
 └─ Role
      └─ Permission
           ├─ Menu
           ├─ Button
           └─ API

核心思想:

  • 菜单控制导航
  • 权限控制行为
  • 角色负责授权

前端菜单生成逻辑:

流程:

用户登录
 ↓
获取角色
 ↓
获取权限和菜单集合
 ↓
根据权限和菜单集合生成菜单树
 ↓
前端展示菜单树

菜单表(Menu)

sys_menu
------
id bigint PK
pid bigint -- 父节点
system_code varchar(50) -- 系统类型
perm_code varchar(50) -- 访问权限编码(可选)

menu_code varchar(50) PK -- 菜单编码
menu_name varchar(200) -- 菜单名称
tag varchar(50) -- 标签

path varchar(200) -- 路由路径
redirect varchar(200) -- 重定向路径
is_iframe int -- 是否内嵌窗口,1内嵌窗口, 0不内嵌窗口
out_link varchar(200) -- 外链地址
is_keep_alive int -- 是否缓存,1缓存, 0不缓存
is_affix int -- 是否固定,1固定, 0不固定
is_expand int -- 是否展开
url varchar(200) -- 界面Url地址

is_eav_menu int -- 是否EAV菜单
entity_type_id bigint -- 实体类型ID

component varchar(200) -- 组件路径
icon varchar(50) -- 图标

sort varchar(50) -- 排序
status int -- 状态, 1启用, 0禁用
is_visible int -- 是否可见,1可见,0不可见

created_at  datetime -- 创建时间
updated_at datetime -- 更新时间

菜单和权限分离,但菜单支持可选绑定访问权限。
菜单有一个可选的“访问权限编码”

  • 有则校验
  • 无则只要菜单分配了就能访问

不是所有菜单页面都必须有 VIEW 权限。只给重要页面加,例如:

  • 财务报表
  • 价格策略
  • 供应商结算
  • 采购成本分析
  • 薪资核算

系统类型表(SystemType)

sys_system_type
------------
id bigint PK

system_code varchar(50) -- 系统类型编码
system_name varchar(200) -- 系统类型名称
remark varchar(200) -- 备注

status int -- 状态, 1启用, 0禁用
created_at datetime -- 创建时间

系统类型表,用于区分不同系统之间的资源,如系统菜单、权限功能点等。

用户角色表(UserRole)

sys_user_role
----------
id bigint PK
tenant_id bigint

user_id bigint
role_id bigint

唯一约束:

UNIQUE (tenant_id, user_id, role_id)

企业级系统,中间表建议带 tenant_id

  • 避免跨租户脏数据
  • 查询更高效
  • 索引优化更直接

tenant_id 可以冗余,但利大于弊。

角色机构表(RoleOrg)

当使用 CUSTOM 时,需要指定机构:

sys_role_org
---------
id bigint PK
tenant_id    bigint

role_id bigint
org_id bigint

唯一性约束:

UNIQUE (tenant_id, role_id, org_id)

角色权限表(RolePermission)

sys_role_permission
----------------
id bigint PK
tenant_id bigint

role_id bigint
perm_id bigint

唯一性约束:

UNIQUE (tenant_id, role_id, perm_id)

租户菜单关系表(TenantMenu)

表示:某个租户可使用哪些菜单资源(一级分配)。然后该租户下的角色,可以根据当前租户拥有的菜单权限来控制可见菜单(二级分配)。

sys_tenant_menu
-
id bigint PK
tenant_id bigint -- 租户ID

menu_id bigint -- 菜单ID
status int -- 状态, 1启用, 0禁用
created_at datetime -- 创建时间
updated_at datetime -- 更新时间

作用, 比如:

  • A租户:可用【采购、销售、库存】
  • B租户:可用【销售、财务】
  • C租户:可用【全部模块】

这样你就实现了“不同租户看到不同菜单体系”。
唯一性约束:

UNIQUE (tenant_id, menu_id)

角色菜单关系表(RoleMenu)

在实际项目里,尤其是 ERP / 后台管理系统,如果把“菜单”和“权限点”完全绑定死,往往会出现:

  • 配置步骤变多
  • 维护成本变高
  • 很多纯展示菜单也要配权限,显得很累赘
  • 前端菜单树和后端权限模型耦合过重

因此,菜单和权限分离建模,但允许菜单可选绑定权限。

  • 菜单(Menu):解决“看不看得见、能不能导航到页面”
  • 权限(Permission / Resource):解决“能不能操作按钮/接口/业务动作”
  • 角色分配时:
    • 可以直接分配 菜单
    • 也可以分配 权限
  • 菜单可选关联一个“访问权限”(不是必须)

菜单直接给角色分配,简单直观。

特点

  • 角色拥有哪些菜单,直接可见
  • 前端菜单树加载简单
  • 配置很直观

优点

  • 实现简单
  • 运维/实施人员容易理解
  • 适合大多数后台系统

缺点

  • 只能控制“能不能看到页面”
  • 按钮、接口、审核、反审核、导出等细粒度动作不好管
  • 最后还是要补权限表

为什么这是最适合 ERP 的?

ERP 里通常有三层:

  • 目录菜单(比如“采购管理”)
  • 页面菜单(比如“采购订单”)
  • 页面内动作(新增、编辑、删除、审核、反审核、导出、打印、关闭、红冲……)

如果你把它们全都统一成权限,会出现:

  • 目录菜单也要定义权限
  • 菜单页也要定义 VIEW 权限
  • 配置会很重

而 ERP 实际上最关键的是:

  • 菜单页是否可见 → 用 role_menu
  • 页面里的动作是否可做 → 用 role_permission

这样非常清晰。

sys_role_menu
-----------
id bigint PK
tenant_id bigint

role_id bigint
menu_id bigint

唯一性约束:

UNIQUE (tenant_id, role_id, menu_id)

操作日志表(OperationLog)

sys_operation_log
--------------
id bigint PK
tenant_id bigint
user_id bigint

module varchar(250) -- 模块
action varchar(50) -- 操作

content varchar(2000) -- 内容

ip varchar(50) -- IP地址
created_at datetime -- 创建时间

登录日志表(LoginLog)

sys_login_log
----------
id bigint PK
tenant_id bigint
user_id bigint

content varchar(2000) -- 内容
ip varchar(50) -- IP地址
created_at

字典类型表

在多租户系统里,字典类型(Dictionary Type) 和 字典项目(Dictionary Item) 是否租户隔离,通常不能一刀切。

字典类型大概率是“全局定义为主”,而字典项目既可能全局共享,也可能租户自定义。

建议把字典分成 3类:

1 系统级字典(全局共享)

  • 所有租户都一样
  • 例如:性别、星期、单据状态、启用状态

2 租户级字典(租户私有)

  • 每个租户可以维护自己的字典项目、
  • 例如:客户等级、供应商分类、仓库分区、付款方式、业务标签

3 混合型字典(系统默认 + 租户可扩展)

  • 系统给默认项
  • 租户可以追加或覆盖
  • 例如:结算方式、订单来源、业务分类

如果你做的是 ERP 场景,下面这些字典项几乎一定会不同:

  • 客户
  • 供应商等级
  • 业务员分组
  • 仓库区域
  • 付款条件
  • 税率组(有时甚至不同组织不同)
  • 单据业务类型
  • 自定义标签
  • 物料属性分类
  • 费用类别
  • 结算方式
  • 运输方式

哪些字典通常不会不同(全局)

例如:

  • 性别
  • 是否启用
  • 星期
  • 月份
  • 国家/省市(如果你自己维护)
  • 单据状态(草稿、已审核、已关闭)
  • 审核状态
  • 布尔型选项
  • 系统固定枚举
  • 这些更适合做 全局字典。
sys_dict_type
---------------
id bigint PK
pid bigint

type_code varchar(50) -- 类型编码
type_name varchar(200) -- 类型名称
dict_category       varchar(20) -- SYSTEM / BUSINESS / CUSTOM -- 字典类别
scope_mode          varchar(20) -- GLOBAL / TENANT / MIXED -- 作用范围

sort varchar(50) -- 排序
remark varchar(200) -- 备注

is_deleted int -- 逻辑删除, 1删除, 0未删除
is_system  int -- 是否系统字典
status  int     -- 状态, 1启用, 0禁用

created_at datetime -- 创建时间

scope_mode 这个字段非常重要,可取值:

  • GLOBAL:只允许全局字典项
  • TENANT:只允许租户私有字典项
  • MIXED:系统默认 + 租户可扩展

字典项目表

sys_dict_data
---------------
id     bigint PK
type_id bigint -- 类型ID
tenant_id bigint null   -- null = 全局项;有值 = 租户项

item_code varchar(100) -- 项目编码
item_name varchar(200) -- 项目名称
item_value varchar(200) -- 项目值
remark varchar(200) -- 备注

is_default smallint default 0 -- 是否默认项
is_builtin smallint default 0 -- 系统内置项
ext_json text -- 扩展属性(颜色、标签、额外配置)

sort  varchar(50) -- 排序
status int -- 状态, 1启用, 0禁用
is_deleted int -- 逻辑删除, 1删除, 0未删除

created_at datetime -- 创建时间

tenant_id = null 表示全局字典项,例如:
GENDER

  • 未知

这些对所有租户都一样。

tenant_id = 某租户ID 表示租户私有字典项

例如:

租户 A 的 CUSTOMER_LEVEL

  • VIP客户
  • 普通客户
  • 战略客户

租户 B 的 CUSTOMER_LEVEL

  • A类客户
  • B类客户
  • C类客户

1 GLOBAL 字典,只查全局项:

select *
from sys_dict_item
where dict_type_id = :dict_type_id
  and tenant_id is null
  and status = 1
order by sort_no, id;

2 TENANT 字典,只查当前租户项:

select *
from sys_dict_item
where dict_type_id = :dict_type_id
  and tenant_id = :tenant_id
  and status = 1
order by sort_no, id;

3 MIXED 字典,查全局和租户项:
查询规则:

  • 先查全局默认项
  • 再查当前租户扩展项
  • 如果允许“覆盖”,则租户项优先

例如:

PAYMENT_METHOD(付款方式), 系统默认:

  • 现金
  • 转账
  • 支票

租户 A 追加:

  • 月结30天
  • 月结60天

租户 B 追加:

  • 银承
  • 商承

查询逻辑(追加模式):

select *
from sys_dict_item
where dict_type_id = :dict_type_id
  and status = 1
  and (tenant_id is null or tenant_id = :tenant_id)
order by sort_no, id;

查询逻辑(覆盖模式:按 item_code 覆盖):

如果你希望租户能覆盖系统默认项,比如:

系统有 BANK_TRANSFER

租户也定义一个 BANK_TRANSFER,名字改成“对公转账”

那么可以按 item_code 做唯一语义。

规则:

  • 先加载全局项
  • 再加载租户项
  • 相同 item_code 的租户项覆盖全局项

参数表(Parameter)

参数表必须天然支持多租户。
参数表在多租户下,强烈建议按“作用域(Scope)”设计。
也就是同一套参数定义,参数值可以有不同层级:

  • 系统级参数(GLOBAL)
  • 租户级参数(TENANT)
  • 组织级参数(ORG,可选)
  • 用户级参数(USER,可选,通常用于偏好设置)

参数“定义”和“值”分离,拆成两张表

  • 参数定义表 sys_param_def
  • 参数值表 sys_param_value

参数定义表 sys_param_def, 定义“这个参数是什么、类型是什么、支持什么作用域、默认值是什么”

sys_parameter_def
----------
id bigint PK
param_code  varchar(50) -- 参数编码
param_name varchar(50) -- 参数名称
group_name varchar(50) -- 分组名称, SYSTEM / INVENTORY / SALES / FINANCE ...
param_type varchar(20) -- 参数类型: STRING / INT / DECIMAL / BOOL / JSON / DATE
scope_mode varchar(20) -- GLOBAL / TENANT / MIXED -- 作用范围
is_required int -- 是否必填 1必填 0非必填
is_encrypted int -- 是否加密 1加密 0不加密
is_builtin int -- 是否系统内置 1系统内置 0非系统内置

remark varchar(200) -- 备注

is_deleted int -- 逻辑删除, 1删除, 0未删除
sort int -- 排序
status int -- 状态, 1启用, 0禁用
created_at datetime -- 创建时间
updated_at datetime -- 更新时间

scope_mode 这个字段非常重要,可取值:

  • GLOBAL:只允许系统级
  • TENANT:每个租户独立
  • ORG:按组织/部门/账套维度(ERP 常见)
  • USER:用户偏好设置
  • MIXED:允许多级覆盖(推荐重点)

参数值表是指“在某个作用域下,这个参数的实际值是什么”,参数值表设计:

sys_parameter
----------
id
tenant_id bigint -- 租户ID
param_def_id bigint -- 参数定义ID

scope_level varchar(20) -- GLOBAL / TENANT / ORG / USER-- 作用范围
org_id bigint -- 组织ID
user_id bigint -- 用户ID

param_value varchar(4000) -- 参数值
value_source varchar(200) -- 参数值来源:MANUAL / DEFAULT / IMPORT / SYSTEM
remark varchar(200) -- 备注

status int -- 状态, 1启用, 0禁用
created_at datetime -- 创建时间
updated_at datetime -- 更新时间

优先级建议
USER > ORG > TENANT > GLOBAL > sys_param_def.default_value

也就是说:

  • 如果有用户级值,用用户级
  • 没有则看组织级
  • 没有则看租户级
  • 没有则看全局级
  • 再没有则用参数定义里的默认值

读取规则(V1)

  • GLOBAL:只查 tenant_id is null
  • TENANT:只查 tenant_id = current_tenant_id,没有则用默认值
  • MIXED:优先租户值,没有则全局值,没有则默认值

进销存业务表建议

核心业务表:

product  
category  
warehouse  
inventory  
supplier  
customer  
purchase_order  
purchase_order_item  
sales_order  
sales_order_item  
stock_in  
stock_out  

所有表都带 tenant_id 字段,用来标识当前数据所属的租户。
对于中间关联表,如UserRole设计,需要增加tenant_id字段。

user_role
----------
id
tenant_id

user_id
role_id

查询,需要根据租户过滤,如:

SELECT *
FROM user_role
WHERE tenant_id = ?

避免跨租户脏数据,可以加唯一索引,逻辑更安全:

CREATE UNIQUE INDEX idx_user_role_tenant_id_user_id_role_id ON user_role (tenant_id, user_id, role_id);

SaaS ORM自动过滤更容易:

query.filter(Model.tenant_id == current_tenant)

删除租户数据更容易:

DELETE FROM user_role WHERE tenant_id = ?

大多数企业系统 全部中间表都会带 tenant_id。例如:

  • user_role
  • role_permission
  • user_org
  • role_org
  • user_position

多租户系统设计原则:
只要是业务表,一律带 tenant_id 字段,并且查询时需要根据租户过滤。
什么时候可以不加 tenant_id? 只有一种情况:全局表,这种是 平台共享数据,不属于某个租户吗,如:租户表,字典表、参数表、系统配置表、菜单表等。。

  • tenant
  • dictionary
  • parameter
  • permission
  • menu
  • country
  • currency

除了租户基础表外,租户还需要包括:租户套餐, 租户套餐关联。

租户套餐:

tenant_package
--------------
id
package_name
user_limit
storage_limit
price

租户套餐关联:

tenant_package_rel
-------------------
tenant_id
package_id
start_time
end_time

ERP系统推荐ID方案

大多数 ERP 系统推荐:

主键ID:BIGINT
业务编码:VARCHAR

主键ID使用分布式ID:

Snowflake
Leaf
Sonyflake

生成 64bit BIGINT:

178923741239123

特点:

  • 全局唯一
  • 有时间顺序
  • 仍然是 BIGINT

因此使用BIGINT + Snowflake 方案。

结构:

id BIGINT PRIMARY KEY

生成:

  • Snowflake ID

优点:

  • 分布式
  • 高性能
  • 顺序索引

ERP数据库标准结构:

典型表:

id BIGINT PRIMARY KEY
tenant_id BIGINT
created_at DATETIME
updated_at DATETIME

不要在id中,把 GUID 存 VARCHAR。
例如:

550e8400-e29b-41d4-a716-446655440000

什么时候用 GUID ?

1 微服务跨系统ID

例如:

订单服务
支付服务
物流服务

2 离线客户端

例如:

移动端
离线同步

3 数据合并

例如:

多数据库合并

posted on 2026-03-16 13:59  伍华聪  阅读(2297)  评论(4)    收藏  举报

导航