modified: main_dc/valitovgaziz/analytics/package.json

modified:   main_dc/valitovgaziz/analytics/server.js
change dependencies
This commit is contained in:
2025-11-11 03:32:56 +05:00
parent 5b10be0113
commit 32385eb549
2 changed files with 99 additions and 60 deletions
@@ -14,7 +14,6 @@
"helmet": "^7.0.0",
"morgan": "^1.10.0",
"compression": "^1.7.4",
"rate-limiter-flexible": "^3.0.8",
"winston": "^3.10.0"
},
"devDependencies": {
+99 -59
View File
@@ -3,19 +3,41 @@ const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const morgan = require('morgan');
const { RateLimiterMemory } = require('rate-limiter-flexible');
const fs = require('fs').promises;
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// Настройка лимитера запросов
const rateLimiter = new RateLimiterMemory({
keyGenerator: (req) => req.ip,
points: 100, // 100 запросов
duration: 60, // за 60 секунд
});
// Простой rate limiting
const requestCounts = new Map();
const RATE_LIMIT = 100; // 100 запросов
const RATE_LIMIT_WINDOW = 60000; // за 1 минуту
function rateLimit(req, res, next) {
const ip = req.ip;
const now = Date.now();
const windowStart = now - RATE_LIMIT_WINDOW;
if (!requestCounts.has(ip)) {
requestCounts.set(ip, []);
}
const requests = requestCounts.get(ip);
// Удаляем старые запросы
const recentRequests = requests.filter(time => time > windowStart);
if (recentRequests.length >= RATE_LIMIT) {
return res.status(429).json({
error: 'Too Many Requests',
retryAfter: Math.ceil((requests[0] + RATE_LIMIT_WINDOW - now) / 1000)
});
}
recentRequests.push(now);
requestCounts.set(ip, recentRequests);
next();
}
// Middleware
app.use(helmet({
@@ -25,50 +47,22 @@ app.use(compression());
app.use(cors());
app.use(express.json({ limit: '1mb' }));
// Логирование запросов
// Логирование запросов в файл
const accessLogStream = require('fs').createWriteStream(
path.join(__dirname, 'logs', 'access.log'),
{ flags: 'a' }
);
app.use(morgan('combined', { stream: accessLogStream }));
// Winston логгер для приложения
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({
filename: path.join(__dirname, 'logs', 'error.log'),
level: 'error'
}),
new winston.transports.File({
filename: path.join(__dirname, 'logs', 'combined.log')
}),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// Middleware для ограничения запросов
app.use(async (req, res, next) => {
try {
await rateLimiter.consume(req.ip);
next();
} catch (rejRes) {
res.status(429).json({
error: 'Too Many Requests',
retryAfter: Math.ceil(rejRes.msBeforeNext / 1000)
});
// Простой логгер
const logger = {
info: (message, meta = {}) => {
console.log(`[INFO] ${message}`, JSON.stringify(meta));
},
error: (message, meta = {}) => {
console.error(`[ERROR] ${message}`, JSON.stringify(meta));
}
});
};
// Функция для записи аналитики в файл
async function writeAnalytics(data) {
@@ -100,6 +94,8 @@ async function writeAnalytics(data) {
eventsCount: data.events?.length || 1,
sessionId: data.sessionId
});
return true;
} catch (error) {
logger.error('Error writing analytics data', { error: error.message });
throw error;
@@ -111,11 +107,12 @@ app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
service: 'analytics-server'
service: 'analytics-server',
version: '1.0.0'
});
});
app.post('/api/analytics', async (req, res) => {
app.post('/api/analytics', rateLimit, async (req, res) => {
try {
const { events, sessionId } = req.body;
@@ -132,6 +129,11 @@ app.post('/api/analytics', async (req, res) => {
received: events.length,
sessionId
});
logger.info('Analytics data received', {
count: events.length,
sessionId: sessionId
});
} catch (error) {
logger.error('Analytics processing error', { error: error.message });
res.status(500).json({
@@ -141,14 +143,18 @@ app.post('/api/analytics', async (req, res) => {
}
});
// Статистика (только для разработки)
// Статистика
app.get('/api/stats', async (req, res) => {
if (process.env.NODE_ENV === 'production') {
return res.status(403).json({ error: 'Forbidden' });
}
try {
const dataDir = path.join(__dirname, 'data');
// Проверяем существует ли директория
try {
await fs.access(dataDir);
} catch {
return res.json({ totalEvents: 0, filesCount: 0, uniqueSessions: 0 });
}
const files = await fs.readdir(dataDir);
const analyticsFiles = files.filter(f => f.startsWith('analytics-'));
@@ -156,25 +162,45 @@ app.get('/api/stats', async (req, res) => {
const sessions = new Set();
for (const file of analyticsFiles.slice(-7)) { // Последние 7 дней
const content = await fs.readFile(path.join(dataDir, file), 'utf8');
const data = JSON.parse(content);
try {
const content = await fs.readFile(path.join(dataDir, file), 'utf8');
const data = JSON.parse(content);
data.forEach(entry => {
totalEvents += entry.events?.length || 0;
if (entry.sessionId) sessions.add(entry.sessionId);
});
data.forEach(entry => {
totalEvents += entry.events?.length || 0;
if (entry.sessionId) sessions.add(entry.sessionId);
});
} catch (error) {
logger.error('Error reading analytics file', { file, error: error.message });
}
}
res.json({
totalEvents,
uniqueSessions: sessions.size,
filesCount: analyticsFiles.length
filesCount: analyticsFiles.length,
lastUpdated: new Date().toISOString()
});
} catch (error) {
logger.error('Stats error', { error: error.message });
res.status(500).json({ error: error.message });
}
});
// Информация о сервисе
app.get('/api/info', (req, res) => {
res.json({
service: 'analytics-server',
version: '1.0.0',
status: 'running',
endpoints: [
'POST /api/analytics - Send analytics events',
'GET /api/stats - Get statistics',
'GET /health - Health check'
]
});
});
// Обработка 404
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
@@ -186,7 +212,21 @@ app.use((error, req, res, next) => {
res.status(500).json({ error: 'Internal server error' });
});
app.listen(PORT, '0.0.0.0', () => {
// Создаем необходимые директории при запуске
async function initialize() {
try {
await fs.mkdir(path.join(__dirname, 'logs'), { recursive: true });
await fs.mkdir(path.join(__dirname, 'data'), { recursive: true });
logger.info('Directories initialized');
} catch (error) {
logger.error('Directory initialization failed', { error: error.message });
}
}
app.listen(PORT, '0.0.0.0', async () => {
await initialize();
logger.info(`Analytics server running on port ${PORT}`);
console.log(`🔍 Analytics server: http://localhost:${PORT}`);
console.log(`📊 Health check: http://localhost:${PORT}/health`);
console.log(`📈 Stats: http://localhost:${PORT}/api/stats`);
});