feat: refs #4452 vn changes
This commit is contained in:
parent
d28fc42570
commit
afcb079407
|
@ -1,3 +0,0 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
logs
|
|
@ -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
|
|
@ -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
|
||||
|
|
35
Dockerfile
35
Dockerfile
|
@ -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"]
|
||||
|
|
26
README.md
26
README.md
|
@ -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.
|
||||
|
|
19
db/pool.js
19
db/pool.js
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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;
|
6
main.js
6
main.js
|
@ -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();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
317
worker/worker.js
317
worker/worker.js
|
@ -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));
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue