记录每次修订的内容,方便追溯。
| 日期 | 版本 | 修订说明 | 作者 |
|---|---|---|---|
| 2026-07-09 | 1.0 | 初稿,组织架构管理(部门管理、职位管理)系统分析设计 | 系分自动生成 |
对本项目的背景以及目标进行描述,方便开发者理解需求,对齐上下文。
本模块来源于 HRMS(人资管理系统)产品规格说明书中第 3 部分——组织架构管理。组织架构是整个人资管理系统的数据基座,部门树与职位体系向上承载员工档案、入转调离、考勤、薪资等业务模块的归属与权限判定,向下决定工号生成规则、数据权限范围与汇报关系链路。当前公司组织信息散落在多份 Excel 与 OA 系统中,存在部门层级混乱、编码不统一、负责人信息缺失、合并拆分无审计等问题,导致工号生成口径不一致、部门主管数据权限边界模糊、跨部门统计口径失真。
当前主要痛点包括:部门层级深度无约束,历史数据中存在 8 级以上嵌套,导致递归查询性能差、前端树渲染卡顿;部门编码人工维护且无唯一性校验,工号生成出现重复风险;部门人数统计依赖人工导表核对,无法实时反映"试用期 + 正式"口径的在职人数;部门合并/拆分直接改库,缺乏员工转移校验与变更历史审计,事后无法追溯。本模块旨在建立统一的多级组织架构数字化管理能力,覆盖部门 CRUD、层级深度限制、闭包表递归查询优化、人数实时统计、部门合并,以及职位序列(M/P/S)与职级范围管理,为后续员工档案、入转调离、薪资核算等模块提供稳定、高性能的组织数据支撑。
本次系统设计的目标是:以"部门树缓存 + 闭包表递归查询优化"为核心,保证部门树查询性能在 1000 部门规模下毫秒级响应;通过应用层校验与数据库唯一索引双重保障部门编码唯一性,确保工号生成稳定可靠;建立部门变更历史审计机制,所有合并、层级调整均可追溯;实现部门人数(含下属部门)实时统计,口径严格限定为"试用期或正式"在职员工。
| 角色 | 姓名 |
|---|---|
| 项目负责人 | ... |
| 产品经理 | ... |
| 设计师 | ... |
| 工程师 | ... |
描述组织架构管理涉及的功能与场景。
本模块核心功能包括:
组织架构管理
├── 部门管理
│ ├── 部门 CRUD(新增/编辑/删除/查询)
│ ├── 部门层级(最大 5 级,子部门数量不限)
│ ├── 部门人数统计(含下属部门,口径:试用期/正式)
│ ├── 部门合并(源部门员工先转移至目标部门)
│ ├── 部门编码唯一性校验(用于工号生成)
│ └── 部门负责人管理(决定数据权限)
└── 职位管理
├── 职位 CRUD(新增/编辑/删除/查询)
├── 职位序列(M 管理 / P 专业 / S 支持)
├── 职级范围(M1-M5 / P1-P10 / S1-S5)
├── 默认试用期(发起入职时自动填充)
└── 职位所属部门(空表示全公司通用)
对组织架构管理涉及的核心流程进行梳理。
用户填写部门信息(名称/编码/上级/负责人/排序/描述) │ ▼ 校验部门编码唯一性(应用层 + DB 唯一索引) │ ├── 编码已存在 ────────────────────> 返回错误"部门编码已存在" │ ▼ 校验上级部门是否存在(空表示根部门) │ ▼ 校验层级深度 ≤ 5(结合闭包表 depth 字段) │ ├── 当前层级 + 1 > 5 ─────────────> 返回错误"超出最大层级深度5级" │ ▼ 持久化部门记录 + 维护闭包表(ancestor/descendant/depth) │ ▼ 记录部门变更历史到 department_change_log │ ▼ 主动刷新 Redis 部门树缓存(DEL + 重建) │ ▼ 返回操作结果,部门创建/编辑完成
用户选择源部门(被合并)和目标部门(保留) │ ▼ 校验源部门与目标部门不同 │ ├── 相同部门 ──────────────────────> 返回错误"源部门与目标部门不能相同" │ ▼ 校验源部门无在职员工(试用期/正式状态) │ ├── 存在在职员工 ──────────────────> 返回错误"源部门存在在职员工,请先转移" │ ▼ 执行合并事务: │ 1. 源部门子部门的 parent_id 更新为目标部门 ID │ 2. 重建闭包表关系(迁移祖先-后代记录) │ 3. 源部门标记为已删除(is_deleted = 1) │ ▼ 记录合并变更历史到 department_change_log │ ▼ 主动刷新 Redis 部门树缓存 + 人数统计缓存 │ ▼ 返回合并结果,部门合并完成
用户选择职位序列(M 管理 / P 专业 / S 支持) │ ▼ 根据序列确定职级范围选项 │ ├── M 序列 ─> M1-M5 │ ├── P 序列 ─> P1-P10 │ └── S 序列 ─> S1-S5 │ ▼ 填写职位名称(必填)+ 职级范围(必填) │ ▼ 关联所属部门(可选,空表示全公司通用) │ ▼ 配置默认试用期月数(发起入职时自动填充) │ ▼ 校验职位名称在同部门/全公司范围内唯一 │ ├── 名称重复 ──────────────────────> 返回错误"职位名称已存在" │ ▼ 校验职级范围与所选序列匹配 │ ├── 范围越界 ──────────────────────> 返回错误"职级范围超出序列允许范围" │ ▼ 持久化职位记录 │ ▼ 返回操作结果,职位创建完成
描述组织架构管理的核心类和依赖关系。
@startuml
class Department {
- id: Long
- name: String "部门名称"
- code: String "部门编码,用于工号生成"
- parentId: Long "上级部门ID,空表示根部门"
- managerId: Long "部门负责人ID,决定数据权限"
- sortOrder: Integer "排序序号,越小越靠前"
- description: String "部门描述"
- status: Integer "状态:1=启用 0=停用"
- createTime: LocalDateTime
- updateTime: LocalDateTime
- isDeleted: Integer "逻辑删除"
--
+ getChildren(): List<Department>
+ getDepth(): Integer "当前层级深度"
+ getOnlineCount(): Integer "在职人数含子部门"
}
class Position {
- id: Long
- name: String "职位名称,如Java开发工程师"
- sequence: PositionSequence "职位序列 M/P/S"
- departmentId: Long "所属部门ID,空表示全公司通用"
- levelRange: String "职级范围,如P1-P10"
- defaultProbationMonths: Integer "默认试用期月数"
- description: String "职位描述"
- status: Integer "状态:1=启用 0=停用"
- createTime: LocalDateTime
- updateTime: LocalDateTime
- isDeleted: Integer "逻辑删除"
--
+ isGeneralPosition(): boolean "是否全公司通用"
}
class DepartmentClosure {
- ancestorId: Long "祖先部门ID"
- descendantId: Long "后代部门ID"
- depth: Integer "层级深度,0=自身,1=直接子级"
--
+ getDescendants(deptId): List<Long>
+ getAncestors(deptId): List<Long>
}
class DepartmentStat {
- deptId: Long "部门ID"
- totalCount: Integer "总人数"
- onlineCount: Integer "在职人数(试用期+正式)"
- updateTime: LocalDateTime
--
+ refresh(): void
}
enum PositionSequence {
M "管理序列 M1-M5:M1主管、M2经理、M3总监、M5VP"
P "专业序列 P1-P10:P3初级、P5中级、P7高级、P9专家"
S "支持序列 S1-S5:职能等级"
}
enum DepartmentStatus {
ENABLED "启用"
DISABLED "停用"
}
Department "1" *-- "0..*" DepartmentClosure : ancestor
Department "1" *-- "0..*" DepartmentClosure : descendant
Department "1" *-- "0..1" DepartmentStat
Department "1" o-- "0..*" Position : 关联职位
Position --> PositionSequence
Department --> DepartmentStatus
note right of DepartmentClosure
闭包表:存储祖先-后代关系
避免递归SQL查询子部门
depth=0 自身,depth=1 直接子级
end note
note right of DepartmentStat
统计口径:在职状态为
"试用期"或"正式"的员工
含所有下属部门
end note
@enduml
描述组织架构管理核心交互的调用时序。
@startuml
actor User as user
participant "前端" as fe
participant "网关/鉴权" as gateway
participant "组织架构服务" as orgSvc
database "Redis" as cache
database "MySQL" as db
user -> fe : 进入组织架构页面
fe -> gateway : GET /api/v1/departments/tree
gateway -> orgSvc : 查询部门树
orgSvc -> cache : GET dept:tree:all
alt 缓存命中
cache --> orgSvc : 返回部门树JSON
else 缓存未命中
orgSvc -> db : SELECT 全量部门 + 闭包表
db --> orgSvc : 返回部门列表与层级关系
orgSvc -> orgSvc : 内存构建树结构
orgSvc -> cache : SET dept:tree:all (TTL 1h)
end
orgSvc --> fe : 返回部门树(含人数统计)
fe --> user : 渲染部门树
@enduml
@startuml
actor HR as hr
participant "前端" as fe
participant "组织架构服务" as orgSvc
participant "统计服务" as statSvc
database "Redis" as cache
database "MySQL" as db
hr -> fe : 点击部门查看人数
fe -> orgSvc : GET /api/v1/departments/{id}/member-count
orgSvc -> cache : GET dept:stat:{id}
alt 缓存命中
cache --> orgSvc : 返回统计结果
else 缓存未命中
orgSvc -> db : 查询部门闭包表获取所有子部门ID
db --> orgSvc : 返回子部门ID列表(含自身)
orgSvc -> statSvc : 统计在职人数(部门ID集合)
statSvc -> db : 统计在职人数
note right of db
SELECT COUNT(*) FROM employee_work_info ew
JOIN employee e ON e.id = ew.employee_id
WHERE ew.department_id IN (:deptIds)
AND e.status IN (1, 2)
-- 1=试用期 2=正式
end note
db --> statSvc : 返回在职人数
statSvc --> orgSvc : 返回 onlineCount
orgSvc -> cache : SET dept:stat:{id} (TTL 5min)
end
orgSvc --> fe : 返回 { totalCount, onlineCount, subDeptCount }
fe --> hr : 展示部门人数(含下属部门)
@enduml
@startuml
actor HR as hr
participant "前端" as fe
participant "组织架构服务" as orgSvc
database "MySQL" as db
database "Redis" as cache
hr -> fe : 填写部门信息并提交
fe -> orgSvc : POST /api/v1/departments
orgSvc -> db : 校验部门编码唯一性
db --> orgSvc : 编码可用
orgSvc -> orgSvc : 校验层级深度 ≤ 5(查询父部门 depth)
alt 层级超限
orgSvc --> fe : 返回错误"超出最大层级深度5级"
else 层级合法
orgSvc -> db : INSERT department + 维护闭包表(事务)
orgSvc -> db : INSERT department_change_log(变更历史)
orgSvc -> cache : DEL dept:tree:all(失效缓存)
orgSvc -> cache : DEL dept:stat:*(失效人数缓存)
orgSvc --> fe : 返回部门ID
fe --> hr : 创建成功,刷新部门树
end
@enduml
组织架构管理模块涉及的核心数据表。技术栈:Spring Boot + MyBatis + MySQL + Redis。
CREATE TABLE IF NOT EXISTS `department` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` VARCHAR(64) NOT NULL COMMENT '部门名称,如"技术部"',
`code` VARCHAR(8) NOT NULL COMMENT '部门编码,用于工号生成(年份+部门编码+序号),全局唯一',
`parent_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '上级部门ID,空表示根部门',
`manager_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '部门负责人ID,关联employee.id,决定数据权限范围',
`sort_order` INT NOT NULL DEFAULT 0 COMMENT '排序序号,越小越靠前',
`description` VARCHAR(256) DEFAULT NULL COMMENT '部门描述,职能说明',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1=启用 0=停用',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=否 1=是',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_manager_id` (`manager_id`),
KEY `idx_status_sort` (`status`, `sort_order`)
) DEFAULT CHARACTER SET = utf8mb4 COMMENT = '部门表,维护多级组织架构';
CREATE TABLE IF NOT EXISTS `department_closure` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`ancestor_id` BIGINT UNSIGNED NOT NULL COMMENT '祖先部门ID,关联department.id',
`descendant_id` BIGINT UNSIGNED NOT NULL COMMENT '后代部门ID,关联department.id',
`depth` INT NOT NULL COMMENT '层级深度:0=自身,1=直接子级,2=孙级...最大4(共5级)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_ancestor_descendant` (`ancestor_id`, `descendant_id`),
KEY `idx_ancestor_id` (`ancestor_id`),
KEY `idx_descendant_id` (`descendant_id`),
KEY `idx_depth` (`depth`)
) DEFAULT CHARACTER SET = utf8mb4 COMMENT = '部门闭包表,存储祖先-后代关系,优化递归查询';
-- 说明:查询某部门所有子孙部门:SELECT descendant_id FROM department_closure WHERE ancestor_id = ?
-- 查询某部门所有祖先:SELECT ancestor_id FROM department_closure WHERE descendant_id = ?
-- 查询某部门层级深度:SELECT MAX(depth) FROM department_closure WHERE descendant_id = ?
CREATE TABLE IF NOT EXISTS `position` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` VARCHAR(64) NOT NULL COMMENT '职位名称,如"Java开发工程师"',
`sequence` VARCHAR(2) NOT NULL COMMENT '职位序列:M=管理序列 P=专业序列 S=支持序列',
`department_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '所属部门ID,空表示全公司通用',
`level_range` VARCHAR(16) NOT NULL COMMENT '职级范围,如P1-P10、M1-M5、S1-S5',
`default_probation_months` INT NOT NULL DEFAULT 3 COMMENT '默认试用期月数,发起入职时自动填充',
`description` VARCHAR(512) DEFAULT NULL COMMENT '职位描述,岗位职责说明',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1=启用 0=停用',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0=否 1=是',
PRIMARY KEY (`id`),
KEY `idx_sequence` (`sequence`),
KEY `idx_department_id` (`department_id`),
KEY `idx_status` (`status`),
KEY `idx_name_sequence` (`name`, `sequence`)
) DEFAULT CHARACTER SET = utf8mb4 COMMENT = '职位表,维护职位序列与职级范围';
CREATE TABLE IF NOT EXISTS `department_stat` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`dept_id` BIGINT UNSIGNED NOT NULL COMMENT '部门ID,关联department.id',
`total_count` INT NOT NULL DEFAULT 0 COMMENT '总人数(含所有在职状态)',
`online_count` INT NOT NULL DEFAULT 0 COMMENT '在职人数,口径:试用期或正式状态',
`sub_dept_count` INT NOT NULL DEFAULT 0 COMMENT '下属部门数量',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '统计更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_dept_id` (`dept_id`),
KEY `idx_online_count` (`online_count`)
) DEFAULT CHARACTER SET = utf8mb4 COMMENT = '部门统计表,缓存部门人数统计结果';
CREATE TABLE IF NOT EXISTS `department_change_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`dept_id` BIGINT UNSIGNED NOT NULL COMMENT '部门ID,关联department.id',
`change_type` VARCHAR(32) NOT NULL COMMENT '变更类型:CREATE/UPDATE/DELETE/MERGE/MOVE/STATUS_CHANGE',
`field_name` VARCHAR(64) DEFAULT NULL COMMENT '变更字段名(UPDATE时记录)',
`old_value` VARCHAR(512) DEFAULT NULL COMMENT '变更前值',
`new_value` VARCHAR(512) DEFAULT NULL COMMENT '变更后值',
`operator_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '操作人ID,关联employee.id',
`remark` VARCHAR(256) DEFAULT NULL COMMENT '备注,如合并时记录源部门与目标部门',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '变更时间',
PRIMARY KEY (`id`),
KEY `idx_dept_id` (`dept_id`),
KEY `idx_change_type` (`change_type`),
KEY `idx_operator_id` (`operator_id`),
KEY `idx_create_time` (`create_time`)
) DEFAULT CHARACTER SET = utf8mb4 COMMENT = '部门变更历史表,记录合并/层级调整/负责人变更等操作';
组织架构管理对外提供的核心接口。
GET /api/v1/departments/tree
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| includeCount | Boolean | 否 | 是否包含人数统计,默认 true |
| status | Integer | 否 | 部门状态筛选:1=启用 0=停用,默认全部 |
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"name": "总公司",
"code": "00",
"parentId": null,
"managerId": 1000,
"managerName": "张总",
"sortOrder": 1,
"status": 1,
"onlineCount": 156,
"subDeptCount": 3,
"children": [
{
"id": 10,
"name": "技术部",
"code": "01",
"parentId": 1,
"managerId": 1001,
"managerName": "李工",
"sortOrder": 1,
"status": 1,
"onlineCount": 45,
"subDeptCount": 2,
"children": [
{
"id": 101,
"name": "后端组",
"code": "0101",
"parentId": 10,
"managerId": 1002,
"sortOrder": 1,
"status": 1,
"onlineCount": 20,
"subDeptCount": 0,
"children": []
}
]
}
]
}
]
}
POST /api/v1/departments
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| name | String | 是 | 部门名称,如"技术部" |
| code | String | 是 | 部门编码,用于工号生成,全局唯一 |
| parentId | Long | 否 | 上级部门ID,空表示根部门 |
| managerId | Long | 否 | 部门负责人ID,决定数据权限 |
| sortOrder | Integer | 是 | 排序序号,越小越靠前 |
| description | String | 否 | 部门描述,职能说明 |
{
"code": 0,
"message": "success",
"data": {
"id": 102,
"name": "前端组",
"code": "0102",
"parentId": 10,
"depth": 3
}
}
PUT /api/v1/departments/{id}
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| id | Long | 是 | 部门ID(路径参数) |
| name | String | 否 | 部门名称 |
| code | String | 否 | 部门编码(变更影响工号生成,谨慎) |
| parentId | Long | 否 | 上级部门ID(变更需校验层级深度) |
| managerId | Long | 否 | 部门负责人ID |
| sortOrder | Integer | 否 | 排序序号 |
| description | String | 否 | 部门描述 |
| status | Integer | 否 | 状态:1=启用 0=停用 |
{
"code": 0,
"message": "success",
"data": {
"id": 102,
"updatedFields": ["name", "managerId", "sortOrder"],
"depth": 3
}
}
DELETE /api/v1/departments/{id}
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| id | Long | 是 | 部门ID(路径参数) |
{
"code": 0,
"message": "success",
"data": {
"id": 102,
"deleted": true
}
}
GET /api/v1/departments/{id}/member-count
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| id | Long | 是 | 部门ID(路径参数) |
| includeSubDept | Boolean | 否 | 是否包含下属部门,默认 true |
{
"code": 0,
"message": "success",
"data": {
"deptId": 10,
"deptName": "技术部",
"totalCount": 50,
"onlineCount": 45,
"probationCount": 5,
"regularCount": 40,
"subDeptCount": 2,
"subDeptBreakdown": [
{ "deptId": 101, "deptName": "后端组", "onlineCount": 20 },
{ "deptId": 102, "deptName": "前端组", "onlineCount": 25 }
],
"updateTime": "2026-07-09T14:30:00"
}
}
GET /api/v1/positions
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| keyword | String | 否 | 职位名称模糊搜索 |
| sequence | String | 否 | 职位序列筛选:M=管理 P=专业 S=支持 |
| departmentId | Long | 否 | 所属部门ID筛选 |
| includeGeneral | Boolean | 否 | 是否包含全公司通用职位(departmentId 为空),默认 true |
| status | Integer | 否 | 状态筛选:1=启用 0=停用,默认启用 |
| page | Integer | 否 | 页码,默认 1 |
| size | Integer | 否 | 每页条数,默认 20 |
{
"code": 0,
"message": "success",
"data": {
"total": 35,
"page": 1,
"size": 20,
"records": [
{
"id": 20,
"name": "Java开发工程师",
"sequence": "P",
"sequenceDesc": "专业序列",
"departmentId": 10,
"departmentName": "技术部",
"levelRange": "P3-P9",
"defaultProbationMonths": 3,
"description": "负责后端服务开发",
"status": 1,
"createTime": "2026-06-01T10:00:00"
}
]
}
}
POST /api/v1/positions
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| name | String | 是 | 职位名称,如"Java开发工程师" |
| sequence | String | 是 | 职位序列:M=管理序列 P=专业序列 S=支持序列 |
| departmentId | Long | 否 | 所属部门ID,空表示全公司通用 |
| levelRange | String | 是 | 职级范围,如 P1-P10,需与 sequence 匹配 |
| defaultProbationMonths | Integer | 是 | 默认试用期月数,发起入职时自动填充 |
| description | String | 否 | 职位描述,岗位职责说明 |
{
"code": 0,
"message": "success",
"data": {
"id": 21,
"name": "前端开发工程师",
"sequence": "P",
"levelRange": "P3-P8",
"defaultProbationMonths": 3
}
}
部门树是组织架构管理中查询频率最高的数据,采用 Redis 缓存完整部门树 JSON,避免每次查询都读取 MySQL 全表并内存构建树。
dept:tree:all,TTL 1 小时DEL dept:tree:all 失效缓存,下次查询时重建lock:dept:tree:build),防止并发回源同时构建传统递归查询子部门使用 MySQL 8.0 的 CTE 或多次查询,性能差且不可控。采用闭包表(Closure Table)存储祖先-后代关系,将递归查询转化为单次 JOIN。
department_closure 存储每对祖先-后代关系及层级深度(depth),depth=0 表示自身,depth=1 表示直接子级SELECT descendant_id FROM department_closure WHERE ancestor_id = ?,单次查询返回所有层级子部门SELECT ancestor_id FROM department_closure WHERE descendant_id = ?SELECT MAX(depth) FROM department_closure WHERE descendant_id = ?,新增子部门时校验 depth + 1 ≤ 5部门编码用于工号生成(年份 + 部门编码 + 序号),其唯一性直接影响工号生成的正确性,采用数据库唯一索引 + 应用层校验双重保障。
department 表对 code 字段建立唯一索引 uk_code,作为最终兜底,防止并发场景下重复编码写入code 是否已存在(排除逻辑删除记录),存在则直接返回错误"部门编码已存在"部门人数统计需实时反映"试用期 + 正式"口径的在职员工数,并含所有下属部门。采用缓存 + 定时刷新策略平衡实时性与性能。
SELECT COUNT(*) FROM employee_work_info ew JOIN employee e ON e.id = ew.employee_id WHERE ew.department_id IN (SELECT descendant_id FROM department_closure WHERE ancestor_id = ?) AND e.status IN (1, 2)dept:stat:{id},TTL 5 分钟),部门变更时主动失效相关缓存department_stat,保证缓存过期后数据仍较新规格要求部门最大层级深度为 5 级,采用应用层校验结合闭包表 depth 字段实现严格限制。
parentDepth = MAX(depth) WHERE descendant_id = parentId,若 parentDepth + 1 > 5 则拒绝操作对研发时间计划进行排期。
| 阶段 | 内容 | 预估工期 |
|---|---|---|
| 需求评审 | 评审产品规格,确认部门/职位字段定义、层级限制、合并规则 | 1天 |
| 技术方案 | 完成系分文档评审,确认闭包表方案、缓存策略、接口方案 | 2天 |
| 数据库开发 | 建表(department/closure/position/stat/change_log)、索引优化、闭包表初始化脚本 | 2天 |
| 后端开发 | 部门CRUD、闭包表维护、职位CRUD、人数统计、合并流程、变更审计 | 6天 |
| 前端开发 | 部门树页面、部门编辑页、职位列表页、职位编辑页、人数统计展示 | 4天 |
| 联调测试 | 前后端联调、层级深度校验、合并流程测试、缓存命中率测试 | 2天 |
| 回归上线 | 全量回归、历史数据治理、预发验证、正式上线 | 1天 |
总预估工期:约 18 个工作日