refactor(devices): 实现设备字段动态校验与配置同步
1. 新增动态设备校验Schema生成器,支持从数据库读取字段配置生成校验规则 2. 重构设备增改接口,使用动态校验替代硬编码Schema 3. 调整前端设备表单,适配字段配置并锁定核心系统字段 4. 修复前后端默认字段配置不一致问题 5. 新增字段管理页面防护,锁定核心字段的必填/可见配置
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
const logger = require('../utils/logger').module('ValidationMiddleware');
|
||||
|
||||
const validate = (schema, source = 'body') => {
|
||||
const validate = (schemaOrFn, source = 'body') => {
|
||||
return async (req, res, next) => {
|
||||
const data = source === 'query' ? req.query : req.body;
|
||||
|
||||
try {
|
||||
// 支持函数形式的schema(动态schema:异步函数或同步函数返回Joi schema)
|
||||
let schema = schemaOrFn;
|
||||
if (typeof schemaOrFn === 'function') {
|
||||
schema = await schemaOrFn();
|
||||
}
|
||||
|
||||
let value;
|
||||
|
||||
if (schema.validate && typeof schema.validate === 'function') {
|
||||
|
||||
@@ -16,13 +16,12 @@ const {
|
||||
} = require('../utils/operationLogger');
|
||||
const { validateBody, validateQuery } = require('../middleware/validation');
|
||||
const {
|
||||
createDeviceSchema,
|
||||
updateDeviceSchema,
|
||||
batchDeviceIdsSchema,
|
||||
batchStatusSchema,
|
||||
batchMoveSchema,
|
||||
queryDeviceSchema,
|
||||
} = require('../validation/deviceSchema');
|
||||
const { createDeviceSchema } = require('../validation/dynamicDeviceSchema');
|
||||
|
||||
const PREVIEW_COUNT = 20;
|
||||
|
||||
@@ -736,7 +735,7 @@ async function generateDeviceId() {
|
||||
}
|
||||
|
||||
// 创建设备
|
||||
router.post('/', validateBody(createDeviceSchema), async (req, res) => {
|
||||
router.post('/', validateBody(() => createDeviceSchema(false)), async (req, res) => {
|
||||
try {
|
||||
const deviceData = { ...req.body };
|
||||
|
||||
@@ -2285,7 +2284,7 @@ router.get('/:deviceId/tickets', async (req, res) => {
|
||||
});
|
||||
|
||||
// 更新设备
|
||||
router.put('/:deviceId', validateBody(updateDeviceSchema), async (req, res) => {
|
||||
router.put('/:deviceId', validateBody(() => createDeviceSchema(true)), async (req, res) => {
|
||||
try {
|
||||
const oldDevice = await Device.findByPk(req.params.deviceId);
|
||||
if (!oldDevice) {
|
||||
|
||||
@@ -114,8 +114,6 @@ const queryDeviceSchema = Joi.object({
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createDeviceSchema,
|
||||
updateDeviceSchema,
|
||||
batchDeviceIdsSchema,
|
||||
batchStatusSchema,
|
||||
batchMoveSchema,
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 动态设备字段验证Schema生成器
|
||||
* 根据数据库DeviceField表的配置动态生成Joi验证Schema
|
||||
* 系统核心字段(name/serialNumber/position/height)强制锁定必填
|
||||
*/
|
||||
|
||||
const Joi = require('joi');
|
||||
const DeviceField = require('../models/DeviceField');
|
||||
|
||||
const DEVICE_TYPES = ['server', 'switch', 'router', 'storage', 'other'];
|
||||
const DEVICE_STATUS = ['running', 'maintenance', 'offline', 'fault', 'idle'];
|
||||
|
||||
// 强制锁定必填的字段(不受字段管理配置影响)
|
||||
const FORCE_REQUIRED_FIELDS = ['name', 'serialNumber', 'position', 'height'];
|
||||
|
||||
/**
|
||||
* 根据字段配置生成单个字段的Joi校验器
|
||||
* @param {Object} field - DeviceField数据库记录
|
||||
* @param {boolean} isUpdate - 是否为更新模式
|
||||
* @returns {Joi.AnySchema} 该字段对应的Joi校验器
|
||||
*/
|
||||
function buildFieldValidator(field, isUpdate) {
|
||||
const { fieldName, required: fieldRequired } = field;
|
||||
// 系统核心字段强制必填
|
||||
const isRequired = FORCE_REQUIRED_FIELDS.includes(fieldName) || fieldRequired;
|
||||
|
||||
let validator;
|
||||
|
||||
switch (fieldName) {
|
||||
case 'name':
|
||||
validator = Joi.string().max(100).messages({
|
||||
'string.empty': '设备名称不能为空',
|
||||
'string.max': '设备名称不能超过100个字符',
|
||||
'any.required': '设备名称是必填字段',
|
||||
});
|
||||
if (isRequired && !isUpdate) validator = validator.required();
|
||||
else validator = validator.allow('', null);
|
||||
break;
|
||||
|
||||
case 'type':
|
||||
validator = Joi.string().valid(...DEVICE_TYPES).messages({
|
||||
'any.only': `设备类型必须是以下之一: ${DEVICE_TYPES.join(', ')}`,
|
||||
'any.required': '设备类型是必填字段',
|
||||
});
|
||||
if (isRequired && !isUpdate) validator = validator.required();
|
||||
break;
|
||||
|
||||
case 'serialNumber':
|
||||
validator = Joi.string().max(100).messages({
|
||||
'string.empty': '序列号不能为空',
|
||||
'string.max': '序列号不能超过100个字符',
|
||||
'any.required': '序列号是必填字段',
|
||||
});
|
||||
if (isRequired && !isUpdate) validator = validator.required();
|
||||
else validator = validator.allow('', null);
|
||||
break;
|
||||
|
||||
case 'rackId':
|
||||
validator = Joi.string().allow('', null).max(50);
|
||||
break;
|
||||
|
||||
case 'position':
|
||||
validator = Joi.number().integer().min(1).max(100).allow(null);
|
||||
break;
|
||||
|
||||
case 'height':
|
||||
validator = Joi.number().integer().min(1).max(50).allow(null);
|
||||
break;
|
||||
|
||||
case 'powerConsumption':
|
||||
validator = Joi.number().min(0).max(100000).allow(null);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
validator = Joi.string().valid(...DEVICE_STATUS).default('offline');
|
||||
break;
|
||||
|
||||
case 'model':
|
||||
validator = Joi.string().allow('', null).max(100);
|
||||
break;
|
||||
|
||||
case 'ipAddress':
|
||||
validator = Joi.string().allow('', null).max(50);
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
validator = Joi.string().allow('', null).max(500);
|
||||
break;
|
||||
|
||||
case 'purchaseDate':
|
||||
case 'warrantyExpiry':
|
||||
validator = Joi.date().allow(null);
|
||||
break;
|
||||
|
||||
case 'brand':
|
||||
validator = Joi.string().allow('', null).max(100);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 自定义字段走宽松校验
|
||||
validator = Joi.any().allow(null);
|
||||
}
|
||||
|
||||
return validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态生成设备创建/更新的Joi Schema
|
||||
* 每次请求时从DeviceField表读取最新配置
|
||||
* @param {boolean} isUpdate - 是否为更新模式
|
||||
* @returns {Promise<Joi.ObjectSchema>} 动态生成的Joi Schema
|
||||
*/
|
||||
async function createDeviceSchema(isUpdate = false) {
|
||||
// 查询所有字段配置
|
||||
const fieldConfigs = await DeviceField.findAll({
|
||||
order: [['order', 'ASC']],
|
||||
});
|
||||
|
||||
const schemaMap = {};
|
||||
|
||||
// 根据字段配置动态生成每个字段的校验器
|
||||
fieldConfigs.forEach(field => {
|
||||
schemaMap[field.fieldName] = buildFieldValidator(field, isUpdate);
|
||||
});
|
||||
|
||||
// 补充customFields(不在DeviceField表中)
|
||||
schemaMap.customFields = Joi.object().allow(null);
|
||||
|
||||
let schema = Joi.object(schemaMap);
|
||||
|
||||
// 更新模式要求至少传一个字段
|
||||
if (isUpdate) {
|
||||
schema = schema.min(1).messages({
|
||||
'object.min': '至少需要提供一个字段进行更新',
|
||||
});
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createDeviceSchema,
|
||||
DEVICE_TYPES,
|
||||
DEVICE_STATUS,
|
||||
FORCE_REQUIRED_FIELDS,
|
||||
};
|
||||
Reference in New Issue
Block a user