robot.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  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. // var isOK = false
  294. // var killCount = 0;
  295. // if (app.id === -1 || (app.childProcess === undefined && app.status === 0)) {
  296. // isOK = true;
  297. // } else {
  298. // var pid = app.childProcess.pid
  299. // logger.info(`当前pid:${pid}`);
  300. // //1 通过指令查询是否存在
  301. // var pidIsOK = false
  302. // exec(`sudo ps -p ${pid} > /dev/null && echo "true" || echo "false"`, (error, stdout, stderr) => {
  303. // if (error) {
  304. // logger.error(`进程${pid} 查询失败: ${error}`);
  305. // return
  306. // }
  307. // logger.info(`进程查询结果:${stdout}`);
  308. // var z = `${stdout}`
  309. // if (z === "true") {
  310. // pidIsOK = true
  311. // }
  312. // });
  313. //
  314. // if (pidIsOK) {
  315. // if (killCount === 0) {
  316. // killCount = 1
  317. // exec(`sudo kill ${pid}`, (error, stdout, stderr) => {
  318. // if (error) {
  319. // logger.error(`进程${pid} 杀死失败: ${error}`);
  320. // }
  321. // logger.info(`进程${pid} 杀死成功`);
  322. // logger.info(`当前app:`, app);
  323. // app.threadStatus = RobotStatus.STOP_PENDING
  324. // robotStatus(app)
  325. // isOK = true
  326. // });
  327. // } else {
  328. // logger.info(`灭杀指令已发送..等待结果!`);
  329. // }
  330. // }else{
  331. // isOK = true
  332. // }
  333. // }
  334. //
  335. // while (true) {
  336. // await delay(2000);
  337. // if(isOK){
  338. // break
  339. // }else{
  340. // logger.info("----等待~~~")
  341. // }
  342. // }
  343. // //开始重启
  344. // var killCount = 0;
  345. // var waitForCondition = function (intervalDuration, conditionFunction) {
  346. // return new Promise((resolve, reject) => {
  347. // const intervalId = setInterval(() => {
  348. // if (conditionFunction()) {
  349. // clearInterval(intervalId);
  350. // resolve();
  351. // }
  352. // }, intervalDuration);
  353. // });
  354. // }
  355. //
  356. //
  357. // // 当这个条件变为true时,会结束等待
  358. // await waitForCondition(1000, () => {
  359. // logger.info('当前app', app.id, app.threadStatus);
  360. // var isOK = false
  361. // if (app.id === -1 || (app.childProcess === undefined && app.status === 0)) {
  362. // isOK = true;
  363. // } else {
  364. // var pid = app.childProcess.pid
  365. // logger.info(`当前pid:${pid}`);
  366. // //1 通过指令查询是否存在
  367. // var pidIsOK = false
  368. // execSync(`sudo ps -p ${pid} > /dev/null && echo "true" || echo "false"`, (error, stdout, stderr) => {
  369. // if (error) {
  370. // logger.error(`进程${pid} 查询失败: ${error}`);
  371. // return
  372. // }
  373. // logger.info(`进程查询结果:${stdout}`);
  374. // if (stdout === "true") {
  375. // pidIsOK = true
  376. // }
  377. // });
  378. //
  379. // if (pidIsOK) {
  380. // if (killCount === 0) {
  381. // killCount = 1
  382. // execSync(`sudo kill ${pid}`, (error, stdout, stderr) => {
  383. // if (error) {
  384. // logger.error(`进程${pid} 杀死失败: ${error}`);
  385. // }
  386. // logger.info(`进程${pid} 杀死成功`);
  387. // logger.info(`当前app:`, app);
  388. // app.threadStatus = RobotStatus.STOP_PENDING
  389. // robotStatus(app)
  390. // isOK = true
  391. // });
  392. // } else {
  393. // logger.info(`灭杀指令已发送..等待结果!`);
  394. // }
  395. // }else{
  396. // isOK = true
  397. // }
  398. // }
  399. //
  400. // if (isOK) {
  401. // return true
  402. // }
  403. // return false
  404. // });
  405. //启动
  406. // run(param)
  407. app.restartStatus = 0
  408. logger.info(`重启完成!!!!!`);
  409. }
  410. async function closeAppAll() {
  411. return new Promise(async (resolve, reject) => {
  412. var appMap = getAppMap()
  413. // console.log(appMap.size); // 输出Map的大小
  414. appMap.forEach((value, key) => {
  415. console.log(key, value); // 输出Map的键值对
  416. // logger.info("???", key, value)
  417. var app = value
  418. logger.info(` 信号: `, app.threadStatus);
  419. /*******新的删除方式*************/
  420. //文件授权
  421. if (app.childProcess !== undefined) {
  422. var pid = app.childProcess.pid
  423. exec(`sudo kill ${pid}`, (error, stdout, stderr) => {
  424. if (error) {
  425. logger.error(`进程${pid} 杀死失败: ${error}`);
  426. }
  427. logger.info(`进程${pid} 杀死成功`);
  428. // logger.info(`当前app:`, app);
  429. app.threadStatus = RobotStatus.STOP_PENDING
  430. robotStatus(app)
  431. });
  432. }
  433. });
  434. while (true) {
  435. var z = 0;
  436. await delay(1000)
  437. var str = "";
  438. appMap.forEach((value, key) => {
  439. str += "机器人:" + key + ",当前状态:" + value.threadStatus + "\t"
  440. if (value.threadStatus !== RobotStatus.STOPPED) {
  441. z += 1
  442. }
  443. });
  444. logger.info(str)
  445. if (z === 0) {
  446. logger.info(`策略全部关闭~~开始关闭node`);
  447. break
  448. } else {
  449. logger.info(`等待关闭~~策略!`);
  450. }
  451. }
  452. return resolve(true)
  453. })
  454. /*******新的删除方式*************/
  455. }
  456. // 下载执行程序,使用的实行服务器指令的方式进行下载
  457. function downloadFileWithPowerShell(url, destination, headers, funBreak) {
  458. const headersString = Object.entries(headers)
  459. .map(([key, value]) => `'${key}'='${value}'`) // 使用单引号来确保特殊字符不被解析
  460. .join('; '); // 使用分号和空格分隔每个键值对
  461. const command = `powershell -command "Invoke-WebRequest -Uri '${url}' -OutFile '${destination}' -Headers @{${headersString}}"`;
  462. exec(command, (error, stdout, stderr) => {
  463. if (error) {
  464. logger.error(`下载出错: ${error}`);
  465. return funBreak(error);
  466. }
  467. logger.info(`下载完成!`);
  468. funBreak(null, true); // 通知下载成功,修改了B为true
  469. });
  470. }
  471. function downloadFileWithLinux(url, destination, headers, funBreak) {
  472. const headersString = Object.entries(headers)
  473. .map(([key, value]) => `-H '${key}: ${value}'`) // 设置curl的HTTP头参数
  474. .join(' '); // 使用空格分隔每个头参数
  475. const command = `curl ${headersString} '${url}' -o '${destination}'`;
  476. exec(command, (error, stdout, stderr) => {
  477. if (error) {
  478. logger.error(`下载出错: ${error}`);
  479. return funBreak(error);
  480. }
  481. logger.info(`下载完成!`);
  482. funBreak(null, true); // 通知下载成功,修改了B为true
  483. });
  484. }
  485. // 上报-机器人状态
  486. function robotStatus(app) {
  487. var config = getRustConfig()
  488. var msg = (app.threadStatus !== RobotStatus.ERROR ? "完成" : app.errorMessage)
  489. http.request_post(`${config.baseUrl}/report/statusReport`, {
  490. "robotId": app.id,
  491. "status": app.threadStatus,
  492. "msg": msg
  493. }, {...config.headers}).then((data) => {
  494. // logger.info('??', data);
  495. logger.info('#####################汇报:状态:', '机器人id:', app.id, '状态:', app.threadStatus);
  496. }).catch((error) => {
  497. logger.error(`请求遇到问题1: ${error.message}`); // 处理可能发生的错误
  498. });
  499. }
  500. // 上报-余额
  501. function robotAmount(app) {
  502. var accUrl = "http://127.0.0.1:" + app.port
  503. //拿到策略余额
  504. try {
  505. var config = getRustConfig()
  506. http.request_get(`${accUrl}/account`, {...config.headers})
  507. .then((data) => {
  508. var d = JSON.parse(data)
  509. //余额有变动上报一次
  510. // logger.info(`余额当前:${(app.nowBalance + "")}--${(d.now_balance + "")}--`);
  511. if ((app.nowBalance + "") !== (d.now_balance + "")) {
  512. http.request_post(`${config.baseUrl}/report/amountReport`, {
  513. "robotId": app.id,
  514. "amount": d.now_balance
  515. }, {...config.headers})
  516. .then((data2) => {
  517. logger.info('上报响应', data2);
  518. logger.info('#####################汇报:余额:pid:', app.childProcess.pid, '机器人id:', app.id, '机器人本地余额:', app.nowBalance, '实际余额:', d.now_balance);
  519. app.nowBalance = d.now_balance
  520. }).catch((error) => {
  521. logger.error(`#####################汇报:余额上报错误: ${error.message}`); // 处理可能发生的错误
  522. });
  523. }
  524. }).catch((error) => {
  525. logger.error(`#####################汇报:余额接口请求失败: ${error.message}`); // 处理可能发生的错误
  526. });
  527. } catch (e) {
  528. logger.error('请求失败!:', e)
  529. }
  530. }
  531. // 定时清理停止状态的 机器人
  532. function delRobot(app) {
  533. // const platform = process.platform;
  534. // if (platform !== 'win32' && app.threadStatus === RobotStatus.STOPPED) {
  535. // var pid = app.childProcess.pid
  536. // delApp(app.id)
  537. // }
  538. }
  539. module.exports = {
  540. run, closeApp, restartApp, delRobot, robotStatus, robotAmount, closeAppAll, appMap, getApp, getAppMap, RobotStatus
  541. };