admin_to_react
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
// ============================================
|
||||
// 📦 Модуль подключения к MySQL базе данных
|
||||
// ============================================
|
||||
|
||||
const mysql = require('mysql2/promise'); // ✅ Используем promise-версию
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const { logger } = require('./logger');
|
||||
|
||||
// Загружаем переменные окружения
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../config.env') });
|
||||
|
||||
// ============================================
|
||||
// 🔧 Конфигурация подключения к MySQL
|
||||
// ============================================
|
||||
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_NAME || 'test',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
// Дополнительные настройки
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+00:00'
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 📊 Глобальные переменные
|
||||
// ============================================
|
||||
|
||||
let pool = null; // Пул соединений
|
||||
let isDBConnected = false;
|
||||
let lastError = null;
|
||||
let connectionAttempts = 0;
|
||||
let isReconnecting = false;
|
||||
|
||||
// ✅ Функция для проверки testMode из config.json
|
||||
async function checkTestMode() {
|
||||
try {
|
||||
const configPath = path.join(__dirname, '../../config.json');
|
||||
const config = await fs.readJson(configPath);
|
||||
return config.testMode === true;
|
||||
} catch (err) {
|
||||
logger.warn('⚠️ Не удалось прочитать config.json для проверки testMode', { error: err.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🔄 Функция подключения с повторными попытками
|
||||
// ============================================
|
||||
|
||||
async function connectDB(retries = 5, delay = 3000) {
|
||||
// ✅ Проверяем testMode
|
||||
const testMode = await checkTestMode();
|
||||
|
||||
// 🔧 Если включен тестовый режим - пропускаем подключение
|
||||
if (testMode) {
|
||||
logger.info('🔧 ТЕСТОВЫЙ РЕЖИМ: Подключение к БД отключено');
|
||||
logger.info('📊 Используются mock-данные из config.json');
|
||||
isDBConnected = false;
|
||||
lastError = 'Test mode active - database connection disabled';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Если уже подключены
|
||||
if (isDBConnected && pool) {
|
||||
logger.verbose('✅ БД уже подключена');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Если идет переподключение
|
||||
if (isReconnecting) {
|
||||
logger.warn('⏳ Идет переподключение к БД, ожидаем...');
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info(`🔄 Подключение к MySQL... (попытка 1/${retries})`);
|
||||
logger.info(`📊 Хост: ${dbConfig.host}, БД: ${dbConfig.database}, Порт: ${dbConfig.port}`);
|
||||
|
||||
// Цикл повторных попыток
|
||||
for (let i = 0; i < retries; i++) {
|
||||
try {
|
||||
connectionAttempts++;
|
||||
|
||||
// Создаем пул соединений
|
||||
pool = mysql.createPool(dbConfig);
|
||||
|
||||
// Проверяем подключение
|
||||
const connection = await pool.getConnection();
|
||||
await connection.ping();
|
||||
connection.release();
|
||||
|
||||
// ✅ Успешно подключились
|
||||
isDBConnected = true;
|
||||
lastError = null;
|
||||
isReconnecting = false;
|
||||
|
||||
logger.info('✅ Успешно подключено к MySQL');
|
||||
logger.info(`📊 Пул соединений: макс. ${dbConfig.connectionLimit}`);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (err) {
|
||||
lastError = err.message;
|
||||
isDBConnected = false;
|
||||
pool = null;
|
||||
|
||||
logger.warn(`❌ Попытка ${i + 1}/${retries} не удалась: ${err.message}`);
|
||||
|
||||
if (i < retries - 1) {
|
||||
logger.info(`⏳ Повторная попытка через ${delay/1000} секунд...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
} else {
|
||||
logger.error('❌ Все попытки подключения к БД исчерпаны');
|
||||
startBackgroundReconnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🔄 Фоновое переподключение
|
||||
// ============================================
|
||||
|
||||
async function reconnectToDatabase() {
|
||||
if (isReconnecting) {
|
||||
logger.warn('⏳ Переподключение уже запущено');
|
||||
return;
|
||||
}
|
||||
|
||||
isReconnecting = true;
|
||||
logger.info('🔄 Запущено фоновое переподключение к БД...');
|
||||
|
||||
const success = await connectDB(10, 5000);
|
||||
|
||||
if (success) {
|
||||
logger.info('✅ Переподключение к БД успешно выполнено');
|
||||
} else {
|
||||
logger.error('❌ Переподключение к БД не удалось');
|
||||
setTimeout(() => {
|
||||
logger.info('🔄 Запускаем повторную попытку переподключения...');
|
||||
reconnectToDatabase();
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
isReconnecting = false;
|
||||
}
|
||||
|
||||
function startBackgroundReconnection() {
|
||||
if (!isReconnecting) {
|
||||
logger.info('🔄 Запускаем фоновое переподключение...');
|
||||
reconnectToDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 📊 Функция выполнения запросов
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Выполняет SQL-запрос с параметрами
|
||||
* @param {string} sql - SQL-запрос с плейсхолдерами ?
|
||||
* @param {Array} params - Параметры для подстановки
|
||||
* @returns {Promise<Array>} - Результат запроса
|
||||
*/
|
||||
async function query(sql, params = []) {
|
||||
if (!isDBConnected || !pool) {
|
||||
throw new Error('База данных не подключена');
|
||||
}
|
||||
|
||||
try {
|
||||
const [rows] = await pool.execute(sql, params);
|
||||
return rows;
|
||||
} catch (err) {
|
||||
logger.error('❌ Ошибка выполнения запроса', { sql, error: err.message });
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет SQL-запрос и возвращает первый результат
|
||||
*/
|
||||
async function queryOne(sql, params = []) {
|
||||
const rows = await query(sql, params);
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 📊 Функции для получения статуса БД
|
||||
// ============================================
|
||||
|
||||
async function getDBStatus() {
|
||||
const testMode = await checkTestMode();
|
||||
|
||||
return {
|
||||
connected: isDBConnected,
|
||||
lastError: lastError,
|
||||
connectionAttempts: connectionAttempts,
|
||||
isReconnecting: isReconnecting,
|
||||
testMode: testMode,
|
||||
dbType: 'MySQL',
|
||||
host: dbConfig.host,
|
||||
database: dbConfig.database
|
||||
};
|
||||
}
|
||||
|
||||
function isDBConnectedFn() {
|
||||
return isDBConnected;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🧹 Закрытие соединения
|
||||
// ============================================
|
||||
|
||||
async function closeDB() {
|
||||
try {
|
||||
if (pool) {
|
||||
await pool.end();
|
||||
pool = null;
|
||||
isDBConnected = false;
|
||||
logger.info('🔒 Соединение с MySQL закрыто');
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('❌ Ошибка при закрытии соединения с БД', { error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 📤 Экспорт модуля
|
||||
// ============================================
|
||||
|
||||
module.exports = {
|
||||
pool, // Пул соединений
|
||||
query, // Выполнение запросов
|
||||
queryOne, // Выполнение запроса с одним результатом
|
||||
connectDB, // Функция подключения
|
||||
getDBStatus, // Получение статуса
|
||||
isDBConnected: isDBConnectedFn,
|
||||
closeDB,
|
||||
reconnectToDatabase
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
const winston = require('winston');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const zlib = require('zlib');
|
||||
const cron = require('node-cron');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../config.env') });
|
||||
const logDir = path.join(__dirname, process.env.LOG_DIR);
|
||||
const logFile = path.join(logDir, process.env.LOG_FILE);
|
||||
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({
|
||||
format: 'HH:mm:ss YYYY-MM-DD '
|
||||
}),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.splat(),
|
||||
winston.format.json()
|
||||
),
|
||||
defaultMeta: { service: 'user-service' },
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: logFile,
|
||||
maxsize: 10 * 1024 * 1024, // 5MB
|
||||
}),
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const archiveLog = () => {
|
||||
if (!fs.existsSync(logFile)) return;
|
||||
|
||||
const archiveName = `app-${new Date().toISOString().slice(0, 10).replace(/-/g, '_')}.log.gz`;
|
||||
const archivePath = path.join(logDir, archiveName);
|
||||
|
||||
fs.createReadStream(logFile)
|
||||
.pipe(zlib.createGzip())
|
||||
.pipe(fs.createWriteStream(archivePath))
|
||||
.on('finish', () => {
|
||||
fs.writeFileSync(logFile, '');
|
||||
logger.info(`Логи архивированы в ${archiveName}`);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
logger.error('Ошибка архивации:', err);
|
||||
});
|
||||
};
|
||||
|
||||
cron.schedule('* * 1 * *', archiveLog);
|
||||
|
||||
module.exports = {
|
||||
logger,
|
||||
archiveLog
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
const winston = require('winston');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const zlib = require('zlib');
|
||||
const cron = require('node-cron');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../config.env') });
|
||||
|
||||
const logDir = path.join(__dirname, process.env.LOG_DIR_CARDS);
|
||||
const logFile = path.join(logDir, process.env.LOG_FILE_CARDS);
|
||||
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
|
||||
//логгируем cards
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.splat(),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: logFile,
|
||||
maxsize: 10 * 1024 * 1024,
|
||||
}),
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const archiveLog = () => {
|
||||
if (!fs.existsSync(logFile)) return;
|
||||
|
||||
const archiveName = `cards-${new Date().toISOString().slice(0, 10).replace(/-/g, '_')}.log.gz`
|
||||
const archivePath = path.join(logDir, archiveName);
|
||||
|
||||
fs.createReadStream(logFile)
|
||||
.pipe(zlib.createGzip())
|
||||
.pipe(fs.createWriteStream(archivePath))
|
||||
.on('finish', () => {
|
||||
fs.writeFileSync(logFile, '');
|
||||
logger.info(`Логи архивированы в ${archiveName}`);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
logger.error('Ошибка архивации:', err);
|
||||
});
|
||||
};
|
||||
|
||||
cron.schedule('* * 1 * *', archiveLog);
|
||||
module.exports = {
|
||||
archiveLog,
|
||||
logger
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { archiveLog } = require('./logger');
|
||||
try {
|
||||
archiveLog();
|
||||
console.log('Архивация запущена');
|
||||
} catch (err) {
|
||||
console.error('Ошибка архивации:', err.message);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { archiveLog } = require('./logger_cards');
|
||||
try {
|
||||
archiveLog();
|
||||
console.log('Архивация запущена');
|
||||
} catch (err) {
|
||||
console.error('Ошибка архивации:', err.message);
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// ============================================
|
||||
// 📦 Маршруты для работы с сотрудниками (MySQL)
|
||||
// ============================================
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const { query, queryOne, isDBConnected, getDBStatus } = require('../config/db');
|
||||
const { logger } = require('../config/logger_cards');
|
||||
|
||||
// ============================================
|
||||
// 📊 Вспомогательные функции
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Получение mock-данных сотрудника из config.json
|
||||
*/
|
||||
async function getMockEmployee(searchParam, type = 'card') {
|
||||
try {
|
||||
const configPath = path.join(__dirname, '../../config.json');
|
||||
const config = await fs.readJson(configPath);
|
||||
|
||||
if (!config.testMode || !config.mockData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mockEmployees = config.mockData.employees || [];
|
||||
|
||||
if (type === 'card') {
|
||||
return mockEmployees.find(e => String(e.cardNumber) === String(searchParam)) || null;
|
||||
} else if (type === 'id') {
|
||||
return mockEmployees.find(e => e.id === parseInt(searchParam)) || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
logger.error('[MOCK ERROR]', { error: err.message });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирование данных сотрудника
|
||||
*/
|
||||
function formatEmployeeResponse(employee) {
|
||||
return {
|
||||
...employee,
|
||||
category: employee.category || null,
|
||||
departmentName: employee.departmentName || null,
|
||||
departmentCode: employee.departmentCode || null
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🔍 Поиск по номеру карты
|
||||
// ============================================
|
||||
|
||||
router.get('/card/:cardNumber', async (req, res) => {
|
||||
const startTime = Date.now();
|
||||
const { cardNumber } = req.params;
|
||||
|
||||
logger.info(`[SEARCH] Поиск сотрудника по номеру карты: ${cardNumber}`);
|
||||
|
||||
try {
|
||||
const dbStatus = await getDBStatus();
|
||||
|
||||
// 🔧 Если БД не доступна или включен тестовый режим
|
||||
if (!dbStatus.connected || dbStatus.testMode) {
|
||||
logger.warn(`[SEARCH] БД недоступна или testMode=true, пробуем mock-данные`);
|
||||
|
||||
const mockEmployee = await getMockEmployee(cardNumber, 'card');
|
||||
|
||||
if (mockEmployee) {
|
||||
logger.info(`[MOCK] Найден тестовый сотрудник: ${mockEmployee.famaly} ${mockEmployee.ima}`);
|
||||
return res.json({
|
||||
...formatEmployeeResponse(mockEmployee),
|
||||
_mock: true,
|
||||
_message: 'Тестовые данные (режим без БД)'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(404).json({
|
||||
message: 'Сотрудник не найден (тестовый режим)',
|
||||
cardNumber: cardNumber,
|
||||
dbStatus: dbStatus
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ БД доступна - выполняем MySQL запрос
|
||||
const sqlQuery = `
|
||||
SELECT
|
||||
mk570.id,
|
||||
mk570.cardNumber,
|
||||
mk570.tnom,
|
||||
mk501.famaly,
|
||||
mk501.ima,
|
||||
mk501.otch,
|
||||
mk503.nameprof AS profession,
|
||||
my501.pm AS category,
|
||||
my401.name AS departmentName,
|
||||
mk503.cex AS departmentCode,
|
||||
mk570.organization,
|
||||
mk570.identNumber
|
||||
FROM mk570
|
||||
JOIN mk501 ON mk570.tnom = mk501.tnom
|
||||
LEFT JOIN mk503 ON mk501.kod_sht = mk503.kod_sht AND mk501.tnom = mk503.tnom
|
||||
LEFT JOIN my501 ON mk503.id_my501 = my501.id
|
||||
LEFT JOIN my401 ON mk501.cex = my401.cex
|
||||
WHERE mk570.cardNumber = ?
|
||||
`;
|
||||
|
||||
const results = await query(sqlQuery, [cardNumber]);
|
||||
|
||||
if (results.length === 0) {
|
||||
logger.warn(`[SEARCH] Сотрудник с картой ${cardNumber} не найден в БД`);
|
||||
|
||||
const mockEmployee = await getMockEmployee(cardNumber, 'card');
|
||||
if (mockEmployee) {
|
||||
logger.info(`[MOCK FALLBACK] Используем mock-данные`);
|
||||
return res.json({
|
||||
...formatEmployeeResponse(mockEmployee),
|
||||
_mock: true,
|
||||
_message: 'Данные из тестового режима (сотрудник не найден в БД)'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(404).json({
|
||||
message: 'Сотрудник не найден',
|
||||
cardNumber: cardNumber
|
||||
});
|
||||
}
|
||||
|
||||
const result = results[0];
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
logger.info(`[SQL] Найден сотрудник: ${result.famaly} ${result.ima} (ID: ${result.id})`);
|
||||
logger.info(`[SQL] Время: ${responseTime}мс`);
|
||||
|
||||
res.json(formatEmployeeResponse(result));
|
||||
|
||||
} catch (err) {
|
||||
logger.error('[SQL ERROR]', { error: err.message });
|
||||
|
||||
const mockEmployee = await getMockEmployee(cardNumber, 'card');
|
||||
if (mockEmployee) {
|
||||
logger.info(`[MOCK FALLBACK] Используем mock-данные из-за ошибки`);
|
||||
return res.json({
|
||||
...formatEmployeeResponse(mockEmployee),
|
||||
_mock: true,
|
||||
_message: 'Тестовые данные (ошибка БД)'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
message: 'Ошибка сервера',
|
||||
error: err.message,
|
||||
dbStatus: await getDBStatus()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 🔍 Поиск по ID
|
||||
// ============================================
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
const startTime = Date.now();
|
||||
const { id } = req.params;
|
||||
|
||||
logger.info(`[SEARCH] Поиск сотрудника по ID: ${id}`);
|
||||
|
||||
try {
|
||||
const dbStatus = await getDBStatus();
|
||||
|
||||
if (!dbStatus.connected || dbStatus.testMode) {
|
||||
const mockEmployee = await getMockEmployee(id, 'id');
|
||||
if (mockEmployee) {
|
||||
logger.info(`[MOCK] Найден тестовый сотрудник по ID`);
|
||||
return res.json({
|
||||
...formatEmployeeResponse(mockEmployee),
|
||||
_mock: true,
|
||||
_message: 'Тестовые данные (режим без БД)'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(404).json({
|
||||
message: 'Сотрудник не найден (тестовый режим)',
|
||||
id: id,
|
||||
dbStatus: dbStatus
|
||||
});
|
||||
}
|
||||
|
||||
const sqlQuery = `
|
||||
SELECT
|
||||
mk570.id,
|
||||
mk570.cardNumber,
|
||||
mk570.tnom,
|
||||
mk501.famaly,
|
||||
mk501.ima,
|
||||
mk501.otch,
|
||||
mk503.nameprof AS profession,
|
||||
my501.pm AS category,
|
||||
my401.name AS departmentName,
|
||||
mk503.cex AS departmentCode,
|
||||
mk570.organization,
|
||||
mk570.identNumber
|
||||
FROM mk570
|
||||
JOIN mk501 ON mk570.tnom = mk501.tnom
|
||||
LEFT JOIN mk503 ON mk501.kod_sht = mk503.kod_sht AND mk501.tnom = mk503.tnom
|
||||
LEFT JOIN my501 ON mk503.id_my501 = my501.id
|
||||
LEFT JOIN my401 ON mk501.cex = my401.cex
|
||||
WHERE mk570.id = ?
|
||||
`;
|
||||
|
||||
const results = await query(sqlQuery, [id]);
|
||||
|
||||
if (results.length === 0) {
|
||||
logger.warn(`[SEARCH] Сотрудник с ID ${id} не найден`);
|
||||
|
||||
const mockEmployee = await getMockEmployee(id, 'id');
|
||||
if (mockEmployee) {
|
||||
logger.info(`[MOCK FALLBACK] Используем mock-данные`);
|
||||
return res.json({
|
||||
...formatEmployeeResponse(mockEmployee),
|
||||
_mock: true,
|
||||
_message: 'Данные из тестового режима'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(404).json({
|
||||
message: 'Сотрудник не найден',
|
||||
id: id
|
||||
});
|
||||
}
|
||||
|
||||
const result = results[0];
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
logger.info(`[SQL] Найден сотрудник: ${result.famaly} ${result.ima} (ID: ${result.id})`);
|
||||
logger.info(`[SQL] Время: ${responseTime}мс`);
|
||||
|
||||
res.json(formatEmployeeResponse(result));
|
||||
|
||||
} catch (err) {
|
||||
logger.error('[SQL ERROR]', { error: err.message });
|
||||
|
||||
const mockEmployee = await getMockEmployee(id, 'id');
|
||||
if (mockEmployee) {
|
||||
logger.info(`[MOCK FALLBACK] Используем mock-данные из-за ошибки`);
|
||||
return res.json({
|
||||
...formatEmployeeResponse(mockEmployee),
|
||||
_mock: true,
|
||||
_message: 'Тестовые данные (ошибка БД)'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
message: 'Ошибка сервера',
|
||||
error: err.message,
|
||||
dbStatus: await getDBStatus()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 📊 Статус сервиса
|
||||
// ============================================
|
||||
|
||||
router.get('/status', async (req, res) => {
|
||||
const dbStatus = await getDBStatus();
|
||||
|
||||
res.json({
|
||||
service: 'employee-search',
|
||||
database: dbStatus,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,37 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const mammoth = require('mammoth');
|
||||
const { logger } = require('../config/logger');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../config.env') });
|
||||
router.get('/test', (req, res) => {
|
||||
res.send('Handbook test route works!');
|
||||
});
|
||||
router.get('/html', async (req, res) => {
|
||||
const docxPath = path.join(__dirname, process.env.DOCX_PATH);
|
||||
logger.info('Checking document', {
|
||||
path: docxPath,
|
||||
exists: fs.existsSync(docxPath)
|
||||
});
|
||||
|
||||
if (!fs.existsSync(docxPath)) {
|
||||
logger.error('Document not found', { path: docxPath });
|
||||
return res.status(404).send('Документ не найден');
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await mammoth.convertToHtml({ path: docxPath });
|
||||
logger.info('Document converted successfully', { path: docxPath });
|
||||
res.send(result.value);
|
||||
} catch (err) {
|
||||
logger.error('Conversion failed', {
|
||||
path: docxPath,
|
||||
error: err.message,
|
||||
stack: err.stack
|
||||
});
|
||||
res.status(500).send('Ошибка при конвертации документа');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,329 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const { connectDB } = require('./config/db');
|
||||
const { logger } = require('./config/logger');
|
||||
const handbookRoutes = require('./routes/handbook');
|
||||
const employeeRoutes = require('./routes/employee');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../config.env') });
|
||||
|
||||
async function connectToNetworkShare(config) {
|
||||
if (config.networkCredentials && config.networkCredentials.username) {
|
||||
try {
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const execAsync = promisify(exec);
|
||||
const payslipsPath = config.payslipsPath;
|
||||
const username = config.networkCredentials.username;
|
||||
const password = config.networkCredentials.password;
|
||||
const domain = config.networkCredentials.domain;
|
||||
const netUseCommand = `net use \\${payslipsPath} ${password} /user:${domain ? domain + '\\' : ''}${username}`;
|
||||
|
||||
logger.info(`Attempting to connect to network share with credentials`);
|
||||
const { stdout, stderr } = await execAsync(netUseCommand);
|
||||
|
||||
if (stderr) {
|
||||
logger.warn(`Network share connection warning: ${stderr}`);
|
||||
}
|
||||
|
||||
logger.info(`Network share connection result: ${stdout}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('Failed to connect to network share', { error: error.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🚀 Инициализация Express
|
||||
// ============================================
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// ============================================
|
||||
// 📍 Маршруты API
|
||||
// ============================================
|
||||
|
||||
app.use('/api/handbook', handbookRoutes);
|
||||
app.use('/api/employee', employeeRoutes);
|
||||
|
||||
// ============================================
|
||||
// 🧪 Тестовые эндпоинты
|
||||
// ============================================
|
||||
|
||||
app.get('/api/test', (req, res) => {
|
||||
res.json({ message: 'API работает' });
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 📊 Эндпоинт для получения статуса системы
|
||||
// ============================================
|
||||
|
||||
app.get('/api/system-status', async (req, res) => {
|
||||
try {
|
||||
const configPath = path.join(__dirname, '../config.json');
|
||||
const config = await fs.readJson(configPath);
|
||||
const { getDBStatus } = require('./config/db');
|
||||
const dbStatus = await getDBStatus();
|
||||
|
||||
let networkAccess = false;
|
||||
let networkError = null;
|
||||
let networkContents = [];
|
||||
|
||||
try {
|
||||
networkAccess = await fs.pathExists(config.payslipsPath);
|
||||
if (networkAccess) {
|
||||
const stats = await fs.stat(config.payslipsPath);
|
||||
networkAccess = stats.isDirectory();
|
||||
if (networkAccess) {
|
||||
networkContents = await fs.readdir(config.payslipsPath);
|
||||
if (networkContents.length > 10) {
|
||||
networkContents = networkContents.slice(0, 10);
|
||||
networkContents.push(`... и еще ${networkContents.length - 10} элементов`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
networkAccess = false;
|
||||
networkError = err.message;
|
||||
}
|
||||
|
||||
const serverInfo = {
|
||||
uptime: process.uptime(),
|
||||
memory: process.memoryUsage(),
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
cwd: process.cwd(),
|
||||
pid: process.pid
|
||||
};
|
||||
|
||||
res.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
server: serverInfo,
|
||||
database: dbStatus,
|
||||
networkShare: {
|
||||
path: config.payslipsPath,
|
||||
accessible: networkAccess,
|
||||
error: networkError,
|
||||
contents: networkContents,
|
||||
testMode: config.testMode || false
|
||||
},
|
||||
config: {
|
||||
testMode: config.testMode || false,
|
||||
serverHost: config.server?.host || 'localhost',
|
||||
serverPort: config.server?.port || '3001'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logger.error('Ошибка получения статуса', { error: err.message });
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Ошибка получения статуса системы',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 🌐 Тест сетевой папки
|
||||
// ============================================
|
||||
|
||||
app.get('/api/test-network', async (req, res) => {
|
||||
try {
|
||||
const configPath = path.join(__dirname, '../config.json');
|
||||
const config = await fs.readJson(configPath);
|
||||
const payslipBasePath = config.payslipsPath;
|
||||
|
||||
logger.info(`Testing network connection to: ${payslipBasePath}`);
|
||||
|
||||
const networkConnected = await connectToNetworkShare(config);
|
||||
logger.info(`Network share connection successful: ${networkConnected}`);
|
||||
|
||||
const testResults = {
|
||||
basePath: payslipBasePath,
|
||||
basePathExists: false,
|
||||
basePathAccessible: false,
|
||||
basePathContents: [],
|
||||
networkConnected: networkConnected,
|
||||
error: null
|
||||
};
|
||||
|
||||
try {
|
||||
testResults.basePathExists = await fs.pathExists(payslipBasePath);
|
||||
logger.info(`Base path exists: ${testResults.basePathExists}`);
|
||||
|
||||
if (testResults.basePathExists) {
|
||||
const stats = await fs.stat(payslipBasePath);
|
||||
testResults.basePathAccessible = stats.isDirectory();
|
||||
logger.info(`Base path is directory: ${testResults.basePathAccessible}`);
|
||||
|
||||
if (testResults.basePathAccessible) {
|
||||
testResults.basePathContents = await fs.readdir(payslipBasePath);
|
||||
logger.info(`Base path contents: ${JSON.stringify(testResults.basePathContents)}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
testResults.error = error.message;
|
||||
logger.error('Network test error', { error: error.message, stack: error.stack });
|
||||
}
|
||||
|
||||
res.json(testResults);
|
||||
} catch (err) {
|
||||
logger.error('Error in network test', { error: err.message, stack: err.stack });
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 📄 Получение расчетного листка (с периодом)
|
||||
// ============================================
|
||||
|
||||
app.get('/api/payslip/:period/:fileName', async (req, res) => {
|
||||
try {
|
||||
const { period, fileName } = req.params;
|
||||
const configPath = path.join(__dirname, '../config.json');
|
||||
const config = await fs.readJson(configPath);
|
||||
const payslipBasePath = config.payslipsPath;
|
||||
|
||||
logger.info(`Payslip request - Period: ${period}, File: ${fileName}`);
|
||||
logger.info(`Base path from config: ${payslipBasePath}`);
|
||||
|
||||
const networkConnected = await connectToNetworkShare(config);
|
||||
logger.info(`Network share connection for payslip: ${networkConnected}`);
|
||||
|
||||
let finalPayslipPath;
|
||||
if (path.isAbsolute(payslipBasePath)) {
|
||||
finalPayslipPath = path.join(payslipBasePath, period, fileName);
|
||||
} else {
|
||||
const projectRoot = path.resolve(__dirname, '../../');
|
||||
finalPayslipPath = path.join(projectRoot, payslipBasePath, period, fileName);
|
||||
}
|
||||
|
||||
logger.info(`Final payslip path: ${finalPayslipPath}`);
|
||||
|
||||
try {
|
||||
const basePathExists = await fs.pathExists(payslipBasePath);
|
||||
logger.info(`Base path exists: ${basePathExists}`);
|
||||
|
||||
if (basePathExists) {
|
||||
const basePathStats = await fs.stat(payslipBasePath);
|
||||
logger.info(`Base path stats: ${JSON.stringify({
|
||||
isDirectory: basePathStats.isDirectory(),
|
||||
permissions: basePathStats.mode,
|
||||
size: basePathStats.size
|
||||
})}`);
|
||||
|
||||
const basePathContents = await fs.readdir(payslipBasePath);
|
||||
logger.info(`Base path contents: ${JSON.stringify(basePathContents)}`);
|
||||
|
||||
const periodPath = path.join(payslipBasePath, period);
|
||||
const periodPathExists = await fs.pathExists(periodPath);
|
||||
logger.info(`Period path exists: ${periodPathExists} (${periodPath})`);
|
||||
|
||||
if (periodPathExists) {
|
||||
const periodPathContents = await fs.readdir(periodPath);
|
||||
logger.info(`Period path contents: ${JSON.stringify(periodPathContents)}`);
|
||||
}
|
||||
}
|
||||
} catch (basePathError) {
|
||||
logger.error('Error checking base path', {
|
||||
error: basePathError.message,
|
||||
stack: basePathError.stack
|
||||
});
|
||||
}
|
||||
|
||||
if (await fs.pathExists(finalPayslipPath)) {
|
||||
logger.info(`Payslip found, sending file: ${finalPayslipPath}`);
|
||||
res.sendFile(finalPayslipPath);
|
||||
} else {
|
||||
logger.warn(`Payslip not found: ${finalPayslipPath}`);
|
||||
res.status(404).json({
|
||||
message: 'Расчетный листок не найден',
|
||||
path: finalPayslipPath,
|
||||
period: period,
|
||||
fileName: fileName,
|
||||
basePath: payslipBasePath,
|
||||
networkConnected: networkConnected
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error getting payslip', { error: err.message, stack: err.stack });
|
||||
res.status(500).json({ message: 'Ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 📄 Получение расчетного листка (без периода)
|
||||
// ============================================
|
||||
|
||||
app.get('/api/payslip/:fileName', async (req, res) => {
|
||||
try {
|
||||
const { fileName } = req.params;
|
||||
const configPath = path.join(__dirname, '../config.json');
|
||||
const config = await fs.readJson(configPath);
|
||||
const payslipBasePath = config.payslipsPath;
|
||||
|
||||
let finalPayslipPath;
|
||||
if (path.isAbsolute(payslipBasePath)) {
|
||||
finalPayslipPath = path.join(payslipBasePath, fileName);
|
||||
} else {
|
||||
const projectRoot = path.resolve(__dirname, '../../');
|
||||
finalPayslipPath = path.join(projectRoot, payslipBasePath, fileName);
|
||||
}
|
||||
|
||||
logger.info(`Attempting to find payslip at resolved path: ${finalPayslipPath}`);
|
||||
|
||||
if (await fs.pathExists(finalPayslipPath)) {
|
||||
res.sendFile(finalPayslipPath);
|
||||
} else {
|
||||
logger.warn(`Payslip not found: ${finalPayslipPath}`);
|
||||
res.status(404).json({ message: 'Расчетный листок не найден' });
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error getting payslip', { error: err.message, stack: err.stack });
|
||||
res.status(500).json({ message: 'Ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
|
||||
// ============================================
|
||||
|
||||
app.get('/api/db-status', async (req, res) => {
|
||||
try {
|
||||
const { query } = require('./config/db');
|
||||
await query('SELECT 1');
|
||||
res.status(200).json({ status: 'ok', message: 'DB connection successful' });
|
||||
} catch (err) {
|
||||
logger.error('Database connection check failed', { error: err.message, stack: err.stack });
|
||||
res.status(500).json({ status: 'error', message: 'DB connection failed' });
|
||||
}
|
||||
});
|
||||
|
||||
async function startServer() {
|
||||
try {
|
||||
const configPath = path.join(__dirname, '../config.json');
|
||||
const config = await fs.readJson(configPath);
|
||||
const { host, port } = config.server;
|
||||
|
||||
await connectDB();
|
||||
app.listen(port, host, () => {
|
||||
logger.info(`Server is running on http://${host}:${port}`);
|
||||
});
|
||||
} catch (err) {
|
||||
if (logger) {
|
||||
logger.error('Failed to start server:', { error: err.message, stack: err.stack });
|
||||
} else {
|
||||
console.error('Failed to start server:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startServer();
|
||||
Reference in New Issue
Block a user