const { fork } = require('child_process'); const calcTotalProfit = require('../triangle/totalProfitCalc'); const childOptions = process.env.NODE_ENV == 'development' ? { execArgv: ['--inspect=9228'] } : {}; const events_child = fork('./triangle/eventsMatch.js', [], childOptions); const Logs = require('../libs/logs'); const Relation = require('./Relation'); const Request = { callbacks: {}, count: 0, } const GAMES = { List: {}, Relations: {}, Solutions: {}, }; /** * 精确浮点数字 * @param {number} number * @param {number} x * @returns {number} */ const fixFloat = (number, x=2) => { return parseFloat(number.toFixed(x)); } /** * 更新比赛列表 */ const updateGamesList = (({ platform, mk, games } = {}) => { return new Promise((resolve, reject) => { if (!platform || !games) { return reject(new Error('PLATFORM_GAMES_INVALID')); } const marketType = mk == 0 ? 'early' : 'today'; let gamesList = games; if (platform == 'jc') { gamesList = []; const gamesEvents = []; const gamesOutrights = []; games.forEach(game => { const { eventId, events, evtime, special, sptime, ...gameInfo } = game; gamesList.push({ eventId, ...gameInfo }); gamesEvents.push({ eventId, events, evtime }); gamesOutrights.push({ parentId: eventId, special, sptime }); }); updateGamesEvents({ platform, games: gamesEvents, outrights: gamesOutrights }); } const timestamp = Date.now(); const GAMES_LIST = GAMES.List; if (!GAMES_LIST[platform]) { GAMES_LIST[platform] = {}; } if (!GAMES_LIST[platform][marketType]) { GAMES_LIST[platform][marketType] = { games: gamesList, timestamp }; return resolve({ add: gamesList.length, del: 0 }); } const oldGames = GAMES_LIST[platform][marketType].games; const newGames = gamesList; const updateCount = { add: 0, del: 0, }; const newMap = new Map(newGames.map(item => [item.eventId, item])); for (let i = oldGames.length - 1; i >= 0; i--) { if (!newMap.has(oldGames[i].eventId)) { oldGames.splice(i, 1); updateCount.del += 1; } } const oldIds = new Set(oldGames.map(item => item.eventId)); const relatedGames = Object.values(GAMES.Relations).map(rel => rel[platform] ?? {}); const relatedMap = new Map(relatedGames.map(item => [item.eventId, item])); newGames.forEach(item => { if (!oldIds.has(item.eventId)) { oldGames.push(item); updateCount.add += 1; } if (relatedMap.has(item.eventId)) { const relatedGame = relatedMap.get(item.eventId); relatedGame.mk = mk; } }); GAMES_LIST[platform][marketType].timestamp = timestamp; resolve(updateCount); }); }); /** * 更新比赛盘口 */ const updateGamesEvents = ({ platform, mk, games, outrights }) => { return new Promise((resolve, reject) => { if (!platform || (!games && !outrights)) { return reject(new Error('PLATFORM_GAMES_INVALID')); } const relatedGames = Object.values(GAMES.Relations).map(rel => rel[platform] ?? {}); if (!relatedGames.length) { return resolve({ update: 0 }); } const updateCount = { update: 0 }; const relatedMap = new Map(relatedGames.map(item => [item.eventId, item])); games?.forEach(game => { const { eventId, evtime, events } = game; const relatedGame = relatedMap.get(eventId); if (relatedGame) { relatedGame.evtime = evtime; relatedGame.events = events; updateCount.update ++; } }); outrights?.forEach(outright => { const { parentId, sptime, special } = outright; const relatedGame = relatedMap.get(parentId); if (relatedGame) { relatedGame.sptime = sptime; relatedGame.special = special; updateCount.update ++; } }); resolve(updateCount); }); } /** * 获取比赛列表 */ const getGamesList = () => { const gamesListMap = {}; Object.keys(GAMES.List).forEach(platform => { const { today, early } = GAMES.List[platform]; const todayList = today?.games ?? []; const earlyList = early?.games ?? []; const timestamp_today = today?.timestamp ?? 0; const timestamp_early = early?.timestamp ?? 0; const timestamp = Math.max(timestamp_today, timestamp_early); gamesListMap[platform] = { games: [...todayList, ...earlyList], timestamp, timestamp_today, timestamp_early, } }); return gamesListMap; } /** * 获取比赛盘口 */ const getGamesEvents = (platform) => { if (platform) { return Object.values(GAMES.Relations).map(rel => rel[platform] ?? {}); } const gamesEvents = {}; Object.values(GAMES.Relations).forEach(rel => { Object.keys(rel).forEach(platform => { const game = rel[platform]; const { eventId, events, special } = game; if (!gamesEvents[platform]) { gamesEvents[platform] = {}; } gamesEvents[platform][eventId] = { ...events, ...special }; }); }); return gamesEvents; } /** * 更新关联比赛 */ const updateGamesRelation = async (relation) => { const { id, rel } = relation; return Relation.findOne({ id }) .then(result => { if (!result) { const gameRelation = new Relation(relation); return gameRelation.save(); } return Relation.updateOne({ id }, { $set: { rel } }); }) .then(result => { GAMES.Relations[id] = rel; return result; }); } /** * 删除关联比赛 */ const removeGamesRelation = async (id) => { if (!id) { return Promise.reject(new Error('ID_INVALID')); } return Relation.deleteOne({ id }) .then(result => { delete GAMES.Relations[id]; return result; }); } /** * 获取关联比赛 */ const getGamesRelation = async (listEvents) => { const relationIds = Object.keys(GAMES.Relations); if (listEvents) { return relationIds.map(id => { const rel = GAMES.Relations[id]; return { id, rel }; }); } return relationIds.map(id => { const rel = { ...GAMES.Relations[id] }; Object.keys(rel).forEach(platform => { const game = { ...rel[platform] }; delete game.events; delete game.evtime; delete game.special; delete game.sptime; rel[platform] = game; }); return { id, rel }; }); } /** * 清理关联比赛 */ const relationsCleanup = () => { const expireTime = Date.now() - 1000*60*5; getGamesRelation() .then(gamesRelation => { gamesRelation.forEach(item => { const { id, rel } = item; const expire = Object.values(rel).find(event => { return event.timestamp <= expireTime; }); if (expire) { Logs.out('relation cleanup', id); removeGamesRelation(id); } return true; }); }); } /** * 从数据库中同步关联比赛 */ const relationSync = () => { Relation.find().then(relation => relation.forEach(item => { const { id, rel } = item.toObject(); GAMES.Relations[id] = rel; })); } /** * 更新中单方案 */ const setSolutions = (solutions) => { if (solutions?.length) { const solutionsHistory = GAMES.Solutions; const updateIds = { add: [], update: [] } solutions.forEach(item => { const { sid, sol: { win_average } } = item; if (!solutionsHistory[sid]) { solutionsHistory[sid] = item; updateIds.add.push({ sid, win_average }); return; } const historyWinAverage = solutionsHistory[sid].sol.win_average; if (win_average != historyWinAverage) { solutionsHistory[sid] = item; updateIds.update.push({ sid, win_average, his_average: historyWinAverage, diff: fixFloat(win_average - historyWinAverage) }); return; } const { timestamp } = item; solutionsHistory[sid].timestamp = timestamp; }); // if (updateIds.add.length || updateIds.update.length) { // const solutionsList = Object.values(solutionsHistory).sort((a, b) => b.sol.win_average - a.sol.win_average); // Logs.outDev('solutions history update', JSON.stringify(solutionsList, null, 2), JSON.stringify(updateIds, null, 2)); // } } } /** * 获取中单方案 */ const getSolutions = async () => { const gamesEvents = getGamesEvents(); const gamesRelations = await getGamesRelation(); const relationsMap = new Map(gamesRelations.map(item => [item.id, item.rel])); const solutions = Object.values(GAMES.Solutions).sort((a, b) => b.sol.win_average - a.sol.win_average).map(item => { const { info: { id } } = item; const relation = relationsMap.get(id); return { ...item, info: { id, ...relation } } }); return { solutions, gamesEvents }; } /** * 清理中单方案 */ const solutionsCleanup = () => { const solutionsHistory = GAMES.Solutions; Object.keys(solutionsHistory).forEach(sid => { const { timestamp } = solutionsHistory[sid]; const nowTime = Date.now(); if (nowTime - timestamp > 1000*60) { delete solutionsHistory[sid]; Logs.out('solution history timeout', sid); return; } const solution = solutionsHistory[sid]; const eventTime = solution.info.timestamp; if (nowTime > eventTime) { delete solutionsHistory[sid]; Logs.out('solution history expired', sid); } }); } const getDataFromChild = (type, callback) => { const id = ++Request.count; Request.callbacks[id] = callback; events_child.send({ method: 'get', id, type }); } events_child.on('message', async (message) => { const { callbacks } = Request; const { method, id, type, data } = message; if (method == 'get' && id) { let responseData = null; if (type == 'getGamesRelation') { responseData = await getGamesRelation(true); } else if (type == 'getSolutionHistory') { responseData = getSolutionHistory(); } events_child.send({ type: 'response', id, data: responseData }); } else if (method == 'post') { if (type == 'setSolutions') { setSolutions(data); } } else if (method == 'response' && id && callbacks[id]) { callbacks[id](data); delete callbacks[id]; } }); relationSync(); setInterval(() => { relationsCleanup(); }, 5000); setInterval(() => { solutionsCleanup(); }, 1000*30); module.exports = { updateGamesList, updateGamesEvents, getGamesList, updateGamesRelation, getGamesRelation, removeGamesRelation, getGamesEvents, getSolutions, calcTotalProfit, }