robot.js 14 KB

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