robot.js 20 KB

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