@fuzionx/framework 0.1.69 → 0.1.71
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/cli/templates/make/app-spa/views/default/spa/package.json +1 -1
- package/lib/core/Application.js +67 -4
- package/lib/core/AutoLoader.js +22 -2
- package/lib/core/Base.js +20 -2
- package/lib/http/ErrorHandler.js +3 -1
- package/lib/realtime/RoomManager.js +13 -0
- package/lib/schedule/TelegramTask.js +80 -0
- package/lib/services/Storage.js +2 -1
- package/lib/utilities/TelegramManager.js +120 -0
- package/package.json +2 -2
package/lib/core/Application.js
CHANGED
|
@@ -32,6 +32,7 @@ import CacheManager from '../cache/CacheManager.js';
|
|
|
32
32
|
import SqlModel from '../database/SqlModel.js';
|
|
33
33
|
import MongoModel from '../database/MongoModel.js';
|
|
34
34
|
import WorkerPool from '../schedule/WorkerPool.js';
|
|
35
|
+
import TelegramManager from '../utilities/TelegramManager.js';
|
|
35
36
|
|
|
36
37
|
// Bridge: lazy-load (테스트 환경에서 native 바인딩 없을 수 있음)
|
|
37
38
|
let FuzionXApp;
|
|
@@ -109,6 +110,8 @@ export default class Application {
|
|
|
109
110
|
this._scheduler = null;
|
|
110
111
|
this._queue = null;
|
|
111
112
|
this._wsHandlers = new Map();
|
|
113
|
+
|
|
114
|
+
this.telegram = null;
|
|
112
115
|
|
|
113
116
|
// DB 연결 매니저
|
|
114
117
|
this._connectionManager = new ConnectionManager();
|
|
@@ -139,10 +142,20 @@ export default class Application {
|
|
|
139
142
|
this._factories.set(name, factory);
|
|
140
143
|
}
|
|
141
144
|
|
|
142
|
-
|
|
145
|
+
/**
|
|
146
|
+
* 서비스 인스턴스 생성/조회 (Singleton)
|
|
147
|
+
*
|
|
148
|
+
* @param {string} name - 서비스 이름 (e.g. 'AuthService' 또는 'frontend:AuthService')
|
|
149
|
+
* @param {boolean} [silent=false] - true이면 미등록 시 에러 대신 null 반환
|
|
150
|
+
* @returns {*} 서비스 인스턴스 또는 null (silent=true 시)
|
|
151
|
+
*/
|
|
152
|
+
make(name, silent = false) {
|
|
143
153
|
if (this._services.has(name)) return this._services.get(name);
|
|
144
154
|
const factory = this._factories.get(name);
|
|
145
|
-
if (!factory)
|
|
155
|
+
if (!factory) {
|
|
156
|
+
if (silent) return null;
|
|
157
|
+
throw new Error(`Service '${name}' is not registered`);
|
|
158
|
+
}
|
|
146
159
|
const instance = factory(this);
|
|
147
160
|
this._services.set(name, instance);
|
|
148
161
|
return instance;
|
|
@@ -285,9 +298,16 @@ export default class Application {
|
|
|
285
298
|
// 2. i18n 로드 (04-bootstrap-lifecycle.md)
|
|
286
299
|
await this.i18n.load();
|
|
287
300
|
|
|
288
|
-
// 3. Scheduler / Queue / Storage / Cache 초기화
|
|
301
|
+
// 3. Scheduler / Queue / Telegram / Storage / Cache 초기화
|
|
289
302
|
this._scheduler = new Scheduler(this);
|
|
290
303
|
this._queue = new Queue(this, { driver: this.config.get('queue.driver', 'memory') });
|
|
304
|
+
|
|
305
|
+
// 텔레그램 매니저 로드 (Queue 기반이므로 _queue 설정 직후 구성)
|
|
306
|
+
const telegramConfig = this.config.get('app.telegram');
|
|
307
|
+
if (telegramConfig && telegramConfig.enabled !== false) {
|
|
308
|
+
this.telegram = new TelegramManager(this, telegramConfig);
|
|
309
|
+
}
|
|
310
|
+
|
|
291
311
|
this.storage = new Storage({
|
|
292
312
|
driver: this.config.get('storage.driver', 'local'),
|
|
293
313
|
basePath: path.resolve(this.baseDir, this.config.get('storage.path', './storage')),
|
|
@@ -646,7 +666,9 @@ export default class Application {
|
|
|
646
666
|
if (handler?.__handler__ && handler.controller) {
|
|
647
667
|
const CtrlClass = handler.controller;
|
|
648
668
|
if (!appEntry.controllerCache.has(CtrlClass)) {
|
|
649
|
-
|
|
669
|
+
const instance = new CtrlClass(this);
|
|
670
|
+
instance._appName = appEntry.name; // 앱별 서비스 격리를 위한 네임스페이스 주입
|
|
671
|
+
appEntry.controllerCache.set(CtrlClass, instance);
|
|
650
672
|
}
|
|
651
673
|
}
|
|
652
674
|
}
|
|
@@ -813,6 +835,7 @@ export default class Application {
|
|
|
813
835
|
chainResult = this._executeSyncChain(middlewareFns, ctx, resolvedHandler);
|
|
814
836
|
}
|
|
815
837
|
} catch (err) {
|
|
838
|
+
console.error(`[FX] Unhandled controller error: ${ctx.method} ${ctx.path}`, err);
|
|
816
839
|
this._handleContextError(err, ctx);
|
|
817
840
|
return ctx._toFusionResponse();
|
|
818
841
|
}
|
|
@@ -828,6 +851,7 @@ export default class Application {
|
|
|
828
851
|
this._sendContextResponse(rawReq.requestId, ctx);
|
|
829
852
|
})
|
|
830
853
|
.catch((err) => {
|
|
854
|
+
console.error(`[FX] Unhandled controller error: ${ctx.method} ${ctx.path}`, err);
|
|
831
855
|
this._handleContextError(err, ctx);
|
|
832
856
|
this._sendContextResponse(rawReq.requestId, ctx);
|
|
833
857
|
});
|
|
@@ -948,6 +972,34 @@ export default class Application {
|
|
|
948
972
|
}
|
|
949
973
|
}
|
|
950
974
|
|
|
975
|
+
/**
|
|
976
|
+
* WS 핸들러 수동 등록 — shared/ws 핸들러를 앱별 레지스트리에 추가
|
|
977
|
+
*
|
|
978
|
+
* boot() 이후, listen() 이전에 호출.
|
|
979
|
+
* listen() 내부의 _registerWsHandlers()가 등록된 핸들러를 Bridge에 연결.
|
|
980
|
+
*
|
|
981
|
+
* @example
|
|
982
|
+
* await app.boot();
|
|
983
|
+
* import NotificationHandler from './shared/ws/NotificationHandler.js';
|
|
984
|
+
* app.registerWsHandler(NotificationHandler); // 기본 앱에 등록
|
|
985
|
+
* app.registerWsHandler(NotificationHandler, 'frontend'); // 특정 앱에 등록
|
|
986
|
+
* await app.listen();
|
|
987
|
+
*
|
|
988
|
+
* @param {typeof import('./WsHandler.js').default} HandlerClass - WsHandler 서브클래스
|
|
989
|
+
* @param {string} [appName] - 등록할 앱 이름 (미지정 시 첫 번째 앱)
|
|
990
|
+
*/
|
|
991
|
+
registerWsHandler(HandlerClass, appName) {
|
|
992
|
+
const name = appName || this._getDefaultAppName();
|
|
993
|
+
const appEntry = this._appRegistry.get(name);
|
|
994
|
+
if (!appEntry) {
|
|
995
|
+
this.logger.warn(`[WS] registerWsHandler: 앱 '${name}'을(를) 찾을 수 없습니다.`);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
const ns = HandlerClass.namespace || '/';
|
|
999
|
+
appEntry.wsHandlers.set(ns, HandlerClass);
|
|
1000
|
+
this.logger.debug(`[WS] 수동 등록: ${ns} → 앱 '${name}'`);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
951
1003
|
/**
|
|
952
1004
|
* Host 기반 디스패치 핸들러 생성 (최적화 v2)
|
|
953
1005
|
*
|
|
@@ -1214,6 +1266,17 @@ export default class Application {
|
|
|
1214
1266
|
}
|
|
1215
1267
|
}
|
|
1216
1268
|
this._errorHandler.handle(err, ctx);
|
|
1269
|
+
|
|
1270
|
+
// 'error' 이벤트 발행 — 앱 레벨에서 ErrorLog 기록용 (fire-and-forget)
|
|
1271
|
+
this.emit('error', {
|
|
1272
|
+
error: err,
|
|
1273
|
+
method: ctx.method,
|
|
1274
|
+
path: ctx.path,
|
|
1275
|
+
appName: ctx.appName,
|
|
1276
|
+
user: ctx.user || null,
|
|
1277
|
+
ip: ctx.ip,
|
|
1278
|
+
userAgent: ctx.get?.('user-agent') || '',
|
|
1279
|
+
}).catch(() => {});
|
|
1217
1280
|
}
|
|
1218
1281
|
|
|
1219
1282
|
/**
|
package/lib/core/AutoLoader.js
CHANGED
|
@@ -144,7 +144,13 @@ export default class AutoLoader {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* services/*.js → DI register (앱별 네임스페이스)
|
|
149
|
+
*
|
|
150
|
+
* 앱 모드에서는 'appName:ServiceName' 키로 앱별 격리 등록 +
|
|
151
|
+
* 'ServiceName' 키로 글로벌 등록 (backward compatibility).
|
|
152
|
+
* 동일 이름 서비스가 여러 앱에 존재하면 앱별 키로 격리됨.
|
|
153
|
+
*/
|
|
148
154
|
async loadServices() {
|
|
149
155
|
const files = await scanDir(path.join(this.baseDir, 'services'));
|
|
150
156
|
for (const file of files) {
|
|
@@ -152,7 +158,21 @@ export default class AutoLoader {
|
|
|
152
158
|
const ServiceClass = mod.default;
|
|
153
159
|
if (!ServiceClass) continue;
|
|
154
160
|
const name = extractName(file, 'Service');
|
|
155
|
-
|
|
161
|
+
const globalKey = `${name}Service`;
|
|
162
|
+
const appName = this._appContext?.name;
|
|
163
|
+
|
|
164
|
+
/** 앱별 scoped 등록 (e.g. 'frontend:AuthService') */
|
|
165
|
+
if (appName) {
|
|
166
|
+
const scopedKey = `${appName}:${globalKey}`;
|
|
167
|
+
this.app.register(scopedKey, (app) => {
|
|
168
|
+
const instance = new ServiceClass(app);
|
|
169
|
+
instance._appName = appName; // 서비스 내에서 다른 서비스 호출 시에도 앱 격리 유지
|
|
170
|
+
return instance;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 글로벌 등록 (마지막 앱이 덮어씀 — backward compatibility) */
|
|
175
|
+
this.app.register(globalKey, (app) => new ServiceClass(app));
|
|
156
176
|
}
|
|
157
177
|
}
|
|
158
178
|
|
package/lib/core/Base.js
CHANGED
|
@@ -19,14 +19,32 @@ export default class Base {
|
|
|
19
19
|
this.config = app.config;
|
|
20
20
|
/** @type {object} Logger */
|
|
21
21
|
this.logger = app.logger;
|
|
22
|
+
/**
|
|
23
|
+
* 앱 네임스페이스 — 서비스 resolve 시 앱별 격리에 사용
|
|
24
|
+
* Controller/Service 생성 시 주입됨.
|
|
25
|
+
* @type {string|null}
|
|
26
|
+
*/
|
|
27
|
+
this._appName = null;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/**
|
|
25
|
-
* 서비스 조회 (DI)
|
|
26
|
-
*
|
|
31
|
+
* 서비스 조회 (DI) — 앱별 네임스페이스 자동 적용
|
|
32
|
+
*
|
|
33
|
+
* resolve 순서:
|
|
34
|
+
* 1. _appName이 있으면 'appName:ServiceName' 우선 조회
|
|
35
|
+
* 2. fallback으로 글로벌 'ServiceName' 조회
|
|
36
|
+
*
|
|
37
|
+
* 이 패턴으로 동일 이름의 서비스가 backend/frontend에 각각 존재해도
|
|
38
|
+
* 앱 코드에서는 this.service('AuthService')로 호출 가능.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} name - 서비스 이름 (e.g. 'AuthService')
|
|
27
41
|
* @returns {*}
|
|
28
42
|
*/
|
|
29
43
|
service(name) {
|
|
44
|
+
if (this._appName) {
|
|
45
|
+
const scoped = this.app.make(`${this._appName}:${name}`, true);
|
|
46
|
+
if (scoped) return scoped;
|
|
47
|
+
}
|
|
30
48
|
return this.app.make(name);
|
|
31
49
|
}
|
|
32
50
|
|
package/lib/http/ErrorHandler.js
CHANGED
|
@@ -27,9 +27,11 @@ export default class ErrorHandler {
|
|
|
27
27
|
handle(err, ctx) {
|
|
28
28
|
const status = err.status || 500;
|
|
29
29
|
|
|
30
|
-
// 500
|
|
30
|
+
// 모든 에러 로깅: 500+ → error, 400+ → warn
|
|
31
31
|
if (status >= 500) {
|
|
32
32
|
this.logger(err);
|
|
33
|
+
} else if (status >= 400) {
|
|
34
|
+
this.logger(`[${status}] ${ctx.method} ${ctx.path} — ${err.message}`);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
// JSON 응답 요청이면 JSON으로
|
|
@@ -67,6 +67,19 @@ export default class RoomManager {
|
|
|
67
67
|
return this._rooms.has(room) && this._rooms.get(room).size > 0;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* 소켓이 속한 룸 목록 조회
|
|
72
|
+
* @param {string} socketId
|
|
73
|
+
* @returns {string[]}
|
|
74
|
+
*/
|
|
75
|
+
roomsOf(socketId) {
|
|
76
|
+
const result = [];
|
|
77
|
+
for (const [room, members] of this._rooms) {
|
|
78
|
+
if (members.has(socketId)) result.push(room);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
/**
|
|
71
84
|
* 전체 룸 목록
|
|
72
85
|
* @returns {string[]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import Task from './Task.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TelegramTask - 텔레그램 메시지 발송을 위한 비동기 백그라운드 큐 작업
|
|
5
|
+
*
|
|
6
|
+
* I/O 바운드 작업으로, 메인 스레드의 블로킹을 방지하고
|
|
7
|
+
* 텔레그램 API의 Rate-Limit(429) 응답에 대비하여 자동 재시도 기능을 제공합니다.
|
|
8
|
+
*/
|
|
9
|
+
export default class TelegramTask extends Task {
|
|
10
|
+
/** @type {string} 큐의 고유 이름 */
|
|
11
|
+
static queue = 'telegram';
|
|
12
|
+
|
|
13
|
+
/** @type {number} 실패 시 최대 재시도 횟수 */
|
|
14
|
+
static retries = 3;
|
|
15
|
+
|
|
16
|
+
/** @type {number} 실패 시 다음 재시도 대기 시간 (밀리초) - 텔레그램 속도 제한 방어 */
|
|
17
|
+
static retryDelay = 3000;
|
|
18
|
+
|
|
19
|
+
/** @type {number} API 호출 제한 시간 (밀리초) */
|
|
20
|
+
static timeout = 10000;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 작업의 실행 본문
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} data 디스패치 될 때 넘어온 데이터
|
|
26
|
+
* @param {string} data.botToken 봇의 인증 토큰
|
|
27
|
+
* @param {string} data.chatId 수신 대상 채팅/채널 ID
|
|
28
|
+
* @param {string} data.text 발송할 메시지 내용
|
|
29
|
+
* @param {string} [data.parseMode='HTML'] 파싱 모드 (HTML, MarkdownV2 등)
|
|
30
|
+
* @returns {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
async handle(data) {
|
|
33
|
+
const { botToken, chatId, text, parseMode = 'HTML' } = data;
|
|
34
|
+
|
|
35
|
+
if (!botToken || !chatId || !text) {
|
|
36
|
+
throw new Error('텔레그램 발송 누락 데이터: botToken, chatId 또는 text가 없습니다.');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Node.js 환경의 전역 fetch (v18+)
|
|
43
|
+
const response = await fetch(url, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
chat_id: chatId,
|
|
50
|
+
text: text,
|
|
51
|
+
parse_mode: parseMode,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
// Rate-Limit 이나 서버 에러의 경우, 이 에러가 throw 되어 Queue.js 의 catch 블록으로 이동, 재시도 됨
|
|
57
|
+
const errorText = await response.text();
|
|
58
|
+
throw new Error(`텔레그램 API 에러 (${response.status}): ${errorText}`);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// 네트워크 연결 실패 등 예기치 않은 오류 시 재시도를 위해 상위로 에러 던지기
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 모든 재시도 소진 후 최종적으로 실패했을 때 호출되는 훅
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} data
|
|
70
|
+
* @param {Error} error
|
|
71
|
+
*/
|
|
72
|
+
async failed(data, error) {
|
|
73
|
+
// Application 객체(this.app)를 통해 코어 로거에 치명적 에러 기록
|
|
74
|
+
if (this.app && this.app.logger) {
|
|
75
|
+
this.app.logger.error(`[TelegramTask] 메시지 발송 최종 실패 (대상 채널: ${data.chatId}):`, error.message);
|
|
76
|
+
} else {
|
|
77
|
+
console.error(`[TelegramTask] 메시지 발송 최종 실패:`, error.message);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
package/lib/services/Storage.js
CHANGED
|
@@ -147,7 +147,8 @@ export default class Storage {
|
|
|
147
147
|
// AWS S3 기본 URL
|
|
148
148
|
return `https://${this._s3.bucket}.s3.${this._s3.region || 'us-east-1'}.amazonaws.com/${filePath}`;
|
|
149
149
|
}
|
|
150
|
-
return `/storage/${filePath}`;
|
|
150
|
+
// return `/storage/${filePath}`;
|
|
151
|
+
return filePath;
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
// ── S3 호환 스토리지 구현 (@aws-sdk/client-s3) ──
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import TelegramTask from '../schedule/TelegramTask.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TelegramBot - 개별 텔레그램 봇의 인스턴스를 래핑합니다.
|
|
5
|
+
* 백그라운드 큐 시스템에 전송 요청을 위임하여 병목을 방지합니다.
|
|
6
|
+
*/
|
|
7
|
+
class TelegramBot {
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('../core/Application.js').default} app 프레임워크 애플리케이션 인스턴스
|
|
10
|
+
* @param {string} token 봇 API 인증 토큰
|
|
11
|
+
* @param {string} chatId 수신 채팅/채널 ID
|
|
12
|
+
*/
|
|
13
|
+
constructor(app, token, chatId) {
|
|
14
|
+
this.app = app;
|
|
15
|
+
this.token = token;
|
|
16
|
+
this.chatId = chatId;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 비동기로 텔레그램 메시지를 발송 (Queue에 적재)
|
|
21
|
+
*
|
|
22
|
+
* @param {string} text 보낼 메시지 내용
|
|
23
|
+
* @param {Object} [options] 전송 옵션
|
|
24
|
+
* @param {string} [options.parseMode='HTML'] 파싱 포맷 (HTML, MarkdownV2 등)
|
|
25
|
+
*/
|
|
26
|
+
sendMessage(text, options = {}) {
|
|
27
|
+
const parseMode = options.parseMode || 'HTML';
|
|
28
|
+
if (this.app._queue) {
|
|
29
|
+
this.app._queue.dispatch('TelegramTask', {
|
|
30
|
+
botToken: this.token,
|
|
31
|
+
chatId: this.chatId,
|
|
32
|
+
text: text,
|
|
33
|
+
parseMode: parseMode
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
this.app.logger.warn('[TelegramBot] 앱 큐(Queue) 시스템이 비활성화 되어 있어 메시지가 전송되지 않았습니다.');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* HTML 파싱을 지원하는 전송 헬퍼 메서드
|
|
42
|
+
* @param {string} html
|
|
43
|
+
*/
|
|
44
|
+
sendHtml(html) {
|
|
45
|
+
return this.sendMessage(html, { parseMode: 'HTML' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* MarkdownV2 파싱을 지원하는 전송 헬퍼 메서드
|
|
50
|
+
* @param {string} markdown
|
|
51
|
+
*/
|
|
52
|
+
sendMarkdown(markdown) {
|
|
53
|
+
return this.sendMessage(markdown, { parseMode: 'MarkdownV2' });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* TelegramManager - yaml 설정에 등록된 복수 개의 텔레그램 봇 인스턴스를 관리합니다.
|
|
59
|
+
* 프레임워크 코어 컨테이너에 의해 싱글톤으로 유지됩니다.
|
|
60
|
+
*/
|
|
61
|
+
export default class TelegramManager {
|
|
62
|
+
/**
|
|
63
|
+
* @param {import('../core/Application.js').default} app
|
|
64
|
+
* @param {Object} config fuzionx.yaml 의 app.telegram 설정 객체
|
|
65
|
+
*/
|
|
66
|
+
constructor(app, config) {
|
|
67
|
+
this.app = app;
|
|
68
|
+
this.config = config || {};
|
|
69
|
+
this._bots = new Map();
|
|
70
|
+
|
|
71
|
+
this._initialize();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 큐에 환경을 구성하고 봇 목록을 구성합니다.
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
_initialize() {
|
|
79
|
+
// 백그라운드 워커에 텔레그램 전송 작업을 담당할 클래스 명세 등록
|
|
80
|
+
if (this.app._queue) {
|
|
81
|
+
this.app._queue.register('TelegramTask', TelegramTask);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// yaml 설정 파일 기반 봇 초기화
|
|
85
|
+
const botsConfig = this.config.bots || {};
|
|
86
|
+
for (const [name, info] of Object.entries(botsConfig)) {
|
|
87
|
+
if (info.token && info.chat_id) {
|
|
88
|
+
// 숫자로 된 채널 ID도 문자열로 변환하여 보존 처리
|
|
89
|
+
const chatIdStr = String(info.chat_id);
|
|
90
|
+
this._bots.set(name, new TelegramBot(this.app, info.token, chatIdStr));
|
|
91
|
+
} else {
|
|
92
|
+
this.app.logger.warn(`[TelegramManager] '${name}' 봇 설정 오류: token 또는 chat_id가 확인되지 않습니다.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 등록된 특정 이름의 봇 인스턴스를 가져옵니다.
|
|
99
|
+
* @param {string} name 봇의 고유 명칭 (예: 'system', 'billing')
|
|
100
|
+
* @returns {TelegramBot|null} 봇 인스턴스 또는 null
|
|
101
|
+
*/
|
|
102
|
+
get(name) {
|
|
103
|
+
return this._bots.get(name) || null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 원하는 대상 봇에게 체인 없이 한 번에 직접 메시지를 보냅니다.
|
|
108
|
+
* @param {string} name 발송할 봇 명칭
|
|
109
|
+
* @param {string} text 메시지 내용
|
|
110
|
+
* @param {Object} [options] 파싱 모드 등 옵션 (get(name) 이후 sendMessage 와 동일)
|
|
111
|
+
*/
|
|
112
|
+
send(name, text, options) {
|
|
113
|
+
const bot = this.get(name);
|
|
114
|
+
if (bot) {
|
|
115
|
+
bot.sendMessage(text, options);
|
|
116
|
+
} else {
|
|
117
|
+
this.app.logger.warn(`[TelegramManager] 설정 파일에 명명된 봇 '${name}'을 찾을 수 없습니다.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzionx/framework",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.71",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Full-stack MVC framework built on @fuzionx/core — Controller, Service, Model, Middleware, DI, EventBus",
|
|
6
6
|
"main": "index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@aws-sdk/client-s3": "^3.1028.0",
|
|
38
|
-
"@fuzionx/core": "^0.1.
|
|
38
|
+
"@fuzionx/core": "^0.1.71",
|
|
39
39
|
"better-sqlite3": "^12.8.0",
|
|
40
40
|
"knex": "^3.2.5",
|
|
41
41
|
"mongoose": "^9.3.2",
|