在OA系统架构中,实现分公司数据隔离的方案
在OA系统中实现分公司数据隔离,核心原则是“逻辑隔离,物理共享”。除非分公司有极其严格的合规要求(如金融)必须物理分库,否则强烈不建议为每个分公司创建独立的数据表或独立数据库。这会导致后续的系统升级、跨公司统计、主数据维护变成一场灾难。
最合理的设计方案是采用“全局租户ID(TenantID)+ 数据字典 + 权限控制”的架构。以下是具体的表设计方案:
1、核心基础表设计(租户模型)
所有业务表都必须包含一个 tenant_id(或 company_id)字段,这是数据隔离的基石。
1.1、租户/分公司表 (sys_tenant):定义分公司的基础信息。
CREATE TABLE sys_tenant (
id BIGINT PRIMARY KEY,
tenant_name VARCHAR(100) NOT NULL COMMENT '分公司名称',
tenant_code VARCHAR(50) UNIQUE NOT NULL COMMENT '分公司编码',
status TINYINT DEFAULT 1 COMMENT '状态: 1-正常, 0-停用',
contact_info JSON COMMENT '联系方式等扩展信息',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
1.2、用户/员工表 (sys_user):用户必须归属某个分公司,同时可以支持“集团管理员”这种跨租户角色。
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL COMMENT '所属分公司ID,集团超管可为NULL或0',
username VARCHAR(50) NOT NULL,
password VARCHAR(255) NOT NULL,
dept_id BIGINT COMMENT '所属部门ID',
is_global_admin TINYINT DEFAULT 0 COMMENT '是否集团超级管理员',
INDEX idx_tenant_id (tenant_id)
);
2、 业务表设计示例
以OA中最常见的审批流程和公告为例:
2.1、审批单表 (oa_approval)
CREATE TABLE oa_approval (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL COMMENT '【核心】数据隔离字段',
form_type VARCHAR(50) NOT NULL COMMENT '表单类型: 请假/报销/采购',
applicant_id BIGINT NOT NULL COMMENT '申请人ID',
current_status TINYINT COMMENT '当前状态',
content JSON COMMENT '表单动态数据',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-- 必须建立联合索引,保证查询性能
INDEX idx_tenant_status (tenant_id, current_status),
INDEX idx_tenant_applicant (tenant_id, applicant_id)
);
2.2、公告表 (oa_notice)
公告需要区分“集团公告”和“分公司公告”
CREATE TABLE oa_notice (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT COMMENT '分公司ID,若为NULL表示集团全员公告',
title VARCHAR(200) NOT NULL,
content TEXT,
publish_scope TINYINT COMMENT '1-集团, 2-指定分公司, 3-指定部门',
created_by BIGINT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_tenant_id (tenant_id)
);
3、关键设计策略与避坑指南
3.1、 索引策略
所有查询必须带tenant_id:在MyBatis/JPA层做拦截器,自动在SQL后追加 WHERE tenant_id = ?。
联合索引:不要只给 tenant_id 建单列索引。通常是 (tenant_id, business_status) 或 (tenant_id, create_time) 的联合索引,因为分公司内部的数据量可能很大,单列索引区分度不够。
3.2、字典与配置隔离
系统级字典(如:性别、学历):tenant_id = 0 或 NULL,全系统共享。
业务级字典(如:分公司特有的部门名称、报销科目):必须带 tenant_id。
查询逻辑:SELECT * FROM sys_dict WHERE tenant_id = ? OR tenant_id = 0(优先取分公司配置,没有则回退到集团配置)。
3.3、跨分公司协作场景
OA系统难免有集团与分公司、分公司与分公司的交互。不要破坏隔离:不要为了协作把 tenant_id 设为 NULL。
建立关联表:例如 oa_cross_company_task,记录 source_tenant_id, target_tenant_id, biz_id。
数据快照:分公司A发起请求给分公司B,B处理时,A的数据对B只读,或者在B的表里存一份快照/引用。
3.4、集团视角的查询
集团后台查看数据时,SQL条件变为 WHERE tenant_id IN (1, 2, 3…) 或直接 WHERE 1=1(需极高权限)。
性能预警:如果集团查询全量数据导致慢SQL,考虑引入 Elasticsearch 或 ClickHouse 做宽表同步,OA关系型数据库只负责事务和隔离,不负责海量报表分析。
3.5、文件附件存储隔离
物理路径:/upload/{tenant_id}/{yyyy}/{mm}/{uuid}.pdf
对象存储:如果上云,使用 Bucket 的目录前缀隔离,或者不同分公司配置不同 Bucket(如果合规要求极高)。
数据库记录:sys_file 表必须包含 tenant_id。
总结:对于95%的OA系统,单库单表 + tenant_id 字段 是最优解。ORM拦截器 自动注入 tenant_id,防止开发人员漏写导致数据泄露。索引必须包含 tenant_id 作为前缀。文件按 tenant_id 目录隔离。字典支持 tenant_id + global 双层覆盖。这种设计既保证了数据隔离的安全性,又保留了集团统一管控和后续数据分析的灵活性。

