Games.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. const mongoose = require('mongoose');
  2. const { Schema } = mongoose;
  3. const gameSchema = new Schema({
  4. leagueId: { type: Number, required: true },
  5. eventId: { type: Number, required: true },
  6. leagueName: { type: String, required: true },
  7. teamHomeName: { type: String, required: true },
  8. teamAwayName: { type: String, required: true },
  9. timestamp: { type: Number, required: true },
  10. matchNumStr: { type: String, required: false }
  11. }, { _id: false });
  12. const relSchema = new Schema({
  13. jc: { type: gameSchema },
  14. ps: { type: gameSchema },
  15. ob: { type: gameSchema },
  16. }, { _id: false });
  17. const relationSchema = new Schema({
  18. id: { type: Number, required: true },
  19. rel: { type: relSchema, required: true },
  20. }, {
  21. toJSON: {
  22. transform(doc, ret) {
  23. delete ret._id;
  24. delete ret.__v;
  25. }
  26. },
  27. toObject: {
  28. transform(doc, ret) {
  29. delete ret._id;
  30. delete ret.__v;
  31. }
  32. }
  33. });
  34. const Relation = mongoose.model('Relation', relationSchema);
  35. const axios = require('axios');
  36. const { fork } = require('child_process');
  37. const calcTotalProfit = require('../triangle/totalProfitCalc');
  38. const childOptions = process.env.NODE_ENV == 'development' ? {
  39. execArgv: ['--inspect=9228']
  40. } : {};
  41. const events_child = fork('./triangle/eventsMatch.js', [], childOptions);
  42. const Logs = require('../libs/logs');
  43. const Setting = require('./Setting');
  44. const Request = {
  45. callbacks: {},
  46. count: 0,
  47. }
  48. const GAMES = {
  49. Leagues: {},
  50. List: {},
  51. Relations: {},
  52. Solutions: {},
  53. };
  54. /**
  55. * 精确浮点数字
  56. * @param {number} number
  57. * @param {number} x
  58. * @returns {number}
  59. */
  60. const fixFloat = (number, x=2) => {
  61. return parseFloat(number.toFixed(x));
  62. }
  63. /**
  64. * 更新联赛列表
  65. */
  66. const syncLeaguesList = ({ mk, leagues }) => {
  67. axios.post('https://api.isthe.me/api/p/syncLeague', { mk, leagues })
  68. .then(res => {
  69. Logs.out('syncLeaguesList', res.data);
  70. })
  71. .catch(err => {
  72. Logs.out('syncLeaguesList', err.message);
  73. });
  74. }
  75. const updateLeaguesList = ({ mk, leagues }) => {
  76. const leaguesList = GAMES.Leagues;
  77. if (JSON.stringify(leaguesList[mk]) != JSON.stringify(leagues)) {
  78. leaguesList[mk] = leagues;
  79. syncLeaguesList({ mk, leagues });
  80. return leagues.length;
  81. }
  82. return 0;
  83. }
  84. /**
  85. * 获取筛选过的联赛
  86. */
  87. const getFilteredLeagues = async (mk) => {
  88. return axios.get(`https://api.isthe.me/api/p/getLeagueTast?mk=${mk}`)
  89. .then(res => {
  90. if (res.data.code == 0) {
  91. return res.data.data;
  92. }
  93. return Promise.reject(new Error(res.data.message));
  94. });
  95. }
  96. /**
  97. * 更新比赛列表
  98. */
  99. const syncGamesList = ({ platform, mk, games }) => {
  100. axios.post('https://api.isthe.me/api/p/syncGames', { platform, mk, games })
  101. .then(res => {
  102. Logs.out('syncGamesList', res.data);
  103. })
  104. .catch(err => {
  105. Logs.out('syncGamesList', err.message);
  106. });
  107. }
  108. const updateGamesList = (({ platform, mk, games } = {}) => {
  109. syncGamesList({ platform, mk, games });
  110. return new Promise((resolve, reject) => {
  111. if (!platform || !games) {
  112. return reject(new Error('PLATFORM_GAMES_INVALID'));
  113. }
  114. const marketType = mk == 0 ? 'early' : 'today';
  115. let gamesList = games;
  116. if (platform == 'jc') {
  117. gamesList = [];
  118. const gamesEvents = [];
  119. const gamesOutrights = [];
  120. games.forEach(game => {
  121. const { eventId, events, evtime, special, sptime, ...gameInfo } = game;
  122. gamesList.push({ eventId, ...gameInfo });
  123. gamesEvents.push({ eventId, events, evtime });
  124. gamesOutrights.push({ parentId: eventId, special, sptime });
  125. });
  126. updateGamesEvents({ platform, games: gamesEvents, outrights: gamesOutrights });
  127. }
  128. const timestamp = Date.now();
  129. const GAMES_LIST = GAMES.List;
  130. if (!GAMES_LIST[platform]) {
  131. GAMES_LIST[platform] = {};
  132. }
  133. if (!GAMES_LIST[platform][marketType]) {
  134. GAMES_LIST[platform][marketType] = { games: gamesList, timestamp };
  135. return resolve({ add: gamesList.length, del: 0 });
  136. }
  137. const oldGames = GAMES_LIST[platform][marketType].games;
  138. const newGames = gamesList;
  139. const updateCount = {
  140. add: 0,
  141. del: 0,
  142. };
  143. const newMap = new Map(newGames.map(item => [item.eventId, item]));
  144. for (let i = oldGames.length - 1; i >= 0; i--) {
  145. if (!newMap.has(oldGames[i].eventId)) {
  146. oldGames.splice(i, 1);
  147. updateCount.del += 1;
  148. }
  149. }
  150. const oldIds = new Set(oldGames.map(item => item.eventId));
  151. const relatedGames = Object.values(GAMES.Relations).map(rel => rel[platform] ?? {});
  152. const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));
  153. newGames.forEach(item => {
  154. if (!oldIds.has(item.eventId)) {
  155. oldGames.push(item);
  156. updateCount.add += 1;
  157. }
  158. if (relatedMap.has(item.eventId)) {
  159. const relatedGame = relatedMap.get(item.eventId);
  160. relatedGame.mk = mk;
  161. }
  162. });
  163. GAMES_LIST[platform][marketType].timestamp = timestamp;
  164. resolve(updateCount);
  165. });
  166. });
  167. /**
  168. * 更新比赛盘口
  169. */
  170. const updateGamesEvents = ({ platform, mk, games, outrights }) => {
  171. return new Promise((resolve, reject) => {
  172. if (!platform || (!games && !outrights)) {
  173. return reject(new Error('PLATFORM_GAMES_INVALID'));
  174. }
  175. const relatedGames = Object.values(GAMES.Relations).map(rel => rel[platform] ?? {});
  176. if (!relatedGames.length) {
  177. return resolve({ update: 0 });
  178. }
  179. const updateCount = {
  180. update: 0
  181. };
  182. const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));
  183. games?.forEach(game => {
  184. const { eventId, evtime, events } = game;
  185. const relatedGame = relatedMap.get(eventId);
  186. if (relatedGame) {
  187. relatedGame.evtime = evtime;
  188. relatedGame.events = events;
  189. updateCount.update ++;
  190. }
  191. });
  192. outrights?.forEach(outright => {
  193. const { parentId, sptime, special } = outright;
  194. const relatedGame = relatedMap.get(parentId);
  195. if (relatedGame) {
  196. relatedGame.sptime = sptime;
  197. relatedGame.special = special;
  198. updateCount.update ++;
  199. }
  200. });
  201. resolve(updateCount);
  202. });
  203. }
  204. /**
  205. * 获取比赛列表
  206. */
  207. const getGamesList = () => {
  208. const gamesListMap = {};
  209. Object.keys(GAMES.List).forEach(platform => {
  210. const { today, early } = GAMES.List[platform];
  211. const todayList = today?.games ?? [];
  212. const earlyList = early?.games ?? [];
  213. const timestamp_today = today?.timestamp ?? 0;
  214. const timestamp_early = early?.timestamp ?? 0;
  215. const timestamp = Math.max(timestamp_today, timestamp_early);
  216. gamesListMap[platform] = {
  217. games: [...todayList, ...earlyList],
  218. timestamp,
  219. timestamp_today,
  220. timestamp_early,
  221. }
  222. });
  223. return gamesListMap;
  224. }
  225. /**
  226. * 获取比赛盘口
  227. */
  228. const getGamesEvents = ({ platform, relIds = [] } = {}) => {
  229. const idSet = new Set(relIds);
  230. const relations = { ...GAMES.Relations };
  231. Object.keys(relations).forEach(id => {
  232. if (idSet.size && !idSet.has(id)) {
  233. delete relations[id];
  234. }
  235. });
  236. if (platform) {
  237. return Object.values(relations).map(rel => rel[platform] ?? {});
  238. }
  239. const gamesEvents = {};
  240. Object.values(relations).forEach(rel => {
  241. Object.keys(rel).forEach(platform => {
  242. const game = rel[platform] ?? {};
  243. const { eventId, events, special } = game;
  244. if (!gamesEvents[platform]) {
  245. gamesEvents[platform] = {};
  246. }
  247. gamesEvents[platform][eventId] = { ...events, ...special };
  248. });
  249. });
  250. return gamesEvents;
  251. }
  252. /**
  253. * 更新关联比赛
  254. */
  255. const updateGamesRelation = async (relation) => {
  256. const { id, rel } = relation;
  257. return Relation.findOne({ id })
  258. .then(result => {
  259. if (!result) {
  260. const gameRelation = new Relation(relation);
  261. return gameRelation.save();
  262. }
  263. return Relation.updateOne({ id }, { $set: { rel } });
  264. })
  265. .then(result => {
  266. GAMES.Relations[id] = rel;
  267. return result;
  268. });
  269. }
  270. /**
  271. * 删除关联比赛
  272. */
  273. const removeGamesRelation = async (id) => {
  274. if (!id) {
  275. return Promise.reject(new Error('ID_INVALID'));
  276. }
  277. return Relation.deleteOne({ id })
  278. .then(result => {
  279. delete GAMES.Relations[id];
  280. return result;
  281. });
  282. }
  283. /**
  284. * 获取关联比赛
  285. */
  286. const getGamesRelation = (listEvents) => {
  287. const relationIds = Object.keys(GAMES.Relations);
  288. if (listEvents) {
  289. return relationIds.map(id => {
  290. const rel = GAMES.Relations[id];
  291. return { id, rel };
  292. });
  293. }
  294. return relationIds.map(id => {
  295. const rel = { ...GAMES.Relations[id] };
  296. Object.keys(rel).forEach(platform => {
  297. const game = { ...rel[platform] };
  298. delete game.events;
  299. delete game.evtime;
  300. delete game.special;
  301. delete game.sptime;
  302. rel[platform] = game;
  303. });
  304. return { id, rel };
  305. });
  306. }
  307. /**
  308. * 清理关联比赛
  309. */
  310. const relationsCleanup = () => {
  311. const expireTime = Date.now() - 1000*60*5;
  312. const gamesRelation = getGamesRelation();
  313. gamesRelation.forEach(item => {
  314. const { id, rel } = item;
  315. const expire = Object.values(rel).find(event => {
  316. return event.timestamp <= expireTime;
  317. });
  318. if (expire) {
  319. Logs.out('relation cleanup', id);
  320. removeGamesRelation(id);
  321. }
  322. return true;
  323. });
  324. }
  325. /**
  326. * 从数据库中同步关联比赛
  327. */
  328. const relationSync = () => {
  329. Relation.find().then(relation => relation.forEach(item => {
  330. const { id, rel } = item.toObject();
  331. GAMES.Relations[id] = rel;
  332. }));
  333. }
  334. /**
  335. * 更新中单方案
  336. */
  337. const setSolutions = (solutions) => {
  338. if (solutions?.length) {
  339. const solutionsHistory = GAMES.Solutions;
  340. const updateIds = { add: [], update: [] }
  341. solutions.forEach(item => {
  342. const { sid, sol: { win_average } } = item;
  343. if (!solutionsHistory[sid]) {
  344. solutionsHistory[sid] = item;
  345. updateIds.add.push({ sid, win_average });
  346. return;
  347. }
  348. const historyWinAverage = solutionsHistory[sid].sol.win_average;
  349. if (win_average != historyWinAverage) {
  350. solutionsHistory[sid] = item;
  351. updateIds.update.push({ sid, win_average, his_average: historyWinAverage, diff: fixFloat(win_average - historyWinAverage) });
  352. return;
  353. }
  354. const { timestamp } = item;
  355. solutionsHistory[sid].timestamp = timestamp;
  356. });
  357. // if (updateIds.add.length || updateIds.update.length) {
  358. // const solutionsList = Object.values(solutionsHistory).sort((a, b) => b.sol.win_average - a.sol.win_average);
  359. // Logs.outDev('solutions history update', JSON.stringify(solutionsList, null, 2), JSON.stringify(updateIds, null, 2));
  360. // }
  361. }
  362. }
  363. /**
  364. * 获取中单方案
  365. */
  366. const getSolutions = async () => {
  367. const solutionsList = Object.values(GAMES.Solutions);
  368. const relIds = solutionsList.map(item => item.info.id);
  369. const gamesEvents = getGamesEvents({ relIds });
  370. const gamesRelations = getGamesRelation();
  371. const relationsMap = new Map(gamesRelations.map(item => [item.id, item.rel]));
  372. const solutions = solutionsList.sort((a, b) => b.sol.win_average - a.sol.win_average).map(item => {
  373. const { info: { id } } = item;
  374. const relation = relationsMap.get(id);
  375. return {
  376. ...item,
  377. info: { id, ...relation }
  378. }
  379. });
  380. return { solutions, gamesEvents };
  381. }
  382. /**
  383. * 清理中单方案
  384. */
  385. const solutionsCleanup = () => {
  386. const solutionsHistory = GAMES.Solutions;
  387. Object.keys(solutionsHistory).forEach(sid => {
  388. const { timestamp } = solutionsHistory[sid];
  389. const nowTime = Date.now();
  390. if (nowTime - timestamp > 1000*60) {
  391. delete solutionsHistory[sid];
  392. Logs.out('solution history timeout', sid);
  393. return;
  394. }
  395. const solution = solutionsHistory[sid];
  396. const eventTime = solution.info.timestamp;
  397. if (nowTime > eventTime) {
  398. delete solutionsHistory[sid];
  399. Logs.out('solution history expired', sid);
  400. }
  401. });
  402. }
  403. const getTotalProfit = (sid1, sid2, gold_side_jc) => {
  404. const preSolution = GAMES.Solutions[sid1];
  405. const subSolution = GAMES.Solutions[sid2];
  406. const relId1 = preSolution?.info?.id;
  407. const relId2 = subSolution?.info?.id;
  408. const relIds = [relId1, relId2];
  409. const gamesEvents = getGamesEvents({ relIds });
  410. const gamesRelations = getGamesRelation();
  411. const relationsMap = new Map(gamesRelations.map(item => [item.id, item.rel]));
  412. const preRelation = relationsMap.get(relId1);
  413. const subRelation = relationsMap.get(relId2);
  414. preSolution.info = { id: relId1, ...preRelation };
  415. subSolution.info = { id: relId2, ...subRelation };
  416. const sol1 = preSolution?.sol;
  417. const sol2 = subSolution?.sol;
  418. if (!sol1 || !sol2 || !gold_side_jc) {
  419. return {};
  420. }
  421. const profit = calcTotalProfit(sol1, sol2, gold_side_jc);
  422. return { profit, preSolution, subSolution, gamesEvents };
  423. }
  424. const getSetting = async () => {
  425. return Setting.get();
  426. }
  427. const getDataFromChild = (type, callback) => {
  428. const id = ++Request.count;
  429. Request.callbacks[id] = callback;
  430. events_child.send({ method: 'get', id, type });
  431. }
  432. events_child.on('message', async (message) => {
  433. const { callbacks } = Request;
  434. const { method, id, type, data } = message;
  435. if (method == 'get' && id) {
  436. let responseData = null;
  437. if (type == 'getGamesRelation') {
  438. responseData = getGamesRelation(true);
  439. }
  440. else if (type == 'getSetting') {
  441. responseData = await getSetting();
  442. }
  443. // else if (type == 'getSolutionHistory') {
  444. // responseData = getSolutionHistory();
  445. // }
  446. events_child.send({ type: 'response', id, data: responseData });
  447. }
  448. else if (method == 'post') {
  449. if (type == 'setSolutions') {
  450. setSolutions(data);
  451. }
  452. }
  453. else if (method == 'response' && id && callbacks[id]) {
  454. callbacks[id](data);
  455. delete callbacks[id];
  456. }
  457. });
  458. relationSync();
  459. setInterval(() => {
  460. relationsCleanup();
  461. }, 5000);
  462. setInterval(() => {
  463. solutionsCleanup();
  464. }, 1000*30);
  465. module.exports = {
  466. updateLeaguesList, getFilteredLeagues,
  467. updateGamesList, updateGamesEvents, getGamesList,
  468. updateGamesRelation, getGamesRelation, removeGamesRelation,
  469. getGamesEvents, getSolutions, getTotalProfit,
  470. }