feat: refs #4452 vn changes

This commit is contained in:
Guillermo Bonet 2024-10-23 14:49:07 +02:00
parent d28fc42570
commit afcb079407
14 changed files with 344 additions and 3433 deletions

View File

@ -1,3 +0,0 @@
node_modules
npm-debug.log
logs

View File

@ -1,59 +0,0 @@
extends:
- eslint:recommended
parserOptions:
ecmaVersion: 2020
sourceType: module
env:
node: true
es6: true
rules:
require-jsdoc: 0
no-undef: 0
max-len:
- error
- code: 120
eqeqeq: 0
operator-linebreak: 0
radix: 0
guard-for-in: 0
camelcase: 0
default-case: 0
no-eq-null: 0
no-console: warn
no-warning-comments: 0
no-empty:
- error
- allowEmptyCatch: true
complexity: 0
max-depth: 0
comma-dangle: 0
bracketSpacing: 0
space-infix-ops: 1
no-invalid-this: 0
space-before-function-paren:
- error
- never
prefer-const: 0
curly:
- error
- multi-or-nest
indent:
- error
- 4
arrow-parens:
- error
- as-needed
no-multiple-empty-lines:
- error
- max: 1
maxEOF: 1
space-in-parens:
- error
- never
arrow-spacing:
- error
- before: true
after: true

69
.gitignore vendored
View File

@ -1,66 +1,3 @@
# Node.js
node_modules/
npm-debug.log
yarn-error.log
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release/
# Optional npm cache directory
.npm/
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env*
# Next.js build output
.next/
# Nuxt.js build / generate output
.nuxt/
dist/
# Gatsby files
.cache/
# Comment in the public line in if your project deploys to "public" and not "dist"
# public/
# Docker
docker-compose.override.yml
docker-compose.debug.yml
docker-compose.test.yml
# macOS
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Linux
.Trash-*
# IDEs and editors
.vscode/
.idea/
*.swp
*~
zplData_*.txt
node_modules
.env
tmp

View File

