robot.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. const file = require("./utils/file");
  2. const {getRustConfig} = require("./config");
  3. const {spawn, exec, fork, execSync} = require("child_process");
  4. const {logger} = require("./utils/logger");
  5. const http = require("./utils/http");
  6. const e = require("express");
  7. const {is, tr} = require("date-fns/locale");
  8. /*******
  9. * web
  10. * **** */
  11. const RobotStatus = Object.freeze({
  12. // 已停止的
  13. STOPPED: "STOPPED",
  14. // 正在停止
  15. STOP_PENDING: "STOP_PENDING",
  16. //正在运行
  17. RUNNING: "RUNNING",
  18. //启动中
  19. START_PENDING: "START_PENDING",
  20. //下载中
  21. DOWNLOADING: "DOWNLOADING",
  22. //错误
  23. ERROR: "ERROR"
  24. });
  25. let appMap = new Map();
  26. function getAppMap() {
  27. return appMap;
  28. }
  29. function getApp(key) {
  30. let app = {
  31. id: -1,
  32. port: -1,
  33. childProcess: undefined,
  34. nowBalance: 0,
  35. messlist: [],
  36. threadStatus: RobotStatus.STOPPED,
  37. errorMessage: "成功",
  38. threadStartTime: -1,
  39. status: 0,
  40. restartStatus: 0
  41. }
  42. // logger.info(appMap, appMap.has(key));
  43. if (appMap.has(key)) {
  44. app = appMap.get(key)
  45. } else {
  46. appMap.set(key, app)
  47. }
  48. // logger.info(app);
  49. return app
  50. }
  51. function delApp(key) {
  52. appMap.delete(key)
  53. }
  54. async function run(param) {
  55. return new Promise(async (resolve, reject) => {
  56. var key = param.id
  57. var appName = param.path
  58. var programName = param.programName
  59. var strategyName = param.strategyName
  60. var app = getApp(key)
  61. var config = getRustConfig()
  62. //检查当前机器人id 对应的as 是否已经启动,为了防止重复启动
  63. if (app.status === 1) {
  64. logger.info(`防止重复启动!结束当前请求`)
  65. return resolve(false)
  66. }
  67. app.status = 1
  68. // 初始化机器人状态
  69. app.threadStatus = RobotStatus.START_PENDING
  70. app.id = key
  71. app.port = param.callPort
  72. robotStatus(app)
  73. /****
  74. *** 第二步:路径经组装
  75. * 注意:可能存在一台服务器多个机器人,通过机器人ID创建文件夹区分,需要组装好路径
  76. ***/
  77. //系统不同 做不同的路径处理
  78. const platform = process.platform;
  79. let exeName = appName; //可执行程序
  80. // let configName = "config.toml"; //配置文件
  81. let configName = "config.json"; //配置文件
  82. let appPath = "";
  83. //相对路径存放
  84. appPath = config.filePath + "/" + app.id + "/" + strategyName + "/" + programName
  85. //1. 检查目录
  86. file.checkPathSync(appPath);
  87. // if (platform === 'win32') {//直接运行相对路径
  88. // appPath = config.winPath + "/" + app.id
  89. // } else {//打包为docker 镜像
  90. // appPath = config.linuxPath + "/" + app.id
  91. // }
  92. /****
  93. *** 第三步:rust 启动程序检查(下载更新)
  94. ***/
  95. var isDow = false
  96. var scheduleDow = 0
  97. var scheduleConfig = 0
  98. //2、 检查执行程序
  99. var destination = appPath + "/" + exeName
  100. if (!file.checkFilePath(destination)) {
  101. app.threadStatus = RobotStatus.DOWNLOADING
  102. robotStatus(app)
  103. isDow = true
  104. var dowHeaders = {...config.headers};
  105. if (platform === 'win32') {
  106. downloadFileWithPowerShell(
  107. config.baseUrl + config.dowAppURL + '/?path=' + appName,
  108. appPath + "/" + appName,
  109. dowHeaders, (err, b) => {
  110. if (err === null) {
  111. scheduleDow = 1
  112. } else {
  113. app.threadStatus = RobotStatus.ERROR
  114. app.errorMessage = '下载失败!'
  115. scheduleDow = -1
  116. robotStatus(app)
  117. }
  118. });
  119. } else {
  120. downloadFileWithLinux(
  121. config.baseUrl + config.dowAppURL + '/?path=' + appName,
  122. appPath + "/" + appName,
  123. dowHeaders, (err, b) => {
  124. if (err === null) {
  125. scheduleDow = 1
  126. } else {
  127. app.threadStatus = RobotStatus.ERROR
  128. app.errorMessage = '下载失败!'
  129. scheduleDow = -1
  130. robotStatus(app)
  131. }
  132. });
  133. }
  134. } else {
  135. scheduleDow = 1
  136. }
  137. /****
  138. *** 第四步:rust 启动配置检查(下载更新)
  139. ***/
  140. //2 为防止启动指令不同,每次重新写入
  141. var destination2 = appPath + "/" + configName
  142. var urrrl = config.baseUrl + config.dowConfigURL + '/?robotId=' + app.id
  143. var configHeaders = {...config.headers};
  144. http.request_get(urrrl, {...config.headers})
  145. .then((data) => {
  146. logger.info('配置参数:', data);
  147. const json = JSON.parse(data)
  148. // 处理成配置文件
  149. const map = json.data
  150. var json_obj = JSON.parse("{}")
  151. for (k in map) {
  152. json_obj[k] = map[k]
  153. // logger.info(map[k] + "\t")
  154. }
  155. logger.info("参数组装完成!")
  156. te = JSON.stringify(json_obj)
  157. file.writeFile(destination2, te, (errer, b) => {
  158. if (errer === null && b === true) {
  159. scheduleConfig = 1
  160. } else {
  161. logger.info("配置参数写入配置失败!", errer)
  162. app.threadStatus = RobotStatus.ERROR
  163. app.errorMessage = '配置参数写入配置失败!'
  164. scheduleConfig = -1
  165. robotStatus(app)
  166. }
  167. })
  168. })
  169. .catch((e) => {
  170. logger.info("配置参数获取失败", e)
  171. app.threadStatus = RobotStatus.ERROR
  172. app.errorMessage = '配置参数获取失败!'
  173. scheduleConfig = -1
  174. robotStatus(app)
  175. })
  176. //监听下载只有下载完成了才能继续
  177. while (true) {
  178. await delay(5000);
  179. let info_t = ""
  180. let info_t2 = ""
  181. //是否开启下载,如果是新下载,下载完成需要授权
  182. if (isDow) {
  183. if (scheduleDow === 1) {
  184. info_t = "启动文件:下载完成!"
  185. //文件授权
  186. execSync(`chmod +x ${appPath + "/" + appName}`, (error, stdout, stderr) => {
  187. if (error) {
  188. logger.error(`启动文件:授权失败: ${error}`);
  189. }
  190. logger.info(`启动文件:授权完成!`);
  191. });
  192. } else if (scheduleDow === -1) {
  193. info_t = "启动文件:下载失败!"
  194. } else {
  195. info_t = "启动文件:还在下载..."
  196. }
  197. } else {
  198. info_t = "启动文件:完整无需下载"
  199. }
  200. if (scheduleConfig === 1) {
  201. info_t2 = "配置文件:读取成功!"
  202. } else if (scheduleConfig === -1) {
  203. info_t = "配置文件:读取失败!"
  204. } else {
  205. info_t2 = "配置文件:还在读取..."
  206. }
  207. logger.info(info_t, info_t2);
  208. if (scheduleDow === 1 && scheduleConfig === 1) {
  209. break
  210. } else if (scheduleConfig === -1 || scheduleDow === -1) {
  211. return resolve(false);
  212. }
  213. }
  214. app.threadStatus = RobotStatus.START_PENDING
  215. robotStatus(app)
  216. logger.info("开始启动程序!");
  217. //3. spawn启动
  218. const exePath = appPath + "/" + exeName
  219. const configPath = appPath + "/" + configName
  220. logger.info(`文件地址:${exePath}-----${configPath}`);
  221. const command = exePath
  222. const args = ['--config=' + configPath, '--port=' + app.port]
  223. app.childProcess = spawn(command, args)
  224. app.threadStartTime = new Date().getTime()
  225. /**********监听*********/
  226. app.childProcess.stdout.on('data', (msg) => {
  227. // logger.info('stdout:' + msg.toString())
  228. })
  229. app.childProcess.on('message', (msg) => {
  230. // logger.info(`message: ${msg}`);
  231. });
  232. app.childProcess.on('exit', (code, signal) => {
  233. logger.info(`子进程退出-exit,退出码: ${code}, 信号: ${signal}`);
  234. });
  235. app.childProcess.on('close', (code) => {
  236. logger.info(`子进程退出-close,退出码 ${code}`);
  237. app.threadStatus = RobotStatus.STOPPED
  238. robotStatus(app)
  239. app.childProcess = undefined
  240. app.status = 0
  241. app.nowBalance = 0
  242. });
  243. app.threadStatus = RobotStatus.RUNNING
  244. return resolve(true)
  245. })
  246. }
  247. function delay(ms) {
  248. return new Promise(resolve => setTimeout(resolve, ms));
  249. }
  250. async function closeApp(param) {
  251. return new Promise((resolve, reject) => {
  252. var key = param.id
  253. var app = getApp(key)
  254. logger.info(` 信号: `, app.threadStatus);
  255. /*******新的删除方式*************/
  256. //文件授权
  257. if (app.childProcess !== undefined) {
  258. var pid = app.childProcess.pid
  259. exec(`sudo kill ${pid}`, (error, stdout, stderr) => {
  260. if (error) {
  261. logger.error(`进程${pid} 杀死失败: ${error}`);
  262. }
  263. logger.info(`进程${pid} 杀死成功`);
  264. logger.info(`当前app:`, app);
  265. app.threadStatus = RobotStatus.STOP_PENDING
  266. robotStatus(app)
  267. });
  268. }
  269. return resolve(true)
  270. })
  271. /*******新的删除方式*************/
  272. }
  273. //重启 RESTART
  274. async function restartApp(param) {
  275. var key = param.id
  276. var app = getApp(key)
  277. var restartStatus = app.restartStatus
  278. if (restartStatus !== 0) {
  279. logger.info("防止重启指令 重复发送~!")
  280. return
  281. }
  282. app.restartStatus = 1
  283. logger.info("--开始重启!")
  284. logger.info('当前app', app.id, app.threadStatus);
  285. const closeResult = await closeApp(param)
  286. // logger.info('?', JSON.stringify(closeResult))
  287. while (true) {
  288. await delay(1000)
  289. const runResult = await run(param)
  290. // logger.info('??', JSON.stringify(runResult))
  291. if (runResult) break
  292. }
  293. app.restartStatus = 0
  294. logger.info(`重启完成!!!!!`);
  295. }
  296. async function closeAppAll() {
  297. return new Promise(async (resolve, reject) => {
  298. var appMap = getAppMap()
  299. // console.log(appMap.size); // 输出Map的大小
  300. appMap.forEach((value, key) => {
  301. console.log(key, value); // 输出Map的键值对
  302. // logger.info("???", key, value)
  303. var app = value
  304. logger.info(` 信号: `, app.threadStatus);
  305. /*******新的删除方式*************/
  306. //文件授权
  307. if (app.childProcess !== undefined) {
  308. var pid = app.childProcess.pid
  309. exec(`sudo kill ${pid}`, (error, stdout, stderr) => {
  310. if (error) {
  311. logger.error(`进程${pid} 杀死失败: ${error}`);
  312. }
  313. logger.info(`进程${pid} 杀死成功`);
  314. // logger.info(`当前app:`, app);
  315. app.threadStatus = RobotStatus.STOP_PENDING
  316. robotStatus(app)
  317. });
  318. }
  319. });
  320. while (true) {
  321. var z = 0;
  322. await delay(1000)
  323. var str = "";
  324. appMap.forEach((value, key) => {
  325. str += "机器人:" + key + ",当前状态:" + value.threadStatus + "\t"
  326. if (value.threadStatus !== RobotStatus.STOPPED) {
  327. z += 1
  328. }
  329. });
  330. logger.info(str)
  331. if (z === 0) {
  332. logger.info(`策略全部关闭~~开始关闭node`);
  333. break
  334. } else {
  335. logger.info(`等待关闭~~策略!`);
  336. }
  337. }
  338. return resolve(true)
  339. })
  340. /*******新的删除方式*************/
  341. }
  342. // 下载执行程序,使用的实行服务器指令的方式进行下载
  343. function downloadFileWithPowerShell(url, destination, headers, funBreak) {
  344. const headersString = Object.entries(headers)
  345. .map(([key, value]) => `'${key}'='${value}'`) // 使用单引号来确保特殊字符不被解析
  346. .join('; '); // 使用分号和空格分隔每个键值对
  347. const command = `powershell -command "Invoke-WebRequest -Uri '${url}' -OutFile '${destination}' -Headers @{${headersString}}"`;
  348. exec(command, (error, stdout, stderr) => {
  349. if (error) {
  350. logger.error(`下载出错: ${error}`);
  351. return funBreak(error);
  352. }
  353. logger.info(`下载完成!`);
  354. funBreak(null, true); // 通知下载成功,修改了B为true
  355. });
  356. }
  357. function downloadFileWithLinux(url, destination, headers, funBreak) {
  358. const headersString = Object.entries(headers)
  359. .map(([key, value]) => `-H '${key}: ${value}'`) // 设置curl的HTTP头参数
  360. .join(' '); // 使用空格分隔每个头参数
  361. const command = `curl ${headersString} '${url}' -o '${destination}'`;
  362. exec(command, (error, stdout, stderr) => {
  363. if (error) {
  364. logger.error(`下载出错: ${error}`);
  365. return funBreak(error);
  366. }
  367. logger.info(`下载完成!`);
  368. funBreak(null, true); // 通知下载成功,修改了B为true
  369. });
  370. }
  371. // 上报-机器人状态
  372. function robotStatus(app) {
  373. var config = getRustConfig()
  374. var msg = (app.threadStatus !== RobotStatus.ERROR ? "完成" : app.errorMessage)
  375. http.request_post(`${config.baseUrl}/report/statusReport`, {
  376. "robotId": app.id,
  377. "status": app.threadStatus,
  378. "msg": msg
  379. }, {...config.headers}).then((data) => {
  380. // logger.info('??', data);
  381. logger.info('#####################汇报:状态:', '机器人id:', app.id, '状态:', app.threadStatus);
  382. }).catch((error) => {
  383. logger.error(`请求遇到问题1: ${error.message}`); // 处理可能发生的错误
  384. });
  385. }
  386. // 上报-余额
  387. function robotAmount(app) {
  388. var accUrl = "http://127.0.0.1:" + app.port
  389. //拿到策略余额
  390. try {
  391. var config = getRustConfig()
  392. http.request_get(`${accUrl}/account`, {...config.headers})
  393. .then((data) => {
  394. var d = JSON.parse(data)
  395. //余额有变动上报一次
  396. // logger.info(`余额当前:${(app.nowBalance + "")}--${(d.now_balance + "")}--`);
  397. if ((app.nowBalance + "") !== (d.now_balance + "")) {
  398. http.request_post(`${config.baseUrl}/report/amountReport`, {
  399. "robotId": app.id,
  400. "amount": d.now_balance
  401. }, {...config.headers})
  402. .then((data2) => {
  403. logger.info('上报响应', data2);
  404. logger.info('#####################汇报:余额:pid:', app.childProcess.pid, '机器人id:', app.id, '机器人本地余额:', app.nowBalance, '实际余额:', d.now_balance);
  405. app.nowBalance = d.now_balance
  406. }).catch((error) => {
  407. logger.error(`#####################汇报:余额上报错误: ${error.message}`); // 处理可能发生的错误
  408. });
  409. }
  410. }).catch((error) => {
  411. logger.error(`#####################汇报:余额接口请求失败: ${error.message}`); // 处理可能发生的错误
  412. });
  413. } catch (e) {
  414. logger.error('请求失败!:', e)
  415. }
  416. }
  417. // 定时清理停止状态的 机器人
  418. function delRobot(app) {
  419. // const platform = process.platform;
  420. // if (platform !== 'win32' && app.threadStatus === RobotStatus.STOPPED) {
  421. // var pid = app.childProcess.pid
  422. // delApp(app.id)
  423. // }
  424. }
  425. module.exports = {
  426. run, closeApp, restartApp, delRobot, robotStatus, robotAmount, closeAppAll, appMap, getApp, getAppMap, RobotStatus
  427. };