eagleye

部门层级关系设计一致性修复方案文档[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. 优化数据流转

前端传递 ID 确保数据准确性

o 后端返回编码提升用户体验

o 中间层处理转换逻辑

3. 提升系统性能

o 减少关联查询次数

o 添加适当冗余字段

o 明确的数据验证

这种设计既保持了数据库关系的规范性,又满足了 API 接口的可用性需求,同时通过分层处理确保了数据在各环节的一致性和安全性。

 

posted on 2025-08-13 18:57  GoGrid  阅读(11)  评论(0)    收藏  举报

导航