第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 组织架构的缓存策略

组织架构的特点是:读多写少。一个几千人的公司,组织架构可能一个月才调整一次,但每天被查询成千上万次。这种场景,缓存是必须的。

我常用的缓存策略是这样的:

  1. 全量缓存:把整棵组织树缓存到Redis,key为org:tree:full,过期时间设为1小时
  2. 节点缓存:每个节点单独缓存,key为org:node:{id},过期时间设为2小时
  3. 路径缓存:缓存节点到根节点的路径,key为org:path:{id},用于权限判断
  4. 缓存更新策略:写操作时,先更新数据库,再删除相关缓存(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模式,读写分离。

下一章咱们聊用户管理与组织架构的关联,包括用户归属部门、岗位分配、以及组织架构变更时用户信息的自动同步。到时候我会分享一个我在项目中踩过的坑——组织架构调整时,用户权限怎么平滑迁移。

嗯,今天就到这儿。代码都在上面了,你可以直接拿去用。