Server.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. const express = require('express')
  2. const cors = require('cors')
  3. const path = require('path')
  4. const fs = require('fs')
  5. const config = require('../config.json')
  6. const Logger = require('./Logger')
  7. const MySQL = require('../plugin/DataBase/MySQL')
  8. const Worker = require('./Lepao/Worker')
  9. const mq = require('../plugin/mq')
  10. const { mq: mqName } = require('../plugin/mq/mqPrefix')
  11. const { startLepaoSchedulePublisher } = require('../plugin/mq/lepaoSchedulePublisher')
  12. const OneBotV11 = require('../plugin/OneBot/OneBotV11')
  13. const AccessControl = require('./AccessControl')
  14. const {
  15. resolveServerRole,
  16. shouldServeApi,
  17. shouldRunLepaoWorker
  18. } = require('./serverRole')
  19. class SERVER {
  20. constructor() {
  21. this.serverRole = resolveServerRole()
  22. this.port = config.port || 3000
  23. this.apiDirectory = path.join(__dirname, '../apis')
  24. const logName = this.serverRole === 'worker' ? 'WorkerServer.log' : 'Server.log'
  25. this.logger = new Logger(path.join(__dirname, '../logs', logName), 'INFO')
  26. this.db = new MySQL()
  27. if (shouldServeApi(this.serverRole)) {
  28. this.app = express()
  29. this.app.use(express.json())
  30. this.app.use(cors())
  31. this.app.use('/uploads', express.static('./uploads'))
  32. this.app.use('/models', express.static('./models'))
  33. this.loadAPIs(this.apiDirectory)
  34. }
  35. }
  36. async initDB() {
  37. try {
  38. this.logger.info('正在测试数据库连接')
  39. await this.db.connect()
  40. await this.db.close()
  41. } catch (error) {
  42. this.logger.error(`数据库连接失败: ${error.stack}`)
  43. process.exit(1)
  44. }
  45. }
  46. async startLepaoWorker() {
  47. const worker = new Worker()
  48. await worker.start()
  49. this.logger.info('RunForge Worker 已启动,正在监听 MQ 任务...')
  50. startLepaoSchedulePublisher({
  51. logger: this.logger,
  52. intervalMs: config.rabbitmq?.lepaoScheduleTickMs ?? 2000,
  53. batch: config.rabbitmq?.lepaoScheduleBatch ?? 100
  54. })
  55. }
  56. async initMQ() {
  57. try {
  58. await mq.init()
  59. const ch = await mq.getChannel('health')
  60. await ch.assertQueue(mqName('mq_health_check'), { durable: false })
  61. this.logger.info('✅ RabbitMQ 初始化 & 测试成功')
  62. if (shouldRunLepaoWorker(this.serverRole)) {
  63. try {
  64. await this.startLepaoWorker()
  65. } catch (err) {
  66. console.error('RunForge Worker 启动失败:', err)
  67. process.exit(1)
  68. }
  69. } else {
  70. this.logger.info('serverRole=api,跳过乐跑 Worker 与 Redis→MQ 调度')
  71. }
  72. } catch (e) {
  73. this.logger.error('❌ RabbitMQ 初始化失败')
  74. process.exit(1)
  75. }
  76. }
  77. async initOneBot() {
  78. try {
  79. const ok = await OneBotV11.initOneBotWs()
  80. if (ok) {
  81. this.logger.info('OneBot v11 ws 初始化成功,已开始监听消息')
  82. } else {
  83. this.logger.info('OneBot v11 ws 未初始化(可能未启用)')
  84. }
  85. } catch (err) {
  86. this.logger.error(`OneBot v11 ws 初始化失败: ${err.message}`)
  87. }
  88. }
  89. async initAccessControlSchema() {
  90. await AccessControl.ensurePermissionSchema()
  91. }
  92. loadAPIs(directory) {
  93. const items = fs.readdirSync(directory)
  94. items.forEach(item => {
  95. const itemPath = path.join(directory, item)
  96. const stats = fs.statSync(itemPath)
  97. if (stats.isDirectory()) {
  98. this.loadAPIs(itemPath)
  99. } else if (stats.isFile() && itemPath.endsWith('.js')) {
  100. this.loadAPIFile(itemPath)
  101. }
  102. })
  103. }
  104. loadAPIFile(filePath) {
  105. try {
  106. const APIClass = require(filePath)
  107. for (const key in APIClass) {
  108. if (APIClass.hasOwnProperty(key)) {
  109. const apiInstance = new APIClass[key]()
  110. apiInstance.setupRoute()
  111. this.app.use('/', apiInstance.getRouter())
  112. this.logger.info(`已加载API:${apiInstance.path} 类型:${apiInstance.method}`)
  113. }
  114. }
  115. } catch (error) {
  116. this.logger.error(`加载API文件失败: ${filePath},错误: ${error.stack}`)
  117. }
  118. }
  119. listenHttp() {
  120. this.app.listen(this.port, () => {
  121. this.logger.info(`==========服务器正在 ${this.port} 端口上运行 (role=${this.serverRole})==========`)
  122. })
  123. }
  124. async startApiServices() {
  125. try {
  126. await this.initAccessControlSchema()
  127. } catch (err) {
  128. this.logger.error(`权限模型初始化异常: ${err.message}`)
  129. }
  130. try {
  131. await this.initOneBot()
  132. } catch (err) {
  133. this.logger.error(`OneBot 初始化异常: ${err.message}`)
  134. }
  135. this.listenHttp()
  136. }
  137. start() {
  138. this.logger.info(`============正在启动 (role=${this.serverRole})============`)
  139. this.initDB()
  140. .then(() => this.initMQ())
  141. .then(async () => {
  142. if (!shouldServeApi(this.serverRole)) {
  143. this.logger.info('serverRole=worker,未启动 HTTP API,进程将保持运行以消费乐跑任务')
  144. return
  145. }
  146. await this.startApiServices()
  147. })
  148. .catch((err) => {
  149. this.logger.error(`启动失败: ${err.message || err}`)
  150. if (err.stack) this.logger.error(err.stack)
  151. process.exit(1)
  152. })
  153. }
  154. }
  155. module.exports = SERVER