flyzto 6 ヶ月 前
コミット
67e30891bf

+ 1 - 1
server/middleware/authMiddleware.js

@@ -13,7 +13,7 @@ module.exports = (req, res, next) => {
     next();
   }
   catch (err) {
-    Logs.errDev('token验证错误:', err);
+    Logs.errDev('token验证错误:', err.message);
     res.unauthorized('无效或已过期的 token');
   }
 };

+ 89 - 22
server/models/Games.js

@@ -37,6 +37,7 @@ const relationSchema = new Schema({
 
 const Relation = mongoose.model('Relation', relationSchema);
 
+const axios = require('axios');
 const { fork } = require('child_process');
 const calcTotalProfit = require('../triangle/totalProfitCalc');
 
@@ -55,6 +56,7 @@ const Request = {
 }
 
 const GAMES = {
+  Leagues: {},
   List: {},
   Relations: {},
   Solutions: {},
@@ -70,10 +72,55 @@ const fixFloat = (number, x=2) => {
   return parseFloat(number.toFixed(x));
 }
 
+/**
+ * 更新联赛列表
+ */
+const syncLeaguesList = ({ mk, leagues }) => {
+  axios.post('https://api.isthe.me/api/p/syncLeague', { mk, leagues })
+  .then(res => {
+    Logs.out('syncLeaguesList', res.data);
+  })
+  .catch(err => {
+    Logs.out('syncLeaguesList', err.message);
+  });
+}
+const updateLeaguesList = ({ mk, leagues }) => {
+  const leaguesList = GAMES.Leagues;
+  if (JSON.stringify(leaguesList[mk]) != JSON.stringify(leagues)) {
+    leaguesList[mk] = leagues;
+    syncLeaguesList({ mk, leagues });
+    return leagues.length;
+  }
+  return 0;
+}
+
+/**
+ * 获取筛选过的联赛
+ */
+const getFilteredLeagues = async (mk) => {
+  return axios.get(`https://api.isthe.me/api/p/getLeagueTast?mk=${mk}`)
+  .then(res => {
+    if (res.data.code == 0) {
+      return res.data.data;
+    }
+    return Promise.reject(new Error(res.data.message));
+  });
+}
+
 /**
  * 更新比赛列表
  */
+const syncGamesList = ({ platform, mk, games }) => {
+  axios.post('https://api.isthe.me/api/p/syncGames', { platform, mk, games })
+  .then(res => {
+    Logs.out('syncGamesList', res.data);
+  })
+  .catch(err => {
+    Logs.out('syncGamesList', err.message);
+  });
+}
 const updateGamesList = (({ platform, mk, games } = {}) => {
+  syncGamesList({ platform, mk, games });
   return new Promise((resolve, reject) => {
     if (!platform || !games) {
       return reject(new Error('PLATFORM_GAMES_INVALID'));
@@ -218,14 +265,21 @@ const getGamesList = () => {
 /**
  * 获取比赛盘口
  */
-const getGamesEvents = (platform) => {
+const getGamesEvents = ({ platform, relIds = [] } = {}) => {
+  const idSet = new Set(relIds);
+  const relations = { ...GAMES.Relations };
+  Object.keys(relations).forEach(id => {
+    if (idSet.size && !idSet.has(id)) {
+      delete relations[id];
+    }
+  });
   if (platform) {
-    return Object.values(GAMES.Relations).map(rel => rel[platform] ?? {});
+    return Object.values(relations).map(rel => rel[platform] ?? {});
   }
   const gamesEvents = {};
-  Object.values(GAMES.Relations).forEach(rel => {
+  Object.values(relations).forEach(rel => {
     Object.keys(rel).forEach(platform => {
-      const game = rel[platform];
+      const game = rel[platform] ?? {};
       const { eventId, events, special } = game;
       if (!gamesEvents[platform]) {
         gamesEvents[platform] = {};
@@ -272,7 +326,7 @@ const removeGamesRelation = async (id) => {
 /**
  * 获取关联比赛
  */
-const getGamesRelation = async (listEvents) => {
+const getGamesRelation = (listEvents) => {
   const relationIds = Object.keys(GAMES.Relations);
   if (listEvents) {
     return relationIds.map(id => {
@@ -299,19 +353,17 @@ const getGamesRelation = async (listEvents) => {
  */
 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 gamesRelation = getGamesRelation();
+  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;
   });
 }
 
@@ -363,10 +415,12 @@ const setSolutions = (solutions) => {
  * 获取中单方案
  */
 const getSolutions = async () => {
-  const gamesEvents = getGamesEvents();
-  const gamesRelations = await getGamesRelation();
+  const solutionsList = Object.values(GAMES.Solutions);
+  const relIds = solutionsList.map(item => item.info.id);
+  const gamesEvents = getGamesEvents({ relIds });
+  const gamesRelations = 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 solutions = solutionsList.sort((a, b) => b.sol.win_average - a.sol.win_average).map(item => {
     const { info: { id } } = item;
     const relation = relationsMap.get(id);
     return {
@@ -402,13 +456,25 @@ const solutionsCleanup = () => {
 const getTotalProfit = (sid1, sid2, gold_side_jc) => {
   const preSolution = GAMES.Solutions[sid1];
   const subSolution = GAMES.Solutions[sid2];
+  const relId1 = preSolution?.info?.id;
+  const relId2 = subSolution?.info?.id;
+  const relIds = [relId1, relId2];
+  const gamesEvents = getGamesEvents({ relIds });
+
+  const gamesRelations = getGamesRelation();
+  const relationsMap = new Map(gamesRelations.map(item => [item.id, item.rel]));
+  const preRelation = relationsMap.get(relId1);
+  const subRelation = relationsMap.get(relId2);
+  preSolution.info = { id: relId1, ...preRelation };
+  subSolution.info = { id: relId2, ...subRelation };
+
   const sol1 = preSolution?.sol;
   const sol2 = subSolution?.sol;
   if (!sol1 || !sol2 || !gold_side_jc) {
     return {};
   }
   const profit = calcTotalProfit(sol1, sol2, gold_side_jc);
-  return { profit, preSolution, subSolution };
+  return { profit, preSolution, subSolution, gamesEvents };
 }
 
 const getSetting = async () => {
@@ -427,7 +493,7 @@ events_child.on('message', async (message) => {
   if (method == 'get' && id) {
     let responseData = null;
     if (type == 'getGamesRelation') {
-      responseData = await getGamesRelation(true);
+      responseData = getGamesRelation(true);
     }
     else if (type == 'getSetting') {
       responseData = await getSetting();
@@ -458,6 +524,7 @@ setInterval(() => {
 }, 1000*30);
 
 module.exports = {
+  updateLeaguesList, getFilteredLeagues,
   updateGamesList, updateGamesEvents, getGamesList,
   updateGamesRelation, getGamesRelation, removeGamesRelation,
   getGamesEvents, getSolutions, getTotalProfit,

+ 26 - 12
server/routes/triangle.js

@@ -29,10 +29,23 @@ router.post('/update_games_events', (req, res) => {
   })
 });
 
-// 获取比赛列表
-router.get('/get_games_list', (req, res) => {
-  const gamesList = Games.getGamesList();
-  res.sendSuccess(gamesList);
+// 更新联赛列表
+router.post('/update_leagues_list', (req, res) => {
+  const { mk, leagues } = req.body ?? {};
+  const updateCount = Games.updateLeaguesList({ mk, leagues });
+  res.sendSuccess({ updateCount });
+});
+
+// 获取筛选过的联赛
+router.get('/get_filtered_leagues', (req, res) => {
+  const { mk } = req.query;
+  Games.getFilteredLeagues(mk)
+  .then(filteredLeagues => {
+    res.sendSuccess(filteredLeagues);
+  })
+  .catch(err => {
+    res.badRequest(err.message);
+  });
 });
 
 // 保存关联比赛
@@ -59,21 +72,22 @@ router.post('/remove_games_relation', authMiddleware, (req, res) => {
   });
 });
 
+// 获取比赛列表
+router.get('/get_games_list', (req, res) => {
+  const gamesList = Games.getGamesList();
+  res.sendSuccess(gamesList);
+});
+
 // 获取关联列表
 router.get('/get_games_relation', (req, res) => {
-  Games.getGamesRelation()
-  .then(ret => {
-    res.sendSuccess(ret);
-  })
-  .catch(err => {
-    res.badRequest(err.message);
-  });
+  const gamesRelation = Games.getGamesRelation();
+  res.sendSuccess(gamesRelation);
 });
 
 // 获取比赛盘口
 router.get('/get_games_events', authMiddleware, (req, res) => {
   const { platform } = req.query;
-  const gamesEvents = Games.getGamesEvents(platform);
+  const gamesEvents = Games.getGamesEvents({ platform });
   res.sendSuccess(gamesEvents);
 });
 

+ 178 - 56
web/apps/web-antd/src/views/match/solutions/index.vue

@@ -35,11 +35,13 @@ const headerStyle = computed(() => {
 });
 
 const totalProfitValue = computed(() => {
-  const { profit = {}, sol1 = {}, sol2 = {} } = totalProfit.value;
-  const profitInfo = {};
+  const { profit = {}, preSolution = {}, subSolution = {}, gamesEvents = {} } = totalProfit.value;
+  const sol1 = formatSolution(preSolution, gamesEvents);
+  const sol2 = formatSolution(subSolution, gamesEvents);
+
   const jcScale = jcOptions.bet / profit.jc_base ?? 10000;
   const jcRebate = jcOptions.bet * jcOptions.rebate / 100;
-
+  const profitInfo = {};
   Object.keys(profit).forEach(key => {
     if (key == 'win_diff') {
       return;
@@ -51,7 +53,53 @@ const totalProfitValue = computed(() => {
       profitInfo[key] = fixFloat(profit[key] * jcScale + jcRebate);
     }
   });
-  return profitInfo;
+
+  const jcInfo = [];
+  const outPreSol = [];
+  const outSubSol = [];
+
+  const solutions = [sol1, sol2].filter(item => item);
+  solutions.forEach((item, index) => {
+    const { sol: { jc_index }, cpr } = item;
+    const newCpr = [...cpr];
+    const jc_info = newCpr.splice(jc_index, 1);
+    jcInfo.push({ ...jc_info[0] });
+    newCpr.forEach((c, i) => {
+      let side = '';
+      if (jc_index == 0) {
+        if (i == 0) {
+          side = "B"
+        }
+        else {
+          side = "M";
+        }
+      }
+      else if (jc_index == 1) {
+        if (i == 0) {
+          side = "A";
+        }
+        else {
+          side = "M";
+        }
+      }
+      else {
+        if (i == 0) {
+          side = "A";
+        }
+        else {
+          side = "B";
+        }
+      }
+      if (index == 0) {
+        outPreSol.push({ ...c, g: profitInfo[`gold${side}${index+1}`] });
+      }
+      else {
+        outSubSol.push({ ...c, g: profitInfo[`gold${side}${index+1}`] });
+      }
+    })
+  });
+
+  return { solutions, profit: profitInfo, jcInfo, outPreSol, outSubSol };
 });
 
 const solutionsList = computed(() => {
@@ -233,30 +281,40 @@ const formatEvents = (events, cprKeys) => {
   });
 }
 
+const formatSolution = (solution, eventsList) => {
+  const { cpr, info } = solution;
+  if (!cpr || !info) {
+    return null;
+  }
+
+  const cprKeys = cpr.map(item => item.k);
+
+  const jcEvents = eventsList.jc?.[info.jc.eventId] ?? {};
+  const psEvents = eventsList.ps?.[info.ps.eventId] ?? {};
+  const obEvents = eventsList.ob?.[info.ob.eventId] ?? {};
+
+  info.jc.events = formatJcEvents(jcEvents);
+  info.ps.events = formatEvents(psEvents, cprKeys);
+  info.ob.events = formatEvents(obEvents, cprKeys);
+
+  info.jc.dateTime = dayjs(info.jc.timestamp).format('YYYY-MM-DD HH:mm:ss');
+  info.ps.dateTime = dayjs(info.ps.timestamp).format('YYYY-MM-DD HH:mm:ss');
+  info.ob.dateTime = dayjs(info.ob.timestamp).format('YYYY-MM-DD HH:mm:ss');
+
+  cpr.forEach(item => {
+    const { k, p } = item;
+    if (!info[p]['selected']) {
+      info[p]['selected'] = [];
+    }
+    info[p]['selected'].push(k);
+  });
+
+  return solution;
+}
+
 const updateSolutions = async () => {
   const { solutions: solutionsList, gamesEvents: eventsList } = await getSolutions();
-  solutions.value = solutionsList?.map(item => {
-    const { cpr, info, sol: { cross_type, jc_index, gold_side_a, gold_side_b, gold_side_m, win_side_a, win_side_b, win_side_m, win_average } } = item;
-    const cprKeys = cpr.map(item => item.k);
-
-    info.jc.events = formatJcEvents(eventsList.jc[info.jc.eventId]);
-    info.ps.events = formatEvents(eventsList.ps[info.ps.eventId], cprKeys);
-    info.ob.events = formatEvents(eventsList.ob[info.ob.eventId], cprKeys);
-
-    info.jc.dateTime = dayjs(info.jc.timestamp).format('YYYY-MM-DD HH:mm:ss');
-    info.ps.dateTime = dayjs(info.ps.timestamp).format('YYYY-MM-DD HH:mm:ss');
-    info.ob.dateTime = dayjs(info.ob.timestamp).format('YYYY-MM-DD HH:mm:ss');
-
-    cpr.forEach(item => {
-      const { k, p } = item;
-      if (!info[p]['selected']) {
-        info[p]['selected'] = [];
-      }
-      info[p]['selected'].push(k);
-    });
-
-    return item;
-  }) ?? [];
+  solutions.value = solutionsList?.map(solution => formatSolution(solution, eventsList)) ?? [];
 }
 
 const showTotalProfit = async () => {
@@ -348,16 +406,57 @@ onUnmounted(() => {
     <Drawer
       title="综合利润方案"
       placement="bottom"
+      height="600"
       :visible="totalProfitVisible"
       @close="closeTotalProfit"
     >
-      <div class="solution-total-profit">
-        <ul style="text-align: center;">
-          <li v-for="key in Object.keys(totalProfitValue)" :key="key">
-            <span>{{ key }}:</span>
-            <span>{{ totalProfitValue[key] }}</span>
-          </li>
-        </ul>
+      <div class="solution-total-profit" v-if="totalProfitValue.solutions.length">
+        <div class="solution-item"
+          v-for="{ sid, info: { jc, ps, ob } } in totalProfitValue.solutions" :key="sid">
+          <MatchCard platform="jc" :eventId="jc.eventId" :leagueName="jc.leagueName" :teamHomeName="jc.teamHomeName"
+            :teamAwayName="jc.teamAwayName" :dateTime="jc.dateTime" :eventInfo="jc.eventInfo" :events="jc.events ?? []"
+            :matchNumStr="jc.matchNumStr" :selected="jc.selected ?? []" />
+
+          <MatchCard platform="ps" :eventId="ps.eventId" :leagueName="ps.leagueName" :teamHomeName="ps.teamHomeName"
+            :teamAwayName="ps.teamAwayName" :dateTime="ps.dateTime" :events="ps.events ?? []"
+            :selected="ps.selected ?? []" />
+
+          <MatchCard platform="ob" :eventId="ob.eventId" :leagueName="ob.leagueName" :teamHomeName="ob.teamHomeName"
+            :teamAwayName="ob.teamAwayName" :dateTime="ob.dateTime" :events="ob.events ?? []"
+            :selected="ob.selected ?? []" />
+        </div>
+      </div>
+      <div class="profit-info">
+        <table>
+          <tr>
+            <th></th>
+            <td>JC</td>
+            <td colspan="2">第一场</td>
+            <td colspan="2">第二场</td>
+          </tr>
+          <tr>
+            <th>赔率</th>
+            <td>{{ totalProfitValue.jcInfo[0]?.v }}: {{ totalProfitValue.jcInfo[1]?.v }}</td>
+            <td>{{ totalProfitValue.outPreSol[0]?.p }}: {{ totalProfitValue.outPreSol[0]?.v }}</td>
+            <td>{{ totalProfitValue.outPreSol[1]?.p }}: {{ totalProfitValue.outPreSol[1]?.v }}</td>
+            <td>{{ totalProfitValue.outSubSol[0]?.p }}: {{ totalProfitValue.outSubSol[0]?.v }}</td>
+            <td>{{ totalProfitValue.outSubSol[1]?.p }}: {{ totalProfitValue.outSubSol[1]?.v }}</td>
+          </tr>
+          <tr>
+            <th>下注</th>
+            <td>{{ jcOptions.bet }}</td>
+            <td>{{ totalProfitValue.outPreSol[0]?.g }}</td>
+            <td>{{ totalProfitValue.outPreSol[1]?.g }}</td>
+            <td>{{ totalProfitValue.outSubSol[0]?.g }}</td>
+            <td>{{ totalProfitValue.outSubSol[1]?.g }}</td>
+          </tr>
+          <tr>
+            <th>利润</th>
+            <td>{{ totalProfitValue.profit.win_jc }}</td>
+            <td colspan="2">{{ totalProfitValue.profit.win_target }}</td>
+            <td colspan="2">{{ totalProfitValue.profit.win_target }}</td>
+          </tr>
+        </table>
       </div>
     </Drawer>
 
@@ -407,39 +506,62 @@ onUnmounted(() => {
   padding-top: 74px;
 }
 
+.solution-item {
+  display: flex;
+  .match-card {
+    flex: 1;
+  }
+}
+
 .solution-list {
   padding: 20px;
   overflow: hidden;
-}
 
-.solution-item {
-  display: flex;
-  border-radius: 10px;
-  background-color: hsl(var(--card));
+  .solution-item {
+    border-radius: 10px;
+    background-color: hsl(var(--card));
 
-  &.selected {
-    background-color: hsl(var(--primary) / 0.15);
-  }
+    &.selected {
+      background-color: hsl(var(--primary) / 0.15);
+    }
 
-  &.disabled {
-    opacity: 0.5;
-    cursor: not-allowed;
-  }
+    &.disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
 
-  &:not(:last-child) {
-    margin-bottom: 20px;
-  }
+    &:not(:last-child) {
+      margin-bottom: 20px;
+    }
 
-  .match-card {
-    flex: 1;
-    border-right: 1px solid hsl(var(--border));
+    .match-card {
+      border-right: 1px solid hsl(var(--border));
+    }
+
+    .solution-profit {
+      display: flex;
+      width: 80px;
+      align-items: center;
+      justify-content: center;
+    }
   }
+}
 
-  .solution-profit {
-    display: flex;
-    width: 80px;
-    align-items: center;
-    justify-content: center;
+.profit-info {
+  table {
+    width: 100%;
+    border-collapse: collapse;
+    border-spacing: 0;
+    table-layout: fixed;
+    th, td {
+      height: 30px;
+      border: 1px solid hsl(var(--border));
+      text-align: center;
+    }
+    th {
+      width: 64px;
+      font-weight: normal;
+    }
   }
 }