modified: main_dc/valitovgaziz/analytics/package.json
modified: main_dc/valitovgaziz/analytics/server.js change dependencies
This commit is contained in:
@@ -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": {
|
||||
|
||||
@@ -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`);
|
||||
});
|
||||
Reference in New Issue
Block a user