feat: add field-level data protection for BOM and user management
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
@ -84,6 +84,32 @@ def login():
|
||||
def create_user():
|
||||
try:
|
||||
data = request.get_json()
|
||||
# 数据清洗:移除用户没有权限的字段
|
||||
user_permissions = get_current_user_permissions()
|
||||
# 超级管理员不过滤
|
||||
if 'system_user:*' not in user_permissions:
|
||||
# 字段名到权限码的映射
|
||||
field_to_perm = {
|
||||
'cn_name': 'system_user:username',
|
||||
'username': 'system_user:username',
|
||||
'password': 'system_user:password',
|
||||
'department': 'system_user:department',
|
||||
'role': 'system_user:role',
|
||||
'email': 'system_user:email',
|
||||
}
|
||||
# 对于 password 字段,如果没有对应权限但用户有操作权限,可以保留(由装饰器保证)
|
||||
# 但如果连操作权限都没有,则不会进入此接口。
|
||||
for field in list(data.keys()):
|
||||
perm_code = field_to_perm.get(field)
|
||||
# 密码字段特殊处理:如果没有 password 权限但用户有操作权限,仍允许(不删除)
|
||||
if field == 'password':
|
||||
# 检查用户是否有操作权限,如果有则保留
|
||||
if 'system_user:operation' not in user_permissions:
|
||||
data.pop(field, None)
|
||||
continue
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
data.pop(field, None)
|
||||
|
||||
claims = get_jwt()
|
||||
operator_role = claims.get('role')
|
||||
|
||||
@ -102,6 +128,30 @@ def create_user():
|
||||
def update_user(user_id):
|
||||
try:
|
||||
data = request.get_json()
|
||||
# 数据清洗:移除用户没有权限的字段
|
||||
user_permissions = get_current_user_permissions()
|
||||
# 超级管理员不过滤
|
||||
if 'system_user:*' not in user_permissions:
|
||||
# 字段名到权限码的映射
|
||||
field_to_perm = {
|
||||
'cn_name': 'system_user:username',
|
||||
'username': 'system_user:username',
|
||||
'password': 'system_user:password',
|
||||
'department': 'system_user:department',
|
||||
'role': 'system_user:role',
|
||||
'email': 'system_user:email',
|
||||
}
|
||||
for field in list(data.keys()):
|
||||
perm_code = field_to_perm.get(field)
|
||||
# 密码字段特殊处理:如果没有 password 权限但用户有操作权限,仍允许(不删除)
|
||||
if field == 'password':
|
||||
# 检查用户是否有操作权限,如果有则保留
|
||||
if 'system_user:operation' not in user_permissions:
|
||||
data.pop(field, None)
|
||||
continue
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
data.pop(field, None)
|
||||
|
||||
claims = get_jwt()
|
||||
operator_role = claims.get('role')
|
||||
|
||||
|
||||
@ -113,6 +113,36 @@ def save_bom():
|
||||
"""保存或更新 BOM 配方(支持自定义 bom_no 和 多版本)"""
|
||||
try:
|
||||
req_data = request.get_json()
|
||||
# 数据清洗:移除用户没有权限的字段
|
||||
user_permissions = get_current_user_permissions()
|
||||
# 超级管理员不过滤
|
||||
if 'bom_manage:*' not in user_permissions:
|
||||
# 字段名到权限码的映射
|
||||
field_to_perm = {
|
||||
'parent_id': 'bom_manage:parent_id',
|
||||
'version': 'bom_manage:version',
|
||||
'is_enabled': 'bom_manage:status',
|
||||
'bom_no': 'bom_manage:bom_no',
|
||||
}
|
||||
# 清洗顶级字段
|
||||
for field in list(req_data.keys()):
|
||||
perm_code = field_to_perm.get(field)
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
req_data.pop(field, None)
|
||||
# 清洗 children 中的字段
|
||||
if 'children' in req_data and isinstance(req_data['children'], list):
|
||||
for child in req_data['children']:
|
||||
# 子件字段映射
|
||||
child_field_to_perm = {
|
||||
'child_id': 'bom_manage:child_id',
|
||||
'dosage': 'bom_manage:dosage',
|
||||
'remark': 'bom_manage:remark',
|
||||
}
|
||||
for field in list(child.keys()):
|
||||
perm_code = child_field_to_perm.get(field)
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
child.pop(field, None)
|
||||
|
||||
# 必需字段校验
|
||||
if 'parent_id' not in req_data or 'children' not in req_data:
|
||||
return jsonify({'code': 400, 'msg': '缺少 parent_id 或 children 字段'}), 400
|
||||
@ -216,6 +246,36 @@ def get_bom(parent_id):
|
||||
def save_bom_legacy():
|
||||
try:
|
||||
req_data = request.get_json()
|
||||
# 数据清洗:移除用户没有权限的字段
|
||||
user_permissions = get_current_user_permissions()
|
||||
# 超级管理员不过滤
|
||||
if 'bom_manage:*' not in user_permissions:
|
||||
# 字段名到权限码的映射
|
||||
field_to_perm = {
|
||||
'parent_id': 'bom_manage:parent_id',
|
||||
'version': 'bom_manage:version',
|
||||
'is_enabled': 'bom_manage:status',
|
||||
'bom_no': 'bom_manage:bom_no',
|
||||
}
|
||||
# 清洗顶级字段
|
||||
for field in list(req_data.keys()):
|
||||
perm_code = field_to_perm.get(field)
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
req_data.pop(field, None)
|
||||
# 清洗 children 中的字段
|
||||
if 'children' in req_data and isinstance(req_data['children'], list):
|
||||
for child in req_data['children']:
|
||||
# 子件字段映射
|
||||
child_field_to_perm = {
|
||||
'child_id': 'bom_manage:child_id',
|
||||
'dosage': 'bom_manage:dosage',
|
||||
'remark': 'bom_manage:remark',
|
||||
}
|
||||
for field in list(child.keys()):
|
||||
perm_code = child_field_to_perm.get(field)
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
child.pop(field, None)
|
||||
|
||||
parent_id = req_data.get('parent_id')
|
||||
child_list = req_data.get('children', [])
|
||||
if not parent_id or not isinstance(child_list, list):
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="父件 (成品)" prop="parent_id">
|
||||
<el-form-item label="父件 (成品)" prop="parent_id" v-if="hasFormFieldPermission('parent_id')">
|
||||
<el-select
|
||||
v-model="form.parent_id"
|
||||
placeholder="请搜索并选择父件"
|
||||
@ -79,7 +79,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="是否启用" prop="is_enabled">
|
||||
<el-form-item label="是否启用" prop="is_enabled" v-if="hasFormFieldPermission('is_enabled')">
|
||||
<el-switch v-model="form.is_enabled" active-text="启用" inactive-text="禁用" :disabled="!userStore.hasPermission('bom_manage:operation')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -87,7 +87,7 @@
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="14">
|
||||
<el-form-item label="BOM 编号" required>
|
||||
<el-form-item label="BOM 编号" required v-if="hasFormFieldPermission('bom_suffix')">
|
||||
<el-input v-model="form.bom_suffix" placeholder="输入后缀 (如 -001)" :disabled="isEditMode">
|
||||
<template #prepend v-if="form.bom_prefix">{{ form.bom_prefix }}</template>
|
||||
</el-input>
|
||||
@ -97,7 +97,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item label="版本号" prop="version">
|
||||
<el-form-item label="版本号" prop="version" v-if="hasFormFieldPermission('version')">
|
||||
<el-input v-model="form.version" placeholder="如: V1.0" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -106,7 +106,7 @@
|
||||
<div style="font-weight: bold; margin: 15px 0 10px 0; border-left: 4px solid #409EFF; padding-left: 10px;">子件列表</div>
|
||||
|
||||
<el-table :data="form.children" border style="width: 100%; margin-bottom: 15px" max-height="300">
|
||||
<el-table-column label="子件物料" min-width="280">
|
||||
<el-table-column label="子件物料" min-width="280" v-if="hasFormFieldPermission('child_id')">
|
||||
<template #default="{ row, $index }">
|
||||
<el-select
|
||||
v-model="row.child_id"
|
||||
@ -129,26 +129,26 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="用量" width="140">
|
||||
<el-table-column label="用量" width="140" v-if="hasFormFieldPermission('dosage')">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.dosage" :min="0" :precision="4" style="width: 100%" controls-position="right" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="备注" width="150">
|
||||
<el-table-column label="备注" width="150" v-if="hasFormFieldPermission('remark')">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="60" align="center">
|
||||
<el-table-column label="操作" width="60" align="center" v-if="userStore.hasPermission('bom_manage:operation')">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" link @click="removeChild($index)">删</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div style="margin-top: 10px; text-align: center;">
|
||||
<div style="margin-top: 10px; text-align: center;" v-if="hasFormFieldPermission('child_id')">
|
||||
<el-button type="primary" plain :icon="Plus" @click="addChild" style="width: 100%">添加一行子件</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
@ -209,6 +209,13 @@ const permissionMap: Record<string, string> = {
|
||||
version: 'bom_manage:version',
|
||||
status: 'bom_manage:status',
|
||||
child_count: 'bom_manage:child_count',
|
||||
// 表单字段
|
||||
parent_id: 'bom_manage:parent_id',
|
||||
is_enabled: 'bom_manage:status',
|
||||
bom_suffix: 'bom_manage:bom_no',
|
||||
child_id: 'bom_manage:child_id',
|
||||
dosage: 'bom_manage:dosage',
|
||||
remark: 'bom_manage:remark',
|
||||
}
|
||||
|
||||
// 检查列权限
|
||||
@ -220,6 +227,15 @@ const hasColumnPermission = (prop: string) => {
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
|
||||
// 检查表单字段权限
|
||||
const hasFormFieldPermission = (fieldName: string) => {
|
||||
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
|
||||
return true
|
||||
}
|
||||
const code = permissionMap[fieldName]
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
bom_prefix: '', // 自动生成的父件规格前缀
|
||||
|
||||
@ -67,7 +67,7 @@
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="真实姓名" prop="cn_name">
|
||||
<el-form-item label="真实姓名" prop="cn_name" v-if="hasFormFieldPermission('cn_name')">
|
||||
<el-input
|
||||
v-model="form.cn_name"
|
||||
placeholder="请输入中文姓名 (如: 张三)"
|
||||
@ -76,7 +76,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="登录账号" prop="username">
|
||||
<el-form-item label="登录账号" prop="username" v-if="hasFormFieldPermission('username')">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="自动生成,可修改 (如: zhangsan)"
|
||||
@ -88,7 +88,7 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-form-item label="密码" prop="password" v-if="hasFormFieldPermission('password')">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
@ -98,7 +98,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="所属部门" prop="department">
|
||||
<el-form-item label="所属部门" prop="department" v-if="hasFormFieldPermission('department')">
|
||||
<el-select
|
||||
v-model="form.department"
|
||||
placeholder="请输入或选择部门"
|
||||
@ -112,7 +112,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="系统角色" prop="role">
|
||||
<el-form-item label="系统角色" prop="role" v-if="hasFormFieldPermission('role')">
|
||||
<el-select v-model="form.role" placeholder="授予权限" style="width: 100%" :disabled="!userStore.hasPermission('system_user:operation')">
|
||||
<el-option
|
||||
v-for="option in roleOptions"
|
||||
@ -123,7 +123,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-form-item label="邮箱" prop="email" v-if="hasFormFieldPermission('email')">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" :disabled="!userStore.hasPermission('system_user:operation')" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -157,6 +157,9 @@ const permissionMap: Record<string, string> = {
|
||||
email: 'system_user:email',
|
||||
status: 'system_user:status',
|
||||
created_at: 'system_user:created_at',
|
||||
// 表单字段
|
||||
cn_name: 'system_user:username',
|
||||
password: 'system_user:password',
|
||||
}
|
||||
|
||||
// 检查列权限
|
||||
@ -167,6 +170,15 @@ const hasColumnPermission = (prop: string) => {
|
||||
const code = permissionMap[prop]
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
|
||||
// 检查表单字段权限
|
||||
const hasFormFieldPermission = (fieldName: string) => {
|
||||
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
|
||||
return true
|
||||
}
|
||||
const code = permissionMap[fieldName]
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
const tableLoading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
Reference in New Issue
Block a user