/
h0tnanny
/
IotPlatform
Обзор
Документация
Войти
/
h0tnanny
/
IotPlatform
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/api/controllers/EquipmentController.ts
431 строка
13 KB
h0tnanny
Парсинг данных как строчки
20 фев 2026, 19:31
20 фев 2026, 19:31
fbb7684
Код
Авторство
О чём код?
import { Request, Response } from 'express'; import { randomUUID } from 'crypto'; import { Main } from '../../runtime/Main'; import { Equipment, DataSourceProtocol, DataMode } from '../../entities/Equipment'; import { Field } from '../../entities/Field'; import { EquipmentRepository } from '../../repositories/EquipmentRepository'; import { PermissionRepository } from '../../repositories/PermissionRepository'; import { AuthRequest } from '../../middleware/auth'; import { LogService } from '../../services/LogService'; export class EquipmentController { private readonly equipmentRepository: EquipmentRepository; private readonly permissionRepo: PermissionRepository; constructor(private readonly main: Main) { this.equipmentRepository = new EquipmentRepository(); this.permissionRepo = new PermissionRepository(); } /** * POST /equipment - Добавить источник данных */ createEquipment = async (req: AuthRequest, res: Response): Promise<void> => { let { id, name, protocol, dataMode, endpoint, variableList, pollInterval, mqttBrokerUrl, mqttTopic, payloadFormat, payloadOptions, } = req.body; // Автоматически генерируем GUID, если ID не предоставлен if (!id) { id = randomUUID(); } // Валидация обязательных полей if (!name || !protocol || !dataMode || !endpoint || !variableList || !Array.isArray(variableList)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Необходимы поля: name, protocol (http/udp), dataMode (active/passive), endpoint, variableList (массив)', }, }); return; } // Валидация протокола const validProtocols = ['http', 'udp', 'mqtt', 'coap']; if (!validProtocols.includes(protocol)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'protocol должен быть "http", "udp", "mqtt" или "coap"', }, }); return; } // Валидация режима if (dataMode !== 'active' && dataMode !== 'passive') { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'dataMode должен быть "active" или "passive"', }, }); return; } // Для активного режима обязателен pollInterval (кроме MQTT) if (dataMode === 'active' && protocol !== 'mqtt' && !pollInterval) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Для активного режима необходим pollInterval', }, }); return; } // Для MQTT обязательны brokerUrl и topic if (protocol === 'mqtt') { if (!mqttBrokerUrl || !mqttTopic) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Для протокола MQTT необходимы mqttBrokerUrl и mqttTopic', }, }); return; } } const fields: Field[] = variableList.map((varData: unknown) => { const v = varData as { name: string; description?: string; type: 'String' | 'Number' | 'Double' | 'Boolean'; value?: unknown; }; return { name: v.name, description: v.description || '', type: v.type, value: v.value ?? this.getDefaultValue(v.type), }; }); const equipment = new Equipment( id, name, protocol as DataSourceProtocol, dataMode as DataMode, endpoint, fields, pollInterval || 0, mqttBrokerUrl, mqttTopic, payloadFormat, payloadOptions ); this.main.addEquipment(equipment); // Сохраняем в БД try { await this.equipmentRepository.save(equipment); if (req.user) { await this.permissionRepo.setOwner('equipment', equipment.id, req.user.userId); } } catch (error) { console.error('[EquipmentController] Ошибка сохранения equipment в БД:', error); } LogService.getInstance().info('equipment', `Источник данных создан: "${equipment.name}"`, { userId: req.user?.userId, resourceId: equipment.id, resourceType: 'equipment', }); res.status(201).json({ data: { id: equipment.id, name: equipment.name, protocol: equipment.protocol, dataMode: equipment.dataMode, endpoint: equipment.endpoint, pollInterval: equipment.pollInterval, variablesCount: equipment.getFieldsCopy().length, }, }); }; /** * PUT /equipment/:id - Обновить источник данных */ updateEquipment = async (req: AuthRequest, res: Response): Promise<void> => { const { id } = req.params; const { name, protocol, dataMode, endpoint, variableList, pollInterval, mqttBrokerUrl, mqttTopic, payloadFormat, payloadOptions, } = req.body; const existingEquipment = this.main.getEquipment(id); if (!existingEquipment) { res.status(404).json({ error: { code: 'NOT_FOUND', message: `Источник данных с ID "${id}" не найден`, }, }); return; } // Валидация полей (аналогично createEquipment) if (!name || !protocol || !dataMode || !endpoint || !variableList || !Array.isArray(variableList)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Необходимы поля: name, protocol (http/udp), dataMode (active/passive), endpoint, variableList (массив)', }, }); return; } const validProtocols = ['http', 'udp', 'mqtt', 'coap']; if (!validProtocols.includes(protocol)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'protocol должен быть "http", "udp", "mqtt" или "coap"', }, }); return; } if (dataMode !== 'active' && dataMode !== 'passive') { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'dataMode должен быть "active" или "passive"', }, }); return; } if (dataMode === 'active' && protocol !== 'mqtt' && !pollInterval) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Для активного режима необходим pollInterval', }, }); return; } if (protocol === 'mqtt') { if (!mqttBrokerUrl || !mqttTopic) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Для протокола MQTT необходимы mqttBrokerUrl и mqttTopic', }, }); return; } } const fields: Field[] = variableList.map((varData: unknown) => { const v = varData as { name: string; description?: string; type: 'String' | 'Number' | 'Double' | 'Boolean'; value?: unknown; }; return { name: v.name, description: v.description || '', type: v.type, value: v.value ?? this.getDefaultValue(v.type), }; }); // Останавливаем старый источник existingEquipment.stop(); // Удаляем старый источник this.main.removeEquipment(id); // Создаем новый источник с обновленными данными const equipment = new Equipment( id, name, protocol as DataSourceProtocol, dataMode as DataMode, endpoint, fields, pollInterval || 0, mqttBrokerUrl, mqttTopic, payloadFormat, payloadOptions ); this.main.addEquipment(equipment); // Сохраняем в БД try { await this.equipmentRepository.save(equipment); } catch (error) { console.error('[EquipmentController] Ошибка обновления equipment в БД:', error); // Продолжаем выполнение, так как equipment уже в памяти } LogService.getInstance().info('equipment', `Источник данных обновлён: "${equipment.name}"`, { userId: req.user?.userId, resourceId: equipment.id, resourceType: 'equipment', }); res.json({ data: { id: equipment.id, name: equipment.name, protocol: equipment.protocol, dataMode: equipment.dataMode, endpoint: equipment.endpoint, pollInterval: equipment.pollInterval, variablesCount: equipment.getFieldsCopy().length, }, }); }; /** * POST /equipment/:id/data - Получить данные от внешнего источника (для пассивного режима). * Body может быть JSON (application/json) или произвольная строка (text/plain) — * в последнем случае интерпретируется по настройке payloadFormat оборудования. */ receiveData = (req: Request, res: Response): void => { const { id } = req.params; const rawBody = req.body; const equipment = this.main.getEquipment(id); if (!equipment) { res.status(404).json({ error: { code: 'NOT_FOUND', message: `Источник данных с ID "${id}" не найден`, }, }); return; } if (equipment.dataMode !== 'passive') { res.status(400).json({ error: { code: 'INVALID_MODE', message: `Источник данных "${equipment.name}" работает в активном режиме и не принимает внешние данные`, }, }); return; } const data = typeof rawBody === 'string' ? equipment.parseIncomingPayload(rawBody) : (rawBody as Record<string, unknown>); equipment.updateFromExternal(data); res.json({ data: { message: `Данные для источника "${equipment.name}" успешно обновлены`, updatedFields: Object.keys(data), }, }); }; /** * POST /equipment/:id/command - Отправить команду на устройство */ sendCommand = async (req: AuthRequest, res: Response): Promise<void> => { const { id } = req.params; const { command, payload } = req.body; const equipment = this.main.getEquipment(id); if (!equipment) { res.status(404).json({ error: { code: 'NOT_FOUND', message: `Источник данных с ID "${id}" не найден`, }, }); return; } if (!command) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Необходимо поле command', }, }); return; } try { await equipment.sendCommand(command, payload || {}); LogService.getInstance().info('equipment', `Команда "${command}" отправлена на "${equipment.name}"`, { userId: req.user?.userId, resourceId: id, resourceType: 'equipment', details: { command, payload }, }); res.json({ data: { message: `Команда "${command}" успешно отправлена на "${equipment.name}"`, }, }); } catch (error) { const errMsg = error instanceof Error ? error.message : 'Неизвестная ошибка'; LogService.getInstance().error('equipment', `Ошибка отправки команды на "${equipment.name}": ${errMsg}`, { userId: req.user?.userId, resourceId: id, resourceType: 'equipment', details: { command, payload, error: errMsg }, }); res.status(500).json({ error: { code: 'COMMAND_ERROR', message: `Ошибка отправки команды: ${errMsg}`, }, }); } }; /** * Возвращает значение по умолчанию для типа поля */ private getDefaultValue(type: 'String' | 'Number' | 'Double' | 'Boolean'): unknown { switch (type) { case 'String': return ''; case 'Number': return 0; case 'Double': return 0.0; case 'Boolean': return false; } } }