部门层级关系设计一致性修复方案文档[code->id]
部门层级关系设计一致性修复方案文档
一、问题分析
1.1 核心矛盾点
当前系统设计中存在一个关键不一致问题:
- 模型层:parent字段使用TreeForeignKey存储部门实例的id(主键)
- 视图层:lookup_field = 'code'配置使 API 使用code作为资源标识符
- 数据流转:API 期望接收code作为parent字段值,但数据库实际需要id
1.2 技术表现
# 模型定义
parent = TreeForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='children',
verbose_name=_('上级部门')
)
# 视图定义
lookup_field = 'code' # 使用code作为查找字段
这种不一致导致在创建/更新部门时出现数据不匹配错误,API 期望接收code字符串但数据库需要整数id。
二、完整解决方案
2.1 序列化器改造
# organization/serializers.py
class DepartmentBaseSerializer(serializers.ModelSerializer):
# 添加可写的 parent_id 字段
parent_id = serializers.IntegerField(
write_only=True,
required=False,
allow_null=True,
help_text="上级部门的ID"
)
# 保留只读的 parent_code 用于显示
parent_code = serializers.CharField(
source='parent.code',
read_only=True,
allow_null=True
)
class Meta:
model = Department
fields = [
'id', 'name', 'code', 'node_type', 'node_type_display',
'parent_id', 'parent_code', # 修改后的关联字段
# ... 其他字段 ...
]
extra_kwargs = {
'parent': {'write_only': False} # 移除 parent 字段的 write_only 限制
}
def validate_parent_id(self, value):
"""验证 parent_id 是否存在"""
if value is not None:
try:
Department.objects.get(id=value)
except Department.DoesNotExist:
raise serializers.ValidationError("指定的上级部门不存在")
return value
def create(self, validated_data):
# 处理 parent_id
parent_id = validated_data.pop('parent_id', None)
if parent_id:
validated_data['parent'] = Department.objects.get(id=parent_id)
return super().create(validated_data)
def update(self, instance, validated_data):
# 处理 parent_id
parent_id = validated_data.pop('parent_id', None)
if parent_id is not None:
if parent_id:
validated_data['parent'] = Department.objects.get(id=parent_id)
else:
validated_data['parent'] = None
return super().update(instance, validated_data)
2.2 视图配置调整
# organization/views.py
class DepartmentViewSet(viewsets.ModelViewSet):
# 修改:使用 id 作为查找字段
lookup_field = 'id'
# ... 其他代码保持不变 ...
2.3 前端请求格式更新
// src/composable/useOrganization.ts
const createDepartment = async (department: Partial<Department>): Promise<Department | null> => {
// 准备请求数据
const payload: any = {
...department,
parent_id: department.parent_id || (department.parent as any)?.id
};
// 移除前端传递的 parent 字段
delete payload.parent;
loading.value = true;
try {
const response = await apiClient.post<Department>('/orgs/departments/', payload);
$q.notify({
type: 'positive',
message: '部门创建成功',
position: 'top'
});
return response.data;
} catch (err) {
handleError(err, '创建部门失败');
return null;
} finally {
loading.value = false;
}
}
2.4 测试数据规范
Apifox测试数据(创建子部门):
{
"name": "华东安全科",
"node_type": 32,
"parent_id": 2, // 使用父部门的 ID
"safety_responsibility": {
"safety_training": true,
"risk_assessment": true
},
"description": "负责华东生产中心的安全管理",
"is_active": true
}
三、关键修改说明
3.1 序列化器改进点
|
修改内容 |
目的 |
|
添加parent_id可写字段 |
接收前端传递的父部门ID |
|
添加parent_code只读字段 |
展示父部门编码,不暴露ID |
|
实现validate_parent_id方法 |
验证父部门ID有效性 |
|
重写create/update方法 |
处理父部门ID到实例的转换 |
|
调整parent字段权限 |
移除write_only限制 |
3.2 数据流转优化
新的数据流转流程:
1. 前端传递parent_id(整数)
2. 序列化器验证parent_id有效性
3. 序列化器将parent_id转换为部门实例
4. 模型层使用实例ID存储关系
5. 响应中返回parent_code(字符串)供前端展示
四、后端模型优化建议
为进一步优化查询性能,建议在模型中添加parent_id冗余字段:
# organization/models/department.py
class Department(MPTTModel):
parent = TreeForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='children',
verbose_name=_('上级部门'),
db_column='parent_id' # 明确指定数据库列名
)
parent_id = models.IntegerField(null=True, blank=True, editable=False)
def save(self, *args, **kwargs):
# 自动维护 parent_id
if self.parent:
self.parent_id = self.parent.id
else:
self.parent_id = None
super().save(*args, **kwargs)
五、迁移实施步骤
5.1 后端迁移
# 创建数据库迁移文件
python manage.py makemigrations organization
# 应用迁移
python manage.py migrate
5.2 前端更新
1. 数据模型调整:
interface Department {
id: number; // 确保使用 ID
name: string;
code: string;
node_type: OrganizationNodeType;
parent_id?: number | null; // 新增 parent_id 字段
parent?: Department; // 可选,用于存储完整对象
// ... 其他字段 ...
}
2. 核心方法适配:
// 更新其他方法使用 id
const updateDepartment = async (
id: number, // 改为使用 id
department: Partial<Department>,
): Promise<Department | null> => {
// ... 实现 ...
}
const deleteDepartment = async (id: number): Promise<boolean> => {
// ... 实现 ...
}
5.3 测试验证
使用 Apifox 进行验证测试:
请求:
POST /api/v1/orgs/departments/
Content-Type: application/json
{
"name": "生产安全部",
"node_type": 32,
"parent_id": 1, // 根部门的ID
"safety_responsibility": {
"risk_assessment": true
}
}
预期响应:
{
"id": 3,
"name": "生产安全部",
"code": "SAFE-001",
"node_type": 32,
"parent_id": 1,
"parent_code": "ORG-ROOT",
// ... 其他字段 ...
}
六、总结
本次修改通过以下措施解决了部门层级关系的设计不一致问题:
1. 明确数据职责:
o parent_id:用于内部数据关联(整数,可写)
o parent_code:用于外部展示(字符串,只读)
2. 优化数据流转:
o 前端传递 ID 确保数据准确性
o 后端返回编码提升用户体验
o 中间层处理转换逻辑
3. 提升系统性能:
o 减少关联查询次数
o 添加适当冗余字段
o 明确的数据验证
这种设计既保持了数据库关系的规范性,又满足了 API 接口的可用性需求,同时通过分层处理确保了数据在各环节的一致性和安全性。
浙公网安备 33010602011771号