robot.js 18 KB

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