Browse Source

✨ feat: 乐跑、api分离

Pchen. 4 days ago
parent
commit
e591922bf2
5 changed files with 116 additions and 62 deletions
  1. 1 0
      config.example.json
  2. 74 61
      lib/Server.js
  3. 29 0
      lib/serverRole.js
  4. 2 1
      package.json
  5. 10 0
      worker.js

+ 1 - 0
config.example.json

@@ -1,5 +1,6 @@
 {
 {
     "port": 30003,
     "port": 30003,
+    "serverRole": "all",
     "database": {
     "database": {
         "host": "YOUR_MYSQL_HOST",
         "host": "YOUR_MYSQL_HOST",
         "database": "YOUR_DB_NAME",
         "database": "YOUR_DB_NAME",

+ 74 - 61
lib/Server.js

@@ -11,33 +11,33 @@ const { mq: mqName } = require('../plugin/mq/mqPrefix')
 const { startLepaoSchedulePublisher } = require('../plugin/mq/lepaoSchedulePublisher')
 const { startLepaoSchedulePublisher } = require('../plugin/mq/lepaoSchedulePublisher')
 const OneBotV11 = require('../plugin/OneBot/OneBotV11')
 const OneBotV11 = require('../plugin/OneBot/OneBotV11')
 const AccessControl = require('./AccessControl')
 const AccessControl = require('./AccessControl')
+const {
+    resolveServerRole,
+    shouldServeApi,
+    shouldRunLepaoWorker
+} = require('./serverRole')
 
 
 class SERVER {
 class SERVER {
     constructor() {
     constructor() {
-        this.app = express()
+        this.serverRole = resolveServerRole()
         this.port = config.port || 3000
         this.port = config.port || 3000
-        this.apiDirectory = path.join(__dirname, '../apis') // API 文件存放目录
+        this.apiDirectory = path.join(__dirname, '../apis')
 
 
-        this.logger = new Logger(path.join(__dirname, '../logs/Server.log'), 'INFO')
+        const logName = this.serverRole === 'worker' ? 'WorkerServer.log' : 'Server.log'
+        this.logger = new Logger(path.join(__dirname, '../logs', logName), 'INFO')
 
 
-        // 解析 JSON 请求体
-        this.app.use(express.json())
-
-        //解决cors跨域
-        this.app.use(cors())
-
-        //使用静态资源
-        this.app.use('/uploads', express.static('./uploads'))
-        this.app.use('/models', express.static('./models'))
-
-        // 初始化数据库连接
         this.db = new MySQL()
         this.db = new MySQL()
 
 
-        // 加载 API 路由
-        this.loadAPIs(this.apiDirectory)
+        if (shouldServeApi(this.serverRole)) {
+            this.app = express()
+            this.app.use(express.json())
+            this.app.use(cors())
+            this.app.use('/uploads', express.static('./uploads'))
+            this.app.use('/models', express.static('./models'))
+            this.loadAPIs(this.apiDirectory)
+        }
     }
     }
 
 
-    // 测试数据库连接
     async initDB() {
     async initDB() {
         try {
         try {
             this.logger.info('正在测试数据库连接')
             this.logger.info('正在测试数据库连接')
@@ -49,7 +49,17 @@ class SERVER {
         }
         }
     }
     }
 
 
-    // 测试MQ连接
+    async startLepaoWorker() {
+        const worker = new Worker()
+        await worker.start()
+        this.logger.info('RunForge Worker 已启动,正在监听 MQ 任务...')
+        startLepaoSchedulePublisher({
+            logger: this.logger,
+            intervalMs: config.rabbitmq?.lepaoScheduleTickMs ?? 2000,
+            batch: config.rabbitmq?.lepaoScheduleBatch ?? 100
+        })
+    }
+
     async initMQ() {
     async initMQ() {
         try {
         try {
             await mq.init()
             await mq.init()
@@ -58,18 +68,15 @@ class SERVER {
             await ch.assertQueue(mqName('mq_health_check'), { durable: false })
             await ch.assertQueue(mqName('mq_health_check'), { durable: false })
             this.logger.info('✅ RabbitMQ 初始化 & 测试成功')
             this.logger.info('✅ RabbitMQ 初始化 & 测试成功')
 
 
-            const worker = new Worker()
-            try {
-                await worker.start()
-                this.logger.info('RunForge Worker 已启动,正在监听 MQ 任务...')
-                startLepaoSchedulePublisher({
-                    logger: this.logger,
-                    intervalMs: config.rabbitmq?.lepaoScheduleTickMs ?? 2000,
-                    batch: config.rabbitmq?.lepaoScheduleBatch ?? 100
-                })
-            } catch (err) {
-                console.error('RunForge Worker 启动失败:', err)
-                process.exit(1)
+            if (shouldRunLepaoWorker(this.serverRole)) {
+                try {
+                    await this.startLepaoWorker()
+                } catch (err) {
+                    console.error('RunForge Worker 启动失败:', err)
+                    process.exit(1)
+                }
+            } else {
+                this.logger.info('serverRole=api,跳过乐跑 Worker 与 Redis→MQ 调度')
             }
             }
         } catch (e) {
         } catch (e) {
             this.logger.error('❌ RabbitMQ 初始化失败')
             this.logger.error('❌ RabbitMQ 初始化失败')
@@ -102,16 +109,13 @@ class SERVER {
             const stats = fs.statSync(itemPath)
             const stats = fs.statSync(itemPath)
 
 
             if (stats.isDirectory()) {
             if (stats.isDirectory()) {
-                // 如果是目录,递归调用
                 this.loadAPIs(itemPath)
                 this.loadAPIs(itemPath)
             } else if (stats.isFile() && itemPath.endsWith('.js')) {
             } else if (stats.isFile() && itemPath.endsWith('.js')) {
-                // 如果是文件且是 JavaScript 文件
                 this.loadAPIFile(itemPath)
                 this.loadAPIFile(itemPath)
             }
             }
         })
         })
     }
     }
 
 
-    // 加载单个 API 文件
     loadAPIFile(filePath) {
     loadAPIFile(filePath) {
         try {
         try {
             const APIClass = require(filePath)
             const APIClass = require(filePath)
@@ -129,36 +133,45 @@ class SERVER {
         }
         }
     }
     }
 
 
+    listenHttp() {
+        this.app.listen(this.port, () => {
+            this.logger.info(`==========服务器正在 ${this.port} 端口上运行 (role=${this.serverRole})==========`)
+        })
+    }
+
+    async startApiServices() {
+        try {
+            await this.initAccessControlSchema()
+        } catch (err) {
+            this.logger.error(`权限模型初始化异常: ${err.message}`)
+        }
+
+        try {
+            await this.initOneBot()
+        } catch (err) {
+            this.logger.error(`OneBot 初始化异常: ${err.message}`)
+        }
+
+        this.listenHttp()
+    }
+
     start() {
     start() {
-        this.logger.info('============正在启动服务器============')
-
-        // 初始化数据库连接
-        this.initDB().then(() => {
-            this.initMQ().then(() => {
-                this.initAccessControlSchema().then(() => {
-                    this.initOneBot().then(() => {
-                        this.app.listen(this.port, () => {
-                            this.logger.info(`==========服务器正在 ${this.port} 端口上运行==========`)
-                        })
-                    }).catch(err => {
-                        this.logger.error(`OneBot 初始化异常: ${err.message}`)
-                        this.app.listen(this.port, () => {
-                            this.logger.info(`==========服务器正在 ${this.port} 端口上运行==========`)
-                        })
-                    })
-                }).catch(err => {
-                    this.logger.error(`权限模型初始化异常: ${err.message}`)
-                    this.app.listen(this.port, () => {
-                        this.logger.info(`==========服务器正在 ${this.port} 端口上运行==========`)
-                    })
-                })
-            }).catch(err => {
-                this.logger.error(`启动服务器失败: ${err.message}`)
+        this.logger.info(`============正在启动 (role=${this.serverRole})============`)
+
+        this.initDB()
+            .then(() => this.initMQ())
+            .then(async () => {
+                if (!shouldServeApi(this.serverRole)) {
+                    this.logger.info('serverRole=worker,未启动 HTTP API,进程将保持运行以消费乐跑任务')
+                    return
+                }
+                await this.startApiServices()
+            })
+            .catch((err) => {
+                this.logger.error(`启动失败: ${err.message || err}`)
+                if (err.stack) this.logger.error(err.stack)
+                process.exit(1)
             })
             })
-        }).catch(err => {
-            this.logger.error(`启动服务器失败: ${err.message}`)
-            process.exit(1) // 启动失败时退出进程
-        })
     }
     }
 }
 }
 
 

+ 29 - 0
lib/serverRole.js

@@ -0,0 +1,29 @@
+const config = require('../config.json')
+
+const VALID_ROLES = new Set(['all', 'api', 'worker'])
+
+/**
+ * 进程角色:all=API+乐跑Worker一体;api=仅HTTP入队;worker=仅消费乐跑任务
+ * 环境变量 RUNFORGE_SERVER_ROLE 优先于 config.serverRole
+ */
+function resolveServerRole() {
+    const raw = process.env.RUNFORGE_SERVER_ROLE || config.serverRole || 'all'
+    const role = String(raw).toLowerCase()
+    if (VALID_ROLES.has(role)) return role
+    console.warn(`[serverRole] 未知值 "${raw}",回退为 all`)
+    return 'all'
+}
+
+function shouldServeApi(role) {
+    return role === 'all' || role === 'api'
+}
+
+function shouldRunLepaoWorker(role) {
+    return role === 'all' || role === 'worker'
+}
+
+module.exports = {
+    resolveServerRole,
+    shouldServeApi,
+    shouldRunLepaoWorker
+}

+ 2 - 1
package.json

@@ -4,7 +4,8 @@
   "description": "",
   "description": "",
   "main": "index.js",
   "main": "index.js",
   "scripts": {
   "scripts": {
-    "start": "node index.js"
+    "start": "node index.js",
+    "start:worker": "node worker.js"
   },
   },
   "author": "thc",
   "author": "thc",
   "license": "ISC",
   "license": "ISC",

+ 10 - 0
worker.js

@@ -0,0 +1,10 @@
+/**
+ * 乐跑 Worker 专用入口:只消费 MQ 任务 + Redis 延迟调度,不启动 HTTP API。
+ * 也可在 config.json 设 "serverRole": "worker" 后直接 node index.js。
+ */
+process.env.RUNFORGE_SERVER_ROLE = process.env.RUNFORGE_SERVER_ROLE || 'worker'
+
+const SERVER = require('./lib/Server')
+
+const server = new SERVER()
+server.start()