qwen-alpha 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +21 -0
- package/.nvmrc +1 -0
- package/.prettierrc +9 -0
- package/LICENSE +21 -0
- package/PUBLISH.md +252 -0
- package/README.md +282 -0
- package/bin/qwen-alpha.js +95 -0
- package/package.json +50 -0
- package/src/bot/bot.js +81 -0
- package/src/bot/handlers/admin.js +231 -0
- package/src/bot/handlers/file.js +177 -0
- package/src/bot/handlers/help.js +43 -0
- package/src/bot/handlers/message.js +132 -0
- package/src/bot/handlers/reset.js +53 -0
- package/src/bot/handlers/settings.js +39 -0
- package/src/bot/handlers/start.js +65 -0
- package/src/bot/handlers/stats.js +76 -0
- package/src/bot/middleware/auth.js +70 -0
- package/src/bot/middleware/logging.js +55 -0
- package/src/bot/middleware/rateLimit.js +74 -0
- package/src/bot/middleware/session.js +63 -0
- package/src/config/index.js +57 -0
- package/src/index.js +86 -0
- package/src/services/db/admins.js +146 -0
- package/src/services/db/index.js +177 -0
- package/src/services/db/sessions.js +342 -0
- package/src/services/db/stats.js +223 -0
- package/src/services/db/users.js +215 -0
- package/src/services/qwenService.js +254 -0
- package/src/utils/logger.js +127 -0
- package/src/utils/paths.js +63 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
const { storeManager } = require('./index');
|
|
2
|
+
const { logger } = require('../../utils/logger');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Сервис для управления сессиями
|
|
6
|
+
* Поддерживает древовидную структуру для групповых чатов
|
|
7
|
+
*/
|
|
8
|
+
class SessionService {
|
|
9
|
+
/**
|
|
10
|
+
* Получение хранилища сессий
|
|
11
|
+
* @private
|
|
12
|
+
*/
|
|
13
|
+
get _store() {
|
|
14
|
+
return storeManager.get('sessions');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Генерация ключа сессии
|
|
19
|
+
* @param {number} userId - Telegram user ID
|
|
20
|
+
* @param {number} chatId - Telegram chat ID
|
|
21
|
+
* @returns {string} Ключ сессии
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
_getSessionKey(userId, chatId) {
|
|
25
|
+
// Личный чат: user:{userId}
|
|
26
|
+
// Групповой чат: chat:{chatId}:session:{sessionId}
|
|
27
|
+
if (userId === chatId || chatId > 0) {
|
|
28
|
+
return `user:${userId}`;
|
|
29
|
+
}
|
|
30
|
+
return `chat:${chatId}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Создание новой сессии
|
|
35
|
+
* @param {Object} options - Опции сессии
|
|
36
|
+
* @param {number} options.userId - Telegram user ID
|
|
37
|
+
* @param {number} options.chatId - Telegram chat ID
|
|
38
|
+
* @param {number} options.rootMessageId - ID корневого сообщения
|
|
39
|
+
* @param {string} [options.chatType] - Тип чата (private, group, supergroup)
|
|
40
|
+
* @param {string} [options.chatTitle] - Название чата (для групп)
|
|
41
|
+
* @returns {Object} Данные сессии
|
|
42
|
+
*/
|
|
43
|
+
create({ userId, chatId, rootMessageId, chatType = 'private', chatTitle = null }) {
|
|
44
|
+
const data = this._store.getData();
|
|
45
|
+
const now = new Date();
|
|
46
|
+
const timeoutHours = storeManager.get('settings').getData().session_timeout_hours || 24;
|
|
47
|
+
const expiresAt = new Date(now.getTime() + timeoutHours * 60 * 60 * 1000);
|
|
48
|
+
|
|
49
|
+
const sessionKey = this._getSessionKey(userId, chatId);
|
|
50
|
+
const sessionId = `${sessionKey}:session:${now.getTime()}`;
|
|
51
|
+
|
|
52
|
+
const session = {
|
|
53
|
+
session_id: sessionId,
|
|
54
|
+
chat_id: chatId,
|
|
55
|
+
root_message_id: rootMessageId,
|
|
56
|
+
root_user_id: userId,
|
|
57
|
+
chat_type: chatType,
|
|
58
|
+
chat_title: chatTitle,
|
|
59
|
+
created_at: now.toISOString(),
|
|
60
|
+
expires_at: expiresAt.toISOString(),
|
|
61
|
+
status: 'active',
|
|
62
|
+
message_tree: {
|
|
63
|
+
[rootMessageId]: {
|
|
64
|
+
message_id: rootMessageId,
|
|
65
|
+
user_id: userId,
|
|
66
|
+
text: '',
|
|
67
|
+
type: 'root',
|
|
68
|
+
children: [],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
context: {
|
|
72
|
+
summary: '',
|
|
73
|
+
keywords: [],
|
|
74
|
+
last_summary_at: null,
|
|
75
|
+
},
|
|
76
|
+
participants: [userId],
|
|
77
|
+
message_count: 1,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Для групповых чатов - массив сессий
|
|
81
|
+
if (chatType !== 'private') {
|
|
82
|
+
if (!data[sessionKey]) {
|
|
83
|
+
data[sessionKey] = [];
|
|
84
|
+
}
|
|
85
|
+
data[sessionKey].push(session);
|
|
86
|
+
} else {
|
|
87
|
+
data[sessionKey] = session;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this._store.setData(data);
|
|
91
|
+
logger.info({ sessionId, userId, chatId }, 'Session created');
|
|
92
|
+
|
|
93
|
+
return session;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Получение сессии по ключу
|
|
98
|
+
* @param {string} sessionKey - Ключ сессии
|
|
99
|
+
* @returns {Object|null} Сессия или null
|
|
100
|
+
*/
|
|
101
|
+
getByKey(sessionKey) {
|
|
102
|
+
const data = this._store.getData();
|
|
103
|
+
return data[sessionKey] || null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Поиск сессии по ID сообщения (для reply в группах)
|
|
108
|
+
* @param {number} chatId - Telegram chat ID
|
|
109
|
+
* @param {number} messageId - ID сообщения
|
|
110
|
+
* @returns {Object|null} Сессия или null
|
|
111
|
+
*/
|
|
112
|
+
findByMessage(chatId, messageId) {
|
|
113
|
+
const data = this._store.getData();
|
|
114
|
+
const chatKey = `chat:${chatId}`;
|
|
115
|
+
|
|
116
|
+
const sessions = data[chatKey];
|
|
117
|
+
if (!Array.isArray(sessions)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const session of sessions) {
|
|
122
|
+
if (session.message_tree && session.message_tree[messageId]) {
|
|
123
|
+
return session;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Добавление сообщения в дерево сессии
|
|
132
|
+
* @param {Object} options - Опции
|
|
133
|
+
* @param {string} options.sessionId - ID сессии
|
|
134
|
+
* @param {number} options.chatId - Telegram chat ID
|
|
135
|
+
* @param {number} options.messageId - ID сообщения
|
|
136
|
+
* @param {number|string} options.userId - Telegram user ID (или 'bot')
|
|
137
|
+
* @param {string} options.text - Текст сообщения
|
|
138
|
+
* @param {number} [options.parentId] - ID родительского сообщения
|
|
139
|
+
* @param {string} [options.type] - Тип сообщения
|
|
140
|
+
* @returns {Object|null} Обновлённая сессия или null
|
|
141
|
+
*/
|
|
142
|
+
addMessage({ sessionId, chatId, messageId, userId, text, parentId = null, type = 'user_message' }) {
|
|
143
|
+
const data = this._store.getData();
|
|
144
|
+
const chatKey = `chat:${chatId}`;
|
|
145
|
+
|
|
146
|
+
let session;
|
|
147
|
+
let sessionIndex = -1;
|
|
148
|
+
|
|
149
|
+
// Поиск сессии
|
|
150
|
+
if (Array.isArray(data[chatKey])) {
|
|
151
|
+
sessionIndex = data[chatKey].findIndex(s => s.session_id === sessionId);
|
|
152
|
+
if (sessionIndex === -1) {
|
|
153
|
+
logger.warn({ sessionId, chatId }, 'Session not found');
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
session = data[chatKey][sessionIndex];
|
|
157
|
+
} else {
|
|
158
|
+
session = data[chatKey];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!session || session.status !== 'active') {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Добавление сообщения в дерево
|
|
166
|
+
session.message_tree[messageId] = {
|
|
167
|
+
message_id: messageId,
|
|
168
|
+
user_id: userId,
|
|
169
|
+
text,
|
|
170
|
+
type,
|
|
171
|
+
parent_id: parentId,
|
|
172
|
+
children: [],
|
|
173
|
+
created_at: new Date().toISOString(),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Обновление родительского узла
|
|
177
|
+
if (parentId && session.message_tree[parentId]) {
|
|
178
|
+
if (!session.message_tree[parentId].children) {
|
|
179
|
+
session.message_tree[parentId].children = [];
|
|
180
|
+
}
|
|
181
|
+
session.message_tree[parentId].children.push(messageId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Обновление участников
|
|
185
|
+
if (typeof userId === 'number' && !session.participants.includes(userId)) {
|
|
186
|
+
session.participants.push(userId);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
session.message_count++;
|
|
190
|
+
|
|
191
|
+
// Сохранение
|
|
192
|
+
if (Array.isArray(data[chatKey])) {
|
|
193
|
+
data[chatKey][sessionIndex] = session;
|
|
194
|
+
} else {
|
|
195
|
+
data[chatKey] = session;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this._store.setData(data);
|
|
199
|
+
logger.debug({ sessionId, messageId }, 'Message added to session');
|
|
200
|
+
|
|
201
|
+
return session;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Получение цепочки сообщений от корня до указанного
|
|
206
|
+
* @param {Object} session - Сессия
|
|
207
|
+
* @param {number} messageId - ID сообщения
|
|
208
|
+
* @returns {Array} Массив сообщений от корня до текущего
|
|
209
|
+
*/
|
|
210
|
+
getMessageChain(session, messageId) {
|
|
211
|
+
const chain = [];
|
|
212
|
+
let currentId = messageId;
|
|
213
|
+
|
|
214
|
+
while (currentId) {
|
|
215
|
+
const message = session.message_tree[currentId];
|
|
216
|
+
if (!message) break;
|
|
217
|
+
|
|
218
|
+
chain.unshift({
|
|
219
|
+
role: message.user_id === 'bot' ? 'assistant' : 'user',
|
|
220
|
+
content: message.text || '',
|
|
221
|
+
message_id: message.message_id,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
currentId = message.parent_id;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return chain;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Обновление контекста сессии
|
|
232
|
+
* @param {string} sessionId - ID сессии
|
|
233
|
+
* @param {number} chatId - Telegram chat ID
|
|
234
|
+
* @param {Object} context - Новый контекст
|
|
235
|
+
* @returns {Object|null} Обновлённая сессия
|
|
236
|
+
*/
|
|
237
|
+
updateContext(sessionId, chatId, context) {
|
|
238
|
+
const data = this._store.getData();
|
|
239
|
+
const chatKey = `chat:${chatId}`;
|
|
240
|
+
|
|
241
|
+
let session;
|
|
242
|
+
let sessionIndex = -1;
|
|
243
|
+
|
|
244
|
+
if (Array.isArray(data[chatKey])) {
|
|
245
|
+
sessionIndex = data[chatKey].findIndex(s => s.session_id === sessionId);
|
|
246
|
+
if (sessionIndex === -1) return null;
|
|
247
|
+
session = data[chatKey][sessionIndex];
|
|
248
|
+
} else {
|
|
249
|
+
session = data[chatKey];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
session.context = { ...session.context, ...context };
|
|
253
|
+
|
|
254
|
+
if (Array.isArray(data[chatKey])) {
|
|
255
|
+
data[chatKey][sessionIndex] = session;
|
|
256
|
+
} else {
|
|
257
|
+
data[chatKey] = session;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this._store.setData(data);
|
|
261
|
+
return session;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Закрытие сессии
|
|
266
|
+
* @param {string} sessionId - ID сессии
|
|
267
|
+
* @param {number} chatId - Telegram chat ID
|
|
268
|
+
* @returns {boolean} Успешность
|
|
269
|
+
*/
|
|
270
|
+
close(sessionId, chatId) {
|
|
271
|
+
const data = this._store.getData();
|
|
272
|
+
const chatKey = `chat:${chatId}`;
|
|
273
|
+
|
|
274
|
+
if (Array.isArray(data[chatKey])) {
|
|
275
|
+
const index = data[chatKey].findIndex(s => s.session_id === sessionId);
|
|
276
|
+
if (index === -1) return false;
|
|
277
|
+
|
|
278
|
+
data[chatKey][index].status = 'closed';
|
|
279
|
+
data[chatKey][index].closed_at = new Date().toISOString();
|
|
280
|
+
} else if (data[chatKey]?.session_id === sessionId) {
|
|
281
|
+
data[chatKey].status = 'closed';
|
|
282
|
+
data[chatKey].closed_at = new Date().toISOString();
|
|
283
|
+
} else {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this._store.setData(data);
|
|
288
|
+
logger.info({ sessionId, chatId }, 'Session closed');
|
|
289
|
+
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Очистка просроченных сессий
|
|
295
|
+
* @returns {number} Количество удалённых сессий
|
|
296
|
+
*/
|
|
297
|
+
cleanupExpired() {
|
|
298
|
+
const data = this._store.getData();
|
|
299
|
+
const now = new Date();
|
|
300
|
+
let removed = 0;
|
|
301
|
+
|
|
302
|
+
for (const [key, value] of Object.entries(data)) {
|
|
303
|
+
if (Array.isArray(value)) {
|
|
304
|
+
// Групповые сессии
|
|
305
|
+
const filtered = value.filter(session => {
|
|
306
|
+
const expiresAt = new Date(session.expires_at);
|
|
307
|
+
if (expiresAt < now) {
|
|
308
|
+
removed++;
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
});
|
|
313
|
+
data[key] = filtered;
|
|
314
|
+
} else if (value?.expires_at) {
|
|
315
|
+
// Личная сессия
|
|
316
|
+
const expiresAt = new Date(value.expires_at);
|
|
317
|
+
if (expiresAt < now) {
|
|
318
|
+
delete data[key];
|
|
319
|
+
removed++;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
this._store.setData(data);
|
|
325
|
+
logger.info({ removed }, 'Expired sessions cleaned up');
|
|
326
|
+
|
|
327
|
+
return removed;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Получение активных сессий чата
|
|
332
|
+
* @param {number} chatId - Telegram chat ID
|
|
333
|
+
* @returns {Array} Массив сессий
|
|
334
|
+
*/
|
|
335
|
+
getChatSessions(chatId) {
|
|
336
|
+
const data = this._store.getData();
|
|
337
|
+
const chatKey = `chat:${chatId}`;
|
|
338
|
+
return data[chatKey] || [];
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
module.exports = new SessionService();
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
const { storeManager } = require('./index');
|
|
2
|
+
const { logger } = require('../../utils/logger');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Сервис для управления статистикой
|
|
6
|
+
*/
|
|
7
|
+
class StatsService {
|
|
8
|
+
/**
|
|
9
|
+
* Получение хранилища статистики
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
get _store() {
|
|
13
|
+
return storeManager.get('stats');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Получение сегодняшней даты в формате YYYY-MM-DD
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
get _today() {
|
|
21
|
+
return new Date().toISOString().split('T')[0];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Инициализация дня в статистике
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
28
|
+
_initDay() {
|
|
29
|
+
const data = this._store.getData();
|
|
30
|
+
const today = this._today;
|
|
31
|
+
|
|
32
|
+
if (!data.daily[today]) {
|
|
33
|
+
data.daily[today] = {
|
|
34
|
+
requests: 0,
|
|
35
|
+
users: 0,
|
|
36
|
+
errors: 0,
|
|
37
|
+
files: 0,
|
|
38
|
+
sessions_created: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Инкремент запроса
|
|
47
|
+
*/
|
|
48
|
+
incrementRequest() {
|
|
49
|
+
const data = this._initDay();
|
|
50
|
+
|
|
51
|
+
data.global.requests_today++;
|
|
52
|
+
data.daily[this._today].requests++;
|
|
53
|
+
|
|
54
|
+
this._store.setData(data);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Инкремент ошибки
|
|
59
|
+
*/
|
|
60
|
+
incrementError() {
|
|
61
|
+
const data = this._initDay();
|
|
62
|
+
|
|
63
|
+
data.global.errors_24h++;
|
|
64
|
+
data.daily[this._today].errors++;
|
|
65
|
+
|
|
66
|
+
this._store.setData(data);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Инкремент анализа файла
|
|
71
|
+
*/
|
|
72
|
+
incrementFile() {
|
|
73
|
+
const data = this._initDay();
|
|
74
|
+
|
|
75
|
+
data.daily[this._today].files++;
|
|
76
|
+
|
|
77
|
+
this._store.setData(data);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Инкремент создания сессии
|
|
82
|
+
*/
|
|
83
|
+
incrementSessionCreated() {
|
|
84
|
+
const data = this._initDay();
|
|
85
|
+
|
|
86
|
+
data.daily[this._today].sessions_created++;
|
|
87
|
+
|
|
88
|
+
this._store.setData(data);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Обновление среднего времени ответа
|
|
93
|
+
* @param {number} responseTimeMs - Время ответа в мс
|
|
94
|
+
*/
|
|
95
|
+
updateAvgResponseTime(responseTimeMs) {
|
|
96
|
+
const data = this._store.getData();
|
|
97
|
+
const currentAvg = data.global.avg_response_time_ms;
|
|
98
|
+
const totalRequests = data.global.requests_today;
|
|
99
|
+
|
|
100
|
+
// Скользящее среднее
|
|
101
|
+
const newAvg = totalRequests > 0
|
|
102
|
+
? ((currentAvg * (totalRequests - 1)) + responseTimeMs) / totalRequests
|
|
103
|
+
: responseTimeMs;
|
|
104
|
+
|
|
105
|
+
data.global.avg_response_time_ms = Math.round(newAvg);
|
|
106
|
+
|
|
107
|
+
this._store.setData(data);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Обновление количества активных пользователей за 24ч
|
|
112
|
+
* @param {number} count - Количество пользователей
|
|
113
|
+
*/
|
|
114
|
+
updateActiveUsers24h(count) {
|
|
115
|
+
const data = this._store.getData();
|
|
116
|
+
data.global.active_24h = count;
|
|
117
|
+
this._store.setData(data);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Обновление общего количества пользователей
|
|
122
|
+
* @param {number} count - Количество пользователей
|
|
123
|
+
*/
|
|
124
|
+
updateTotalUsers(count) {
|
|
125
|
+
const data = this._store.getData();
|
|
126
|
+
data.global.total_users = count;
|
|
127
|
+
this._store.setData(data);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Получение глобальной статистики
|
|
132
|
+
* @returns {Object} Глобальная статистика
|
|
133
|
+
*/
|
|
134
|
+
getGlobal() {
|
|
135
|
+
const data = this._store.getData();
|
|
136
|
+
return data.global;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Получение статистики за день
|
|
141
|
+
* @param {string} [date] - Дата в формате YYYY-MM-DD (по умолчанию сегодня)
|
|
142
|
+
* @returns {Object} Статистика за день
|
|
143
|
+
*/
|
|
144
|
+
getDaily(date = this._today) {
|
|
145
|
+
const data = this._store.getData();
|
|
146
|
+
return data.daily[date] || {
|
|
147
|
+
requests: 0,
|
|
148
|
+
users: 0,
|
|
149
|
+
errors: 0,
|
|
150
|
+
files: 0,
|
|
151
|
+
sessions_created: 0,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Получение статистики за период
|
|
157
|
+
* @param {number} days - Количество дней
|
|
158
|
+
* @returns {Object} Статистика за период
|
|
159
|
+
*/
|
|
160
|
+
getPeriod(days) {
|
|
161
|
+
const data = this._store.getData();
|
|
162
|
+
const result = {
|
|
163
|
+
total_requests: 0,
|
|
164
|
+
total_errors: 0,
|
|
165
|
+
total_files: 0,
|
|
166
|
+
total_sessions: 0,
|
|
167
|
+
daily: [],
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < days; i++) {
|
|
171
|
+
const date = new Date();
|
|
172
|
+
date.setDate(date.getDate() - i);
|
|
173
|
+
const dateStr = date.toISOString().split('T')[0];
|
|
174
|
+
|
|
175
|
+
const dayStats = data.daily[dateStr] || {
|
|
176
|
+
requests: 0,
|
|
177
|
+
errors: 0,
|
|
178
|
+
files: 0,
|
|
179
|
+
sessions_created: 0,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
result.total_requests += dayStats.requests;
|
|
183
|
+
result.total_errors += dayStats.errors;
|
|
184
|
+
result.total_files += dayStats.files;
|
|
185
|
+
result.total_sessions += dayStats.sessions_created;
|
|
186
|
+
|
|
187
|
+
result.daily.push({
|
|
188
|
+
date: dateStr,
|
|
189
|
+
...dayStats,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Сброс статистики за день (вызывается ежедневно)
|
|
198
|
+
*/
|
|
199
|
+
resetDaily() {
|
|
200
|
+
const data = this._store.getData();
|
|
201
|
+
|
|
202
|
+
// Архивация старых данных (опционально)
|
|
203
|
+
// Очистка данных старше 30 дней
|
|
204
|
+
const thirtyDaysAgo = new Date();
|
|
205
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
206
|
+
const cutoffDate = thirtyDaysAgo.toISOString().split('T')[0];
|
|
207
|
+
|
|
208
|
+
for (const date of Object.keys(data.daily)) {
|
|
209
|
+
if (date < cutoffDate) {
|
|
210
|
+
delete data.daily[date];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Сброс счётчиков 24ч
|
|
215
|
+
data.global.errors_24h = 0;
|
|
216
|
+
data.global.active_24h = 0;
|
|
217
|
+
|
|
218
|
+
this._store.setData(data);
|
|
219
|
+
logger.info('Daily stats reset');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = new StatsService();
|