@ -1,16 +1,29 @@
FROM node:latest
FROM debian:bookworm-slim
# Copiar los archivos de configuración
COPY package*.json ./
ENV TZ Europe/Madrid
# Instalar las dependencias
RUN npm install --only=production
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
sudo \
nodejs \
npm \
&& rm -rf /var/lib/apt/lists/*
# Copiar el resto de la aplicación
COPY . .
WORKDIR /rfidnatura
# Exponer el puerto
EXPOSE 3000
COPY \
package.json \
package-lock.json \
./
RUN npm install
# Comando por defecto para iniciar la aplicación
CMD ["npm", "start"]
COPY \
main.js \
log.js \
./
COPY db db
COPY resources resources
COPY worker worker
CMD ["node", "main.js"]

View File

@ -37,11 +37,16 @@ Este proyecto es una aplicación backend que utiliza Node.js, Docker, MariaDB y
4. Crea un archivo `.env` en el directorio raíz del proyecto y configura las variables de entorno necesarias:
```env
DB_HOST=mariadb
DB_PORT=3306
DB_USER=user
DB_PASSWORD=password
DB_NAME=database
DB_HOST = mariadb
DB_PORT = 3306
DB_USER = user
DB_PASSWORD = password
DB_NAME = name
DB_PRINTER_SCHEMA = name
WORKERS = 10
# DEBUG = 1
# KEEP_TMP_FILES = 1
# DRY_PRINT = 1
```
## Uso
@ -88,8 +93,7 @@ Define cómo construir la imagen Docker para el servicio de backend.
Maneja el procesamiento de registros de impresión y la comunicación con las impresoras.
- **Configuración de logger**: Se configura `log4js` para la salida de logs.
- **getConnectionWithRetries**: Intenta obtener una conexión a la base de datos con reintentos.
- **getConnWithRetries**: Intenta obtener una conexión a la base de datos con reintentos.
- **sendZPL**: Envía el contenido ZPL a la impresora con reintentos.
- **getPrinterIpAddress**: Obtiene la dirección IP de una impresora basada en su ID.
- **getRecordForProcessing**: Obtiene un registro para procesamiento, con manejo de transacciones y reintentos.
@ -140,11 +144,3 @@ Este archivo define el formato y los mapeos necesarios para la generación de et
### `devDependencies`
Estas son las bibliotecas necesarias solo durante el desarrollo y no en producción.
- **eslint: ^7.32.0**
- **Función**: Una herramienta para encontrar y arreglar problemas en el código JavaScript. Ayuda a mantener un código limpio y consistente.
- **Uso en tu proyecto**: Analizar el código y asegurar que sigue las convenciones de codificación especificadas.
- **eslint-plugin-import: ^2.29.1**
- **Función**: Un plugin para ESLint que ayuda a validar la importación/exportación de módulos ES6+.
- **Uso en tu proyecto**: Asegurar que las importaciones en tu proyecto sean válidas y que no haya problemas de módulos no encontrados.

View File

@ -1,18 +1,15 @@
const mysql = require('mysql2/promise');
const dotenv = require('dotenv');
dotenv.config();
const env = process.env;
const env = require('dotenv').config().parsed || process.env;
// Crea un pool de conexiones
const pool = mysql.createPool({
host: env.DB_HOST || 'localhost',
port: env.DB_PORT || 3306,
user: env.DB_USER || 'user',
password: env.DB_PASSWORD || 'password',
database: env.DB_NAME || 'database',
waitForConnections: true,
queueLimit: 0
host: env.DB_HOST || 'localhost',
port: env.DB_PORT || 3306,
user: env.DB_USER || 'user',
password: env.DB_PASSWORD || 'password',
database: env.DB_NAME || 'database',
waitForConnections: true,
queueLimit: 0
});
module.exports = pool;

View File

@ -1,35 +1,7 @@
version: '3'
version: '3.7'
services:
mariadb:
image: mariadb:latest
container_name: mariadb
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: database
MYSQL_USER: user
MYSQL_PASSWORD: password
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
backend:
main:
image: registry.verdnatura.es/rfidnatura:${VERSION:?}
build:
dockerfile: Dockerfile
container_name: backend
environment:
DB_HOST: mariadb
DB_USER: user
DB_PASSWORD: password
DB_NAME: database
ports:
- "3000:3000"
depends_on:
- mariadb
volumes:
db_data:
context: .
dockerfile: Dockerfile

23
log.js Normal file
View File

@ -0,0 +1,23 @@
const chalk = require('chalk');
const env = require('dotenv').config().parsed || process.env;
function log(realm, message) {
switch(realm) {
case 'success':
console.log(`[SUCCESS] ${chalk.green(message)}`);
break;
case 'error':
console.error(`[ERROR] ${chalk.red(message)}`);
break;
case 'info':
console.log(`[INFO] ${chalk.yellow(message)}`);
break;
case 'debug':
if (env.DEBUG) console.debug(`[DEBUG] ${chalk.blue(message)}`);
break;
default:
console.log(message);
}
}
module.exports = log;

View File

@ -1,10 +1,8 @@
const workerPool = require('./worker/workerPool');
const dotenv = require('dotenv');
dotenv.config();
const env = require('dotenv').config().parsed || process.env;
// Iniciar el pool de workers
const workers = new workerPool(process.env.WORKERS || 30);
const workers = new workerPool(env.WORKERS || 10);
// Asignar tareas iniciales a los workers
workers.start();

2952
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,20 @@
{
"name": "backend",
"name": "rfidnatura",
"version": "1.0.0",
"description": "Backend application",
"main": "main.js",
"scripts": {
"start": "node main.js",
"lint": "eslint ."
"author": "Verdnatura Levante SL",
"description": "Print server RFID",
"license": "GPL-3.0",
"repository": {
"type": "git",
"url": "https://gitea.verdnatura.es/verdnatura/rfidnatura"
},
"engines": {
"node": ">=18",
"npm": ">=8"
},
"keywords": [
"backend",
"mariadb"
],
"author": "Nombre del autor",
"license": "Licencia del proyecto (por ejemplo, MIT)",
"dependencies": {
"chalk": "^4.1.2",
"dotenv": "^16.4.5",
"log4js": "^6.9.1",
"mariadb": "^2.5.3",
"mysql2": "^3.10.3"
},
"devDependencies": {
"eslint": "^7.32.0",
"eslint-plugin-import": "^2.29.1"
}
}

View File

@ -1,46 +1,46 @@
module.exports = function generateZPL(record, label) {
const rf_id_verdnatura = 'AABB';
const id_verdnatura = String(record.expeditionFk).padStart(20, '0');
const rf_id_verdnatura = 'AABB';
const id_verdnatura = String(record.expeditionFk).padStart(20, '0');
// Convert text values of the record to uppercase
const upperCaseRecord = {};
for (let key in record) {
if (Object.prototype.hasOwnProperty.call(record, key)) {
if (typeof record[key] === 'string')
upperCaseRecord[key] = record[key].toUpperCase();
else
upperCaseRecord[key] = record[key];
}
}
// Convert text values of the record to uppercase
const upperCaseRecord = {};
for (let key in record) {
if (Object.prototype.hasOwnProperty.call(record, key)) {
if (typeof record[key] === 'string')
upperCaseRecord[key] = record[key].toUpperCase();
else
upperCaseRecord[key] = record[key];
}
}
const result = {};
const result = {};
for (let labelKey in label) {
if (Object.prototype.hasOwnProperty.call(label, labelKey)) {
const labelConfig = label[labelKey];
let zpl = labelConfig.zpl;
for (let labelKey in label) {
if (Object.prototype.hasOwnProperty.call(label, labelKey)) {
const labelConfig = label[labelKey];
let zpl = labelConfig.zpl;
for (let mappingKey in labelConfig.mappings) {
if (Object.prototype.hasOwnProperty.call(labelConfig.mappings, mappingKey)) {
const recordKey = labelConfig.mappings[mappingKey];
const recordValue = upperCaseRecord[recordKey];
const placeholder = `#${mappingKey}`;
if(placeholder == '#RFID_Code')
zpl = zpl.replace(new RegExp(placeholder, 'g'), rf_id_verdnatura + id_verdnatura);
else
zpl = zpl.replace(new RegExp(placeholder, 'g'), recordValue);
}
}
for (let mappingKey in labelConfig.mappings) {
if (Object.prototype.hasOwnProperty.call(labelConfig.mappings, mappingKey)) {
const recordKey = labelConfig.mappings[mappingKey];
const recordValue = upperCaseRecord[recordKey];
const placeholder = `#${mappingKey}`;
if(placeholder == '#RFID_Code')
zpl = zpl.replace(new RegExp(placeholder, 'g'), rf_id_verdnatura + id_verdnatura);
else
zpl = zpl.replace(new RegExp(placeholder, 'g'), recordValue);
}
}
result[labelKey] = {
"name": labelConfig.name,
"zpl": zpl,
"density": labelConfig.density,
"width": labelConfig.width,
"height": labelConfig.height
};
}
}
result[labelKey] = {
"name": labelConfig.name,
"zpl": zpl,
"density": labelConfig.density,
"width": labelConfig.width,
"height": labelConfig.height
};
}
}
return result;
return result;
};

View File

@ -2,202 +2,181 @@ const { parentPort } = require('worker_threads');
const fs = require('fs').promises;
const path = require('path');
const pool = require('../db/pool');
const log4js = require('log4js');
const dotenv = require('dotenv');
const env = require('dotenv').config().parsed || process.env;
const net = require('net');
const generateZPL = require('../resources/zplTemplate');
const label = require('../resources/label.json');
dotenv.config();
// Configuración de log4js
log4js.configure({
appenders: {
file: { type: 'file', filename: 'logs/app.log', maxLogSize: 10485760, backups: 3, compress: true },
console: { type: 'console' }
},
categories: {
default: { appenders: ['file', 'console'], level: 'info' }
}
});
const logger = log4js.getLogger('default');
const log = require('../log')
// Función para obtener una conexión con reintentos
async function getConnectionWithRetries(retries = 3, delay = 5000) {
let attempt = 0;
while (attempt < retries) {
try {
return await pool.getConnection();
} catch (error) {
attempt++;
if (attempt >= retries)
throw new Error('No se pudo obtener conexión después de múltiples intentos.');
async function getConn(retries = 3, delay = 5000) {
let attempt = 0;
while (attempt < retries) {
try {
return await pool.getConnection();
} catch (error) {
attempt++;
if (attempt >= retries)
throw new Error('No se pudo obtener conexión después de múltiples intentos.');
await new Promise(resolve => setTimeout(resolve, delay));
}
}
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
//Función para enviar ZPL a la impresora con TCP socket y reintentos
async function sendZPL(zplContent, ipAddress, retries = 2) {
const port = 9100;
try {
const result = await new Promise((resolve, reject) => {
const client = new net.Socket();
client.setTimeout(1000);
const port = 9100;
try {
const result = await new Promise((resolve, reject) => {
const client = new net.Socket();
client.setTimeout(1000);
client.connect(port, ipAddress, () => {
client.write(zplContent, () => {
client.destroy();
resolve('success');
});
});
client.connect(port, ipAddress, () => {
if (!env.DRY_PRINT)
client.write(zplContent, () => {
client.destroy();
resolve('success');
});
else {
client.destroy();
resolve('success');
}
});
client.on('timeout', () => {
logger.error('Tiempo de espera agotado al conectar con la impresora');
client.destroy();
reject('error');
});
client.on('timeout', () => {
log('error', 'Tiempo de espera agotado al conectar con la impresora');
client.destroy();
reject('error');
});
client.on('error', error => {
logger.error(`Error al enviar ZPL a la impresora: ${error.message || error}`);
client.on('error', error => {
log('error', `Error al enviar ZPL a la impresora: ${error.message || error}`);
client.destroy();
reject('error');
});
});
client.destroy();
reject('error');
});
});
if (result === 'success') return 'success';
} catch (error) {
if (error !== 'error') logger.error(`Error inesperado al enviar ZPL a la impresora: ${error.message || error}`);
if (retries > 0) {
logger.info(`Reintentando... Quedan ${retries} intentos.`);
return sendZPL(zplContent, ipAddress, retries - 1);
} else {
logger.error('Todos los intentos fallaron. No se pudo enviar el ZPL.');
return 'error';
}
}
if (result === 'success') return true;
} catch (error) {
if (error !== 'error')
log('error', `Error inesperado al enviar ZPL a la impresora: ${error.message || error}`);
if (retries > 0) {
log('debug', `Reintentando... Quedan ${retries} intentos.`);
return sendZPL(zplContent, ipAddress, retries - 1);
} else {
log('error', 'Todos los intentos fallaron. No se pudo enviar el ZPL.');
return 'error';
}
}
}
// Función para obtener la dirección IP de la impresora, realizando una llamada a la base de datos a la tabla de printer
async function getPrinterIpAddress(printerFk, connection) {
try {
const [rows] = await connection.query(`
SELECT ipAddress
FROM ${process.env.DB_PRINTER_SCHEMA}.printer
WHERE id = ?
`, [printerFk]);
if (!rows.length)
throw new Error(`No se encontró la impresora con id = ${printerFk}`);
return rows[0].ipAddress;
} catch (error) {
logger.error('Error al obtener la dirección IP de la impresora:', error);
throw error;
}
async function getPrinterIp(printerFk, conn) {
try {
const [rows] = await conn.query(`
SELECT ipAddress
FROM ${env.DB_PRINTER_SCHEMA}.printer
WHERE id = ?
`, [printerFk]);
if (!rows.length)
throw new Error(`No se encontró la impresora con id = ${printerFk}`);
return rows[0].ipAddress;
} catch (error) {
log('error', 'Error al obtener la dirección IP de la impresora:', error);
throw error;
}
}
// Función para obtener un único registro para procesar
async function getRecordForProcessing(retries = 5, delay = 4000) {
for (let attempt = 0; attempt < retries; attempt++) {
const conn = await getConnectionWithRetries();
try {
await conn.beginTransaction();
const [rows] = await conn.query(`
SELECT * FROM expedition_PrintOut
WHERE isPrinted = 10
LIMIT 1 FOR UPDATE
`);
if (!rows.length) {
await conn.commit();
return;
}
const record = rows[0];
await conn.query(`
UPDATE expedition_PrintOut
SET isPrinted = 12
WHERE expeditionFk = ?
`, [record.expeditionFk]);
await conn.commit();
return record;
} catch (error) {
await conn.rollback();
if (error.code === 'ER_LOCK_WAIT_TIMEOUT') {
logger.error('Lock wait timeout exceeded, retrying...');
if (attempt >= retries - 1)
throw new Error('No se pudo obtener el registro después de múltiples intentos.');
await new Promise(resolve => setTimeout(resolve, delay));
} else {
logger.error('Error al obtener y marcar el registro para procesamiento:', error);
return;
}
} finally {
conn.release();
}
}
async function getRecord(retries = 5, delay = 4000) {
for (let attempt = 0; attempt < retries; attempt++) {
const conn = await getConn();
try {
await conn.beginTransaction();
const [rows] = await conn.query(`
SELECT * FROM expedition_PrintOut
WHERE isPrinted = 10
LIMIT 1 FOR UPDATE
`);
if (!rows.length) {
await conn.commit();
return;
}
const record = rows[0];
await updateState(conn, record.expeditionFk, 12)
await conn.commit();
return record;
} catch (error) {
await conn.rollback();
if (error.code === 'ER_LOCK_WAIT_TIMEOUT') {
log('error', 'Lock wait timeout exceeded, retrying...');
if (attempt >= retries - 1)
throw new Error('No se pudo obtener el registro después de múltiples intentos.');
await new Promise(resolve => setTimeout(resolve, delay));
} else {
log('error', 'Error al obtener y marcar el registro para procesamiento:', error);
return;
}
} finally {
conn.release();
}
}
}
// Función para procesar un único registro
async function processRecord(record) {
const connection = await getConnectionWithRetries();
const conn = await getConn();
try {
await connection.beginTransaction();
try {
await conn.beginTransaction();
log('debug', `(${record.expeditionFk}) Procesando...`);
const zplData = generateZPL(record, label);
logger.info(`(${record.expeditionFk}) Procesando...`);
const zplData = generateZPL(record, label);
if (env.KEEP_TMP_FILES) {
const dirPath = path.join(__dirname, '..', 'tmp');
await fs.mkdir(dirPath, { recursive: true });
const filePath = path.join(dirPath, `zplData_${record.expeditionFk}.txt`);
await fs.writeFile(filePath, zplData['VerdNatura Label RFID'].zpl, 'utf8');
}
const filePath = path.join(__dirname, `zplData_${record.expeditionFk}.txt`);
const ipAddress = await getPrinterIp(record.printerFk, conn);
const isSendResult = await sendZPL(zplData['VerdNatura Label RFID'].zpl, ipAddress);
await fs.writeFile(filePath, zplData["VerdNatura Label RFID"].zpl, 'utf8');
if (isSendResult) {
await updateState(conn, record.expeditionFk, 11)
log('success', `(${record.expeditionFk}) Impresión realizada correctamente`);
} else {
await updateState(conn, record.expeditionFk, 13)
log('error', `(${record.expeditionFk}) Error en la impresión`);
}
parentPort.postMessage('done');
await conn.commit();
} catch (error) {
log('error', `Error al procesar el registro ${error}`);
await updateState(conn, record.expeditionFk, 13)
parentPort.postMessage('error');
await conn.rollback();
} finally {
conn.release();
}
}
const zplContent = await fs.readFile(filePath, 'utf8');
const ipAddress = await getPrinterIpAddress(record.printerFk, connection);
const sendResult = await sendZPL(zplContent, ipAddress);
fs.unlink(filePath);
if (sendResult === 'success') {
await connection.query(`
UPDATE expedition_PrintOut
SET isPrinted = 11
WHERE expeditionFk = ?
`, [record.expeditionFk]);
logger.info(`(${record.expeditionFk}) Base de datos actualizada`);
} else {
await connection.query(`
UPDATE expedition_PrintOut
SET isPrinted = 13
WHERE expeditionFk = ?
`, [record.expeditionFk]);
logger.error(`(${record.expeditionFk}) Error al enviar ZPL a la impresora`);
}
parentPort.postMessage('done');
await connection.commit();
} catch (error) {
logger.error('Error al procesar el registro:', error);
parentPort.postMessage('error');
// if (!error.message === `Can't add new command when connection is in closed state`)
await connection.rollback();
} finally {
connection.release();
}
async function updateState(conn, expeditionId, state) {
await conn.query(`
UPDATE expedition_PrintOut
SET isPrinted = ?
WHERE expeditionFk = ?
`, [state, expeditionId]);
}
// Escuchar mensajes del hilo principal
parentPort.on('message', async msg => {
if (msg === 'check') {
const record = await getRecordForProcessing();
if (record)
await processRecord(record);
else {
// Si no hay registros, espera y vuelve a verificar
setTimeout(async() => {
parentPort.postMessage('done');
}, 5000);
}
} else
processRecord(msg).catch(err => logger.error('Error en el worker:', err));
if (msg === 'check') {
const record = await getRecord();
if (record)
await processRecord(record);
else {
setTimeout(async() => {
parentPort.postMessage('done');
}, 5000);
}
} else
processRecord(msg).catch(err => log('error', 'Error en el worker:', err));
});

View File

@ -1,76 +1,72 @@
const { Worker } = require('worker_threads');
const log4js = require('log4js');
// Configuración de log4js
log4js.configure({
appenders: {
out: { type: 'stdout' },
app: { type: 'file', filename: 'app.log' }
},
categories: { default: { appenders: ['out', 'app'], level: 'debug' } }
});
const logger = log4js.getLogger();
const chalk = require('chalk');
const log = require('../log')
const env = require('dotenv').config().parsed || process.env;
// Clase para gestionar workers
class WorkerPool {
constructor(numWorkers) {
this.numWorkers = numWorkers;
this.workers = [];
this.initWorkers();
}
constructor(numWorkers) {
this.workers = [];
this.initWorkers(numWorkers);
}
// Inicializar workers
initWorkers() {
for (let i = 0; i < this.numWorkers; i++)
this.createWorker();
}
// Inicializar workers
initWorkers(number) {
for (let i = 0; i < number; i++) this.createWorker();
}
// Crear un nuevo worker y manejar sus mensajes
createWorker() {
const worker = new Worker('./worker/worker.js');
worker.on('message', async msg => {
if (msg === 'done' || msg === 'error')
worker.postMessage('check'); // Pedir al worker que verifique nuevos registros
});
worker.on('error', error => {
logger.error('Error en el worker:', error);
// Si un worker falla, lo eliminamos del array y creamos uno nuevo
this.replaceWorker(worker);
});
worker.on('exit', code => {
if (code !== 0) {
logger.error(`Worker stopped with exit code ${code}`);
this.replaceWorker(worker);
}
});
this.workers.push(worker);
}
// Crear un nuevo worker y manejar sus mensajes
createWorker() {
const worker = new Worker('./worker/worker.js');
worker.on('message', async msg => {
if (msg === 'done' || msg === 'error')
worker.postMessage('check');
});
worker.on('error', error => {
log('error', 'Error en el worker:', error);
this.replaceWorker(worker);
});
worker.on('exit', code => {
if (code !== 0) {
log('error', `Worker stopped with exit code ${code}`);
setTimeout(() => {
this.replaceWorker(worker);
}, 1000);
} else {
this.workers = this.workers.filter(worker => worker !== worker);
}
});
this.workers.push(worker);
}
// Reemplazar un worker fallido
replaceWorker(failedWorker) {
this.workers = this.workers.filter(worker => worker !== failedWorker);
this.createWorker();
}
// Reemplazar un worker fallido
replaceWorker(failedWorker) {
this.workers = this.workers.filter(worker => worker !== failedWorker);
log('debug', 'Replacing a worker...');
this.createWorker();
}
// Asignar tareas iniciales a los workers
async start() {
const decoration = '△▽'.repeat(10);
console.log(`${decoration} Dismuntel service ${decoration}`);
for (const worker of this.workers)
worker.postMessage('check'); // Pedir al worker que verifique nuevos registros
}
// Asignar tareas iniciales a los workers
async start() {
const decoration = '△▽'.repeat(10);
const rfidnatura = chalk.white.bold('Rfid') + chalk.green.bold('Natura')
log(null, `${decoration} ${rfidnatura} ${decoration}`);
if (env.DRY_PRINT)
log('info', 'Dry print mode')
for (const worker of this.workers)
worker.postMessage('check'); // Pedir al worker que verifique nuevos registros
}
// Cerrar todos los workers
end() {
try {
for (const worker of this.workers) worker.terminate();
console.log('\nBye ( ◕ ‿ ◕ )っ');
} catch (err) {
console.error(err);
}
process.exit();
}
// Cerrar todos los workers
end() {
try {
for (const worker of this.workers) worker.terminate();
log(null, '\nBye ( ◕ ‿ ◕ )っ')
} catch (err) {
log('error', err)
}
process.exit();
}
}
module.exports = WorkerPool;