robot.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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. path: "",
  34. programName: "",
  35. strategyName: "",
  36. childProcess: undefined,
  37. nowBalance: -1,
  38. posNum: -1,
  39. messlist: [],
  40. threadStatus: RobotStatus.STOPPED,
  41. errorMessage: "成功",
  42. threadStartTime: -1,
  43. status: 0,
  44. restartStatus: 0,
  45. closeTime: 0,
  46. isClearance: false,
  47. }
  48. // logger.info(appMap, appMap.has(key));
  49. if (appMap.has(key)) {
  50. app = appMap.get(key)
  51. } else {
  52. appMap.set(key, app)
  53. }
  54. // logger.info(app);
  55. return app
  56. }
  57. function delApp(key) {
  58. appMap.delete(key)
  59. }
  60. async function run(param) {
  61. return new Promise(async (resolve, reject) => {
  62. var key = param.id
  63. var appName = param.path
  64. let newAppName = "4l_as_" + appName;
  65. var programName = param.programName
  66. var strategyName = param.strategyName
  67. var app = getApp(key)
  68. var config = getRustConfig()
  69. //检查当前机器人id 对应的as 是否已经启动,为了防止重复启动
  70. if (app.status === 1) {
  71. logger.info(`防止重复启动!结束当前请求`)
  72. return resolve(false)
  73. }
  74. app.status = 1
  75. // 初始化机器人状态
  76. app.threadStatus = RobotStatus.START_PENDING
  77. app.id = key
  78. app.port = param.callPort
  79. app.path = param.path
  80. app.programName = param.programName
  81. app.strategyName = param.strategyName
  82. app.isClearance = false
  83. robotStatus(app)
  84. /****
  85. *** 第二步:路径经组装
  86. * 注意:可能存在一台服务器多个机器人,通过机器人ID创建文件夹区分,需要组装好路径
  87. ***/
  88. //系统不同 做不同的路径处理
  89. const platform = process.platform;
  90. let exeName = newAppName; //可执行程序
  91. let configName = "config.json"; //配置文件
  92. let appPath = "";
  93. //相对路径存放
  94. appPath = config.filePath + "/" + app.id + "/" + strategyName + "/" + programName
  95. //1. 检查目录
  96. file.checkPathSync(appPath);
  97. /****
  98. *** 第三步:rust 启动程序检查(下载更新)
  99. ***/
  100. var isDow = false
  101. var scheduleDow = 0
  102. var scheduleConfig = 0
  103. //2、 检查执行程序
  104. var destination = appPath + "/" + exeName
  105. if (!file.checkFilePath(destination)) {
  106. app.threadStatus = RobotStatus.DOWNLOADING
  107. robotStatus(app)
  108. isDow = true
  109. var dowHeaders = {...config.headers};
  110. if (platform === 'win32') {
  111. downloadFileWithPowerShell(
  112. config.baseUrl + config.dowAppURL + '/?path=' + appName,
  113. appPath + "/" + appName,
  114. dowHeaders, (err, b) => {
  115. if (err === null) {
  116. scheduleDow = 1
  117. } else {
  118. app.threadStatus = RobotStatus.ERROR
  119. app.errorMessage = '下载失败!'
  120. scheduleDow = -1
  121. robotStatus(app)
  122. }
  123. });
  124. } else {
  125. downloadFileWithLinux(
  126. config.baseUrl + config.dowAppURL + '/?path=' + appName,
  127. appPath + "/" + appName,
  128. dowHeaders, (err, b) => {
  129. if (err === null) {
  130. scheduleDow = 1
  131. } else {
  132. app.threadStatus = RobotStatus.ERROR
  133. app.errorMessage = '下载失败!'
  134. scheduleDow = -1
  135. robotStatus(app)
  136. }
  137. });
  138. }
  139. } else {
  140. scheduleDow = 1
  141. }
  142. /****
  143. *** 第四步:rust 启动配置检查(下载更新)
  144. ***/
  145. //2 为防止启动指令不同,每次重新写入
  146. var destination2 = appPath + "/" + configName
  147. var urrrl = config.baseUrl + config.dowConfigURL + '/?robotId=' + app.id
  148. var configHeaders = {...config.headers};
  149. http.request_get(urrrl, {...config.headers})
  150. .then((data) => {
  151. logger.info('配置参数:', data);
  152. const json = JSON.parse(data)
  153. // 处理成配置文件
  154. const map = json.data
  155. var json_obj = JSON.parse("{}")
  156. for (k in map) {
  157. json_obj[k] = map[k]
  158. // logger.info(map[k] + "\t")
  159. }
  160. logger.info("参数组装完成!")
  161. te = JSON.stringify(json_obj)
  162. file.writeFile(destination2, te, (errer, b) => {
  163. if (errer === null && b === true) {
  164. scheduleConfig = 1
  165. } else {
  166. logger.info("配置参数写入配置失败!", errer)
  167. app.threadStatus = RobotStatus.ERROR
  168. app.errorMessage = '配置参数写入配置失败!'
  169. scheduleConfig = -1
  170. robotStatus(app)
  171. }
  172. })
  173. })
  174. .catch((e) => {
  175. logger.info("配置参数获取失败", e)
  176. app.threadStatus = RobotStatus.ERROR
  177. app.errorMessage = '配置参数获取失败!'
  178. scheduleConfig = -1
  179. robotStatus(app)
  180. })
  181. //监听下载只有下载完成了才能继续
  182. while (true) {
  183. await delay(5000);
  184. let info_t = ""
  185. let info_t2 = ""
  186. //是否开启下载,如果是新下载,下载完成需要授权
  187. if (isDow) {
  188. if (scheduleDow === 1) {
  189. info_t = "启动文件:下载完成!"
  190. //文件下载好了,设置统一前缀
  191. execSync(`sudo mv ${appPath + "/" + appName} ${appPath + "/" + newAppName}`, (error, stdout, stderr) => {
  192. if (error) {
  193. logger.error(`文件重命名失败: ${error}`);
  194. }
  195. logger.info(`文件重命名完成!`);
  196. });
  197. //文件授权
  198. execSync(`chmod +x ${appPath + "/" + newAppName}`, (error, stdout, stderr) => {
  199. if (error) {
  200. logger.error(`启动文件:授权失败: ${error}`);
  201. }
  202. logger.info(`启动文件:授权完成!`);
  203. });
  204. } else if (scheduleDow === -1) {
  205. info_t = "启动文件:下载失败!"
  206. } else {
  207. info_t = "启动文件:还在下载..."
  208. }
  209. } else {
  210. info_t = "启动文件:完整无需下载"
  211. }
  212. if (scheduleConfig === 1) {
  213. info_t2 = "配置文件:读取成功!"
  214. } else if (scheduleConfig === -1) {
  215. info_t = "配置文件:读取失败!"
  216. } else {
  217. info_t2 = "配置文件:还在读取..."
  218. }
  219. logger.info(info_t, info_t2);
  220. if (scheduleDow === 1 && scheduleConfig === 1) {
  221. break
  222. } else if (scheduleConfig === -1 || scheduleDow === -1) {
  223. return resolve(false);
  224. }
  225. }
  226. app.threadStatus = RobotStatus.START_PENDING
  227. robotStatus(app)
  228. logger.info("开始启动程序!");
  229. //3. spawn启动
  230. const exePath = appPath + "/" + exeName
  231. const configPath = appPath + "/" + configName
  232. logger.info(`文件地址:${exePath}-----${configPath}`);
  233. const command = exePath
  234. const args = ['--config=' + configPath, '--port=' + app.port]
  235. app.childProcess = spawn(command, args)
  236. app.threadStartTime = new Date().getTime()
  237. /**********监听*********/
  238. app.childProcess.stdout.on('data', (msg) => {
  239. // logger.info('stdout:' + msg.toString())
  240. })
  241. app.childProcess.on('message', (msg) => {
  242. // logger.info(`message: ${msg}`);
  243. });
  244. app.childProcess.on('error', (err) => {
  245. logger.error('子程序-异常:', err);
  246. app.threadStatus = RobotStatus.ERROR
  247. app.errorMessage = `子线程发生异常!! ${err}`
  248. robotStatus(app)
  249. app.childProcess = undefined
  250. app.status = 0
  251. app.closeTime = new Date().getTime()
  252. });
  253. app.childProcess.on('exit', (code, signal) => {
  254. logger.info(`子进程退出-exit,退出码: ${code}, 信号: ${signal}`);
  255. app.threadStatus = RobotStatus.STOPPED
  256. robotStatus(app)
  257. app.childProcess = undefined
  258. app.status = 0
  259. app.closeTime = new Date().getTime()
  260. });
  261. app.childProcess.on('close', (code) => {
  262. logger.info(`子进程退出-close,退出码 ${code}`);
  263. app.threadStatus = RobotStatus.STOPPED
  264. robotStatus(app)
  265. app.childProcess = undefined
  266. app.status = 0
  267. app.closeTime = new Date().getTime()
  268. });
  269. app.threadStatus = RobotStatus.RUNNING
  270. robotStatus(app)
  271. return resolve(true)
  272. })
  273. }
  274. function delay(ms) {
  275. return new Promise(resolve => setTimeout(resolve, ms));
  276. }
  277. async function closeApp(param) {
  278. return new Promise((resolve, reject) => {
  279. var key = param.id
  280. var app = getApp(key)
  281. logger.info(` 信号: `, app.threadStatus);
  282. /*******新的删除方式*************/
  283. //文件授权
  284. if (app.childProcess !== undefined) {
  285. var pid = app.childProcess.pid
  286. exec(`sudo kill ${pid}`, (error, stdout, stderr) => {
  287. if (error) {
  288. logger.error(`进程${pid} 杀死失败: ${error}`);
  289. }
  290. logger.info(`进程${pid} 杀死成功`);
  291. logger.info(`当前app:`, app);
  292. app.threadStatus = RobotStatus.STOP_PENDING
  293. app.isClearance = false
  294. robotStatus(app)
  295. });
  296. }
  297. return resolve(true)
  298. })
  299. /*******新的删除方式*************/
  300. }
  301. //重启 RESTART
  302. async function restartApp(param) {
  303. var key = param.id
  304. var app = getApp(key)
  305. // var restartStatus = app.restartStatus
  306. // if (restartStatus !== 0) {
  307. // logger.info("防止重启指令 重复发送~!")
  308. // return
  309. // }
  310. // app.restartStatus = 1
  311. logger.info("--开始重启!")
  312. logger.info('当前app', app.id, app.threadStatus);
  313. const closeResult = await closeApp(param)
  314. // logger.info('?', JSON.stringify(closeResult))
  315. while (true) {
  316. await delay(1000)
  317. const runResult = await run(param)
  318. // logger.info('??', JSON.stringify(runResult))
  319. if (runResult) break
  320. }
  321. // app.restartStatus = 0
  322. logger.info(`重启完成!!!!!`);
  323. }
  324. //启动- 检查程序
  325. async function searchPositions(param) {
  326. return new Promise(async (resolve, reject) => {
  327. // {"id":"375","callPort":"1111","path":"80654e330ed67df2880231a67925e984as-rust","programName":"v3.3.2_clear","strategyName":"as","checkId":"467"}
  328. var key = param.id
  329. var appName = param.path
  330. var r_id = param.id
  331. let newAppName = "4l_as_" + appName;
  332. let configName = "config.json"; //配置文件
  333. var programName = param.programName
  334. var strategyName = param.strategyName
  335. var config = getRustConfig()
  336. var app = getApp(key)
  337. if (app.port === -1) {
  338. } else {
  339. }
  340. //相对路径存放
  341. var appPath = config.filePath + "/" + param.id + "/" + strategyName + "/" + programName
  342. const asName = appPath + "/" + newAppName
  343. const configPath = appPath + "/" + configName
  344. const appPort = param.callPort
  345. const command = `sudo ${asName} --config=${configPath} --port=${appPort} --run_mode=1 --r_id=${r_id}`
  346. exec(command, (error, stdout, stderr) => {
  347. if (error) {
  348. logger.error(`命令执行失败~: ${command} 错误:${error}`);
  349. }
  350. logger.info(`检查指令发送成功!`);
  351. logger.info(command);
  352. });
  353. return resolve(true)
  354. })
  355. }
  356. async function closeAppAll() {
  357. return new Promise(async (resolve, reject) => {
  358. var appMap = getAppMap()
  359. // console.log(appMap.size); // 输出Map的大小
  360. // appMap.forEach((value, key) => {
  361. // console.log(key, value); // 输出Map的键值对
  362. // // logger.info("???", key, value)
  363. // var app = value
  364. // logger.info(` 信号: `, app.threadStatus);
  365. // /*******新的删除方式*************/
  366. // //文件授权
  367. // if (app.childProcess !== undefined) {
  368. // var pid = app.childProcess.pid
  369. // exec(`sudo kill ${pid}`, (error, stdout, stderr) => {
  370. // if (error) {
  371. // logger.error(`进程${pid} 杀死失败: ${error}`);
  372. // }
  373. // logger.info(`进程${pid} 杀死成功`);
  374. // // logger.info(`当前app:`, app);
  375. // app.threadStatus = RobotStatus.STOP_PENDING
  376. // robotStatus(app)
  377. // });
  378. // }
  379. // });
  380. exec(`sudo pkill 4l_as`, (error, stdout, stderr) => {
  381. if (error) {
  382. logger.error(`4l_as杀死失败: ${error}`);
  383. }
  384. logger.info(`4l_as 杀死成功`);
  385. });
  386. while (true) {
  387. var z = 0;
  388. await delay(1000)
  389. var str = "";
  390. appMap.forEach((value, key) => {
  391. str += "机器人:" + key + ",当前状态:" + value.threadStatus + "\t"
  392. if (value.threadStatus !== RobotStatus.STOPPED) {
  393. z += 1
  394. }
  395. });
  396. logger.info(str)
  397. if (z === 0) {
  398. logger.info(`策略全部关闭~~开始关闭node`);
  399. break
  400. } else {
  401. logger.info(`等待关闭~~策略!`);
  402. }
  403. }
  404. //为了确保as
  405. // while (true) {
  406. // execSync(`sudo kill $(pgrep -f "^.*\/4l_as_")`, (error, stdout, stderr) => {
  407. // if (error) {
  408. // logger.error(`全杀-进程执行失败: ${error}`);
  409. // }
  410. // logger.info(`全杀-进程执行成功`);
  411. // break
  412. // });
  413. // }
  414. return resolve(true)
  415. })
  416. /*******新的删除方式*************/
  417. }
  418. // 下载执行程序,使用的实行服务器指令的方式进行下载
  419. function downloadFileWithPowerShell(url, destination, headers, funBreak) {
  420. const headersString = Object.entries(headers)
  421. .map(([key, value]) => `'${key}'='${value}'`) // 使用单引号来确保特殊字符不被解析
  422. .join('; '); // 使用分号和空格分隔每个键值对
  423. const command = `powershell -command "Invoke-WebRequest -Uri '${url}' -OutFile '${destination}' -Headers @{${headersString}}"`;
  424. exec(command, (error, stdout, stderr) => {
  425. if (error) {
  426. logger.error(`下载出错: ${error}`);
  427. return funBreak(error);
  428. }
  429. logger.info(`下载完成!`);
  430. funBreak(null, true); // 通知下载成功,修改了B为true
  431. });
  432. }
  433. function downloadFileWithLinux(url, destination, headers, funBreak) {
  434. const headersString = Object.entries(headers)
  435. .map(([key, value]) => `-H '${key}: ${value}'`) // 设置curl的HTTP头参数
  436. .join(' '); // 使用空格分隔每个头参数
  437. const command = `curl ${headersString} '${url}' -o '${destination}'`;
  438. exec(command, (error, stdout, stderr) => {
  439. if (error) {
  440. logger.error(`下载出错: ${error}`);
  441. return funBreak(error);
  442. }
  443. logger.info(`下载完成!`);
  444. funBreak(null, true); // 通知下载成功,修改了B为true
  445. });
  446. }
  447. // 上报-机器人状态
  448. function robotStatus(app) {
  449. var config = getRustConfig()
  450. var msg = (app.threadStatus !== RobotStatus.ERROR ? "完成" : app.errorMessage)
  451. http.request_post(`${config.reportedUrl}/report/statusReport`, {
  452. "robotId": app.id,
  453. "status": app.threadStatus,
  454. "msg": msg
  455. }, {...config.headers}).then((data) => {
  456. // logger.info('??', data);
  457. logger.info('#####################汇报:状态:', '机器人id:', app.id, '状态:', app.threadStatus);
  458. }).catch((error) => {
  459. logger.error(`请求遇到问题1: ${error.message}`); // 处理可能发生的错误
  460. });
  461. }
  462. // 上报-余额
  463. function robotAmount(app) {
  464. var accUrl = "http://127.0.0.1:" + app.port
  465. //拿到策略余额
  466. try {
  467. var config = getRustConfig()
  468. http.request_get(`${accUrl}/account`, {...config.headers})
  469. .then((data) => {
  470. var d = JSON.parse(data)
  471. app.nowBalance = d.now_balance
  472. app.posNum = d.pos
  473. //余额有变动上报一次
  474. // logger.info(`余额当前:${(app.nowBalance + "")}--${(d.now_balance + "")}--`);
  475. // if ((app.nowBalance + "") !== (d.now_balance + "")) {
  476. // http.request_post(`${config.reportedUrl}/report/amountReport`, {
  477. // "robotId": app.id,
  478. // "amount": d.now_balance
  479. // }, {...config.headers})
  480. // .then((data2) => {
  481. // logger.info('上报响应', data2);
  482. // logger.info('#####################汇报:余额:pid:', app.childProcess.pid, '机器人id:', app.id, '机器人本地余额:', app.nowBalance, '实际余额:', d.now_balance);
  483. // app.nowBalance = d.now_balance
  484. // }).catch((error) => {
  485. // logger.error(`#####################汇报:余额上报错误: ${error.message}`); // 处理可能发生的错误
  486. // });
  487. // }
  488. }).catch((error) => {
  489. logger.error(`#####################汇报:余额接口请求失败: ${error.message}`); // 处理可能发生的错误
  490. });
  491. } catch (e) {
  492. logger.error('请求失败!:', e)
  493. }
  494. }
  495. // 定时清理停止状态的 机器人
  496. function delRobot(app) {
  497. // const platform = process.platform;
  498. // if (platform !== 'win32' && app.threadStatus === RobotStatus.STOPPED) {
  499. // var pid = app.childProcess.pid
  500. // delApp(app.id)
  501. // }
  502. }
  503. //----------------------------------------------
  504. // 新策略-获取状态
  505. function predictorState(app) {
  506. logger.info('#####################predictorState:');
  507. var accUrl = "http://127.0.0.1:" + app.port
  508. //拿到策略余额
  509. try {
  510. var config = getRustConfig()
  511. http.request_get(`${accUrl}/predictor_state`, {...config.headers})
  512. .then((data) => {
  513. var d = JSON.parse(data)
  514. logger.info('#####################-d',d);
  515. return {'code': 200, 'data': d, "message": "SUCCESS"}
  516. }).catch((error) => {
  517. // 处理可能发生的错误
  518. logger.error(`#####################汇报:获取状态请求失败: ${error.message}`)
  519. return {'code': -1, 'data': null, "message": `获取状态请求失败${error.message}`}
  520. });
  521. } catch (e) {
  522. logger.error('请求失败!:', e)
  523. return {'code': -1, 'data': null, "message": "获取状态请求出错"}
  524. }
  525. }
  526. module.exports = {
  527. run,
  528. closeApp,
  529. restartApp,
  530. delRobot,
  531. robotStatus,
  532. robotAmount,
  533. closeAppAll,
  534. searchPositions,
  535. predictorState,
  536. appMap,
  537. getApp,
  538. getAppMap,
  539. RobotStatus
  540. };