第1章:组织架构管理——公司/部门/岗位的数据模型设计
组织架构,说白了就是企业的骨架。我做了这么多年ERP系统,发现一个规律:凡是组织架构设计得好的项目,后续的权限、流程、报表模块都顺风顺水。凡是上来就糊弄的,后面全是坑。
今天咱们就从数据模型开始,一步步把组织架构这个核心模块搭起来。
1.1 数据模型设计:三张表搞定一切
我个人习惯把组织架构拆成三个实体:公司、部门、岗位。你想想看,不管企业多大,最终都能用这三层来抽象。
先看核心的表结构设计:
-- 公司表
CREATE TABLE company (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
code VARCHAR(32) NOT NULL UNIQUE COMMENT '公司编码',
name VARCHAR(128) NOT NULL COMMENT '公司名称',
short_name VARCHAR(64) COMMENT '公司简称',
parent_id BIGINT DEFAULT NULL COMMENT '上级公司ID',
level INT DEFAULT 1 COMMENT '层级:1集团 2子公司 3分公司',
sort_order INT DEFAULT 0 COMMENT '排序号',
status TINYINT DEFAULT 1 COMMENT '状态:1启用 0停用',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司表';
-- 部门表
CREATE TABLE department (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
company_id BIGINT NOT NULL COMMENT '所属公司ID',
code VARCHAR(32) NOT NULL COMMENT '部门编码',
name VARCHAR(128) NOT NULL COMMENT '部门名称',
parent_id BIGINT DEFAULT NULL COMMENT '上级部门ID',
dept_type VARCHAR(32) COMMENT '部门类型:行政/技术/销售等',
manager_id BIGINT DEFAULT NULL COMMENT '部门负责人ID',
sort_order INT DEFAULT 0,
status TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_company_code (company_id, code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';
-- 岗位表
CREATE TABLE position (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
dept_id BIGINT NOT NULL COMMENT '所属部门ID',
code VARCHAR(32) NOT NULL COMMENT '岗位编码',
name VARCHAR(128) NOT NULL COMMENT '岗位名称',
job_level VARCHAR(16) COMMENT '职级:P1-P9/M1-M5',
max_headcount INT DEFAULT 1 COMMENT '编制人数',
current_headcount INT DEFAULT 0 COMMENT '当前人数',
status TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_dept_code (dept_id, code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='岗位表';
设计要点:
- 公司表也支持树形结构,因为集团下面可能还有子公司
- 部门表通过company_id关联公司,同时parent_id自关联实现层级
- 岗位表挂在部门下,通过dept_id关联
- 编码字段建议用唯一索引,我在项目中吃过重复编码的亏
1.2 组织树形结构实现:递归查询
树形结构怎么查?最直接的方式就是递归。MySQL 8.0以上支持CTE递归查询,用起来很爽。
我曾经在旧项目里用程序递归查,每次都要发N条SQL,性能惨不忍睹。后来换成数据库递归,一次查询搞定。
-- 查询某个部门及其所有子部门
WITH RECURSIVE dept_tree AS (
-- 根节点
SELECT id, name, parent_id, 1 AS level
FROM department
WHERE id = ? -- 传入根部门ID
UNION ALL
-- 递归子节点
SELECT d.id, d.name, d.parent_id, dt.level + 1
FROM department d
INNER JOIN dept_tree dt ON d.parent_id = dt.id
)
SELECT * FROM dept_tree ORDER BY level, id;
-- 查询完整组织树(公司+部门+岗位)
WITH RECURSIVE org_tree AS (
-- 公司层
SELECT id, name, 'company' AS node_type, parent_id, 1 AS level
FROM company
WHERE parent_id IS NULL
UNION ALL
-- 部门层
SELECT d.id, d.name, 'dept' AS node_type, d.parent_id, ot.level + 1
FROM department d
INNER JOIN org_tree ot ON d.parent_id = ot.id AND ot.node_type = 'dept'
UNION ALL
-- 岗位层
SELECT p.id, p.name, 'position' AS node_type, p.dept_id, ot.level + 1
FROM position p
INNER JOIN org_tree ot ON p.dept_id = ot.id AND ot.node_type = 'dept'
)
SELECT * FROM org_tree ORDER BY level, id;
我的经验:递归查询虽然方便,但深度超过10层时性能会下降。建议在应用层做缓存,别每次都查数据库。
1.3 组织架构CRUD API设计
API设计我遵循RESTful风格,但会加一些组织架构特有的参数。嗯,这里要注意:删除操作必须做级联检查。
核心接口清单:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/org/tree | 获取完整组织树 |
| GET | /api/v1/org/node/{id} | 获取单个节点详情 |
| POST | /api/v1/org/node | 新增节点(公司/部门/岗位) |
| PUT | /api/v1/org/node/{id} | 更新节点信息 |
| DELETE | /api/v1/org/node/{id} | 删除节点(含级联检查) |
| POST | /api/v1/org/move | 移动节点(调整层级关系) |
来看一个新增部门的Python示例:
from flask import Blueprint, request, jsonify
from models import Department, db
org_bp = Blueprint('org', __name__)
@org_bp.route('/api/v1/org/node', methods=['POST'])
def create_org_node():
data = request.get_json()
node_type = data.get('node_type') # company/dept/position
if node_type == 'dept':
# 检查上级部门是否存在
parent_id = data.get('parent_id')
if parent_id:
parent = Department.query.get(parent_id)
if not parent:
return jsonify({'code': 400, 'msg': '上级部门不存在'}), 400
# 检查同一公司下编码是否重复
existing = Department.query.filter_by(
company_id=data['company_id'],
code=data['code']
).first()
if existing:
return jsonify({'code': 400, 'msg': '部门编码已存在'}), 400
dept = Department(
company_id=data['company_id'],
code=data['code'],
name=data['name'],
parent_id=parent_id,
dept_type=data.get('dept_type'),
sort_order=data.get('sort_order', 0)
)
db.session.add(dept)
db.session.commit()
return jsonify({'code': 200, 'data': dept.to_dict()}), 201
# 其他节点类型类似处理...
避坑指南:我曾经在删除部门时没检查下面有没有人,结果把财务部删了,财务总监的账号直接变成无部门状态,审批流全断了。后来我加了一条规则:删除前必须检查该节点下是否有子节点和关联人员。
1.4 组织架构的缓存策略
组织架构的特点是:读多写少。一个几千人的公司,组织架构可能一个月才调整一次,但每天被查询成千上万次。这种场景,缓存是必须的。
我常用的缓存策略是这样的:
- 全量缓存:把整棵组织树缓存到Redis,key为
org:tree:full,过期时间设为1小时 - 节点缓存:每个节点单独缓存,key为
org:node:{id},过期时间设为2小时 - 路径缓存:缓存节点到根节点的路径,key为
org:path:{id},用于权限判断 - 缓存更新策略:写操作时,先更新数据库,再删除相关缓存(Cache-Aside模式)
来看缓存更新的实现:
import redis
from flask import current_app
cache = redis.Redis(host='localhost', port=6379, db=0)
def update_org_node(node_id, node_type):
"""更新组织节点后,清理相关缓存"""
# 1. 先更新数据库
# ... 数据库更新逻辑 ...
# 2. 删除节点缓存
cache.delete(f'org:node:{node_id}')
# 3. 删除全量树缓存
cache.delete('org:tree:full')
# 4. 如果移动了父节点,还要删除路径缓存
cache.delete(f'org:path:{node_id}')
# 5. 异步重建缓存(可选)
# 可以用Celery异步任务去重建,避免接口响应慢
rebuild_org_cache.delay()
def get_org_tree():
"""获取组织树,优先从缓存读取"""
tree = cache.get('org:tree:full')
if tree:
return json.loads(tree)
# 缓存未命中,查数据库
tree = build_org_tree_from_db()
# 写入缓存,设置过期时间
cache.setex('org:tree:full', 3600, json.dumps(tree))
return tree
我的建议:缓存失效时不要所有请求都去重建缓存,否则高并发下会打爆数据库。可以用互斥锁或者缓存预热来解决。我一般用Redis的SETNX做分布式锁,只让一个线程去重建。
1.5 总结
组织架构管理,说白了就是三件事:数据模型要稳、树形查询要快、缓存策略要准。
数据模型上,公司-部门-岗位三层结构基本够用。树形查询用CTE递归,简单高效。缓存用Cache-Aside模式,读写分离。
下一章咱们聊用户管理与组织架构的关联,包括用户归属部门、岗位分配、以及组织架构变更时用户信息的自动同步。到时候我会分享一个我在项目中踩过的坑——组织架构调整时,用户权限怎么平滑迁移。
嗯,今天就到这儿。代码都在上面了,你可以直接拿去用。