flyzto 7 maanden geleden
bovenliggende
commit
8623fc46a1

+ 106 - 3
server/models/Games.js

@@ -15,9 +15,23 @@ const Request = {
 
 const GAMES = {
   List: {},
-  Relations: {}
+  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) {
@@ -93,6 +107,9 @@ const updateGamesList = (({ platform, mk, games } = {}) => {
   });
 });
 
+/**
+ * 更新比赛盘口
+ */
 const updateGamesEvents = ({ platform, mk, games, outrights }) => {
   return new Promise((resolve, reject) => {
     if (!platform || (!games && !outrights)) {
@@ -135,6 +152,9 @@ const updateGamesEvents = ({ platform, mk, games, outrights }) => {
   });
 }
 
+/**
+ * 获取比赛列表
+ */
 const getGamesList = () => {
   const gamesListMap = {};
   Object.keys(GAMES.List).forEach(platform => {
@@ -154,6 +174,9 @@ const getGamesList = () => {
   return gamesListMap;
 }
 
+/**
+ * 更新关联比赛
+ */
 const updateGamesRelation = async (relation) => {
   const { id, rel } = relation;
   return Relation.findOne({ id })
@@ -170,6 +193,9 @@ const updateGamesRelation = async (relation) => {
   });
 }
 
+/**
+ * 删除关联比赛
+ */
 const removeGamesRelation = async (id) => {
   if (!id) {
     return Promise.reject(new Error('ID_INVALID'));
@@ -181,6 +207,9 @@ const removeGamesRelation = async (id) => {
   });
 }
 
+/**
+ * 获取关联比赛
+ */
 const getGamesRelation = async (listEvents) => {
   const relationIds = Object.keys(GAMES.Relations);
   if (listEvents) {
@@ -203,6 +232,9 @@ const getGamesRelation = async (listEvents) => {
   });
 }
 
+/**
+ * 清理关联比赛
+ */
 const relationsCleanup = () => {
   const expireTime = Date.now() - 1000*60*5;
   getGamesRelation()
@@ -221,6 +253,9 @@ const relationsCleanup = () => {
   });
 }
 
+/**
+ * 从数据库中同步关联比赛
+ */
 const relationSync = () => {
   Relation.find().then(relation => relation.forEach(item => {
     const { id, rel } = item.toObject();
@@ -228,6 +263,69 @@ const relationSync = () => {
   }));
 }
 
+/**
+ * 更新中单方案
+ */
+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 = () => {
+  return Object.values(GAMES.Solutions).sort((a, b) => b.sol.win_average - a.sol.win_average);
+}
+
+/**
+ * 清理中单方案
+ */
+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;
@@ -248,8 +346,8 @@ events_child.on('message', async (message) => {
     events_child.send({ type: 'response', id, data: responseData });
   }
   else if (method == 'post') {
-    if (type == 'setSolution') {
-      setSolution(data);
+    if (type == 'setSolutions') {
+      setSolutions(data);
     }
   }
   else if (method == 'response' && id && callbacks[id]) {
@@ -263,7 +361,12 @@ setInterval(() => {
   relationsCleanup();
 }, 5000);
 
+setInterval(() => {
+  solutionsCleanup();
+}, 1000*30);
+
 module.exports = {
   updateGamesList, updateGamesEvents, getGamesList,
   updateGamesRelation, getGamesRelation, removeGamesRelation,
+  getSolutions,
 }

+ 8 - 2
server/routes/triangle.js

@@ -36,7 +36,7 @@ router.get('/get_games_list', (req, res) => {
 });
 
 // 保存关联比赛
-router.post('/update_games_relation', (req, res) => {
+router.post('/update_games_relation', authMiddleware, (req, res) => {
   const relation = req.body;
   Games.updateGamesRelation(relation)
   .then(ret => {
@@ -48,7 +48,7 @@ router.post('/update_games_relation', (req, res) => {
 });
 
 // 删除关联比赛
-router.post('/remove_games_relation', (req, res) => {
+router.post('/remove_games_relation', authMiddleware, (req, res) => {
   const { id } = req.body;
   Games.removeGamesRelation(id)
   .then(ret => {
@@ -70,5 +70,11 @@ router.get('/get_games_relation', (req, res) => {
   });
 });
 
+// 获取中单方案
+router.get('/get_solutions', authMiddleware, (req, res) => {
+  const solutions = Games.getSolutions();
+  res.sendSuccess(solutions);
+});
+
 
 module.exports = router;

+ 1 - 51
server/triangle/eventsMatch.js

@@ -146,34 +146,7 @@ const eventMatch = () => {
     });
 
     const solutions = eventsCombination(passableEvents);
-    if (solutions?.length) {
-      const solutionsHistory = GLOBAL_DATA.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));
-      }
-    }
+    postDataToParent('setSolutions', solutions);
   })
   .finally(() => {
     setTimeout(() => {
@@ -182,27 +155,4 @@ const eventMatch = () => {
   });
 };
 
-const solutionsCleanup = () => {
-  const solutionsHistory = GLOBAL_DATA.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);
-    }
-  });
-}
-
-setInterval(() => {
-  solutionsCleanup();
-}, 1000*30);
-
 eventMatch();

+ 21 - 314
web/apps/web-antd/src/views/match/center-order/index.vue

@@ -1,148 +1,31 @@
 <script setup>
 import { Page } from '@vben/common-ui';
-import { ref } from 'vue';
-import { Tag, Card, Typography, Divider, Row, Col } from 'ant-design-vue';
-
-const { Text } = Typography;
-
-// 样例数据
-const centerOrders = ref([
-  {
-    "sid": "2031696_ior_rh_025_ior_rc_0_ior_mn",
-    "sol": {
-      "gold_home": 1000,
-      "gold_away": 678.97,
-      "gold_special": 200.31,
-      "odds_home": 0.84,
-      "odds_away": 1.71,
-      "odds_special": 2.3,
-      "win_home": -39.28,
-      "win_away": -39.27,
-      "win_special": -39.29,
-      "win_average": -39.28,
-    },
-    "cpr": [
-      {
-        "k": "ior_rh_025",
-        "p": "ob",
-        "v": 1.84,
-        "o": {
-          "ps": 1.806,
-          "ob": 1.84
-        }
-      },
-      {
-        "k": "ior_rc_0",
-        "p": "ps",
-        "v": 2.71,
-        "o": {
-          "ps": 2.71,
-          "ob": 2.51
-        }
-      },
-      {
-        "k": "ior_mn",
-        "p": "jc",
-        "v": 3.3,
-        "o": {
-          "jc": 3.3,
-          "ps": 3.44,
-          "ob": 3.65
-        }
-      }
-    ],
-    "info": {
-      "leagueName": "意大利甲级联赛",
-      "teamHomeName": "亚特兰大",
-      "teamAwayName": "罗马",
-      "id": "2031696",
-      "timestamp": 1747075500000
-    },
-    "rule": "A:7",
-    "timestamp": 1747023599690
-  },
-  {
-    "sid": "2031697_ior_rh_025_ior_rc_0_ior_mn",
-    "sol": {
-      "gold_home": 800,
-      "gold_away": 578.97,
-      "gold_special": 300.21,
-      "odds_home": 0.92,
-      "odds_away": 1.65,
-      "odds_special": 2.1,
-      "win_home": -42.28,
-      "win_away": -42.27,
-      "win_special": -42.29,
-      "win_average": -42.28,
-
-    },
-    "cpr": [
-      {
-        "k": "ior_rh_025",
-        "p": "ob",
-        "v": 1.92,
-        "o": {
-          "ps": 1.86,
-          "ob": 1.92
-        }
-      },
-      {
-        "k": "ior_rc_0",
-        "p": "ps",
-        "v": 2.65,
-        "o": {
-          "ps": 2.65,
-          "ob": 2.45
-        }
-      },
-      {
-        "k": "ior_mn",
-        "p": "jc",
-        "v": 3.1,
-        "o": {
-          "jc": 3.1,
-          "ps": 3.24,
-          "ob": 3.35
-        }
-      }
-    ],
-    "info": {
-      "leagueName": "英格兰超级联赛",
-      "teamHomeName": "曼城",
-      "teamAwayName": "阿森纳",
-      "id": "2031697",
-      "timestamp": 1747175500000
-    },
-    "rule": "A:5",
-    "timestamp": 1747023599690
+import { requestClient } from '#/api/request';
+import { Button, message } from 'ant-design-vue';
+import { ref, reactive, computed, onMounted, useTemplateRef } from 'vue';
+import dayjs from 'dayjs';
+
+const getSolutions = async () => {
+  try {
+    const data = await requestClient.get('/triangle/get_solutions');
+    return data;
+  }
+  catch (error) {
+    console.error('Failed to fetch solutions:', error);
+    message.error('获取中单方案失败');
+    return [];
   }
-]);
-
-// 格式化时间戳为日期
-function formatDate(timestamp) {
-  const date = new Date(timestamp);
-  return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
 }
 
-// 获取平台标识
-function getPlatformName(code) {
-  const platforms = {
-    'ps': '平台A',
-    'ob': '平台B',
-    'jc': '竞彩'
-  };
-  return platforms[code] || code;
+const updateSolutions = async () => {
+  const solutions = await getSolutions();
+  console.log(solutions);
 }
 
-// 获取玩法名称
-function getBetTypeName(type) {
-  const types = {
-    'ior_rh_025': '让球(+0.25)',
-    'ior_rc_0': '大小球(0)',
-    'ior_mn': '独赢'
-  };
-  return types[type] || type;
-}
+onMounted(() => {
+  updateSolutions();
+});
+
 </script>
 
 <template>
@@ -153,187 +36,11 @@ function getBetTypeName(type) {
           <span>中单记录</span>
         </div>
       </div>
-      <div class="center-order-content">
-        <Card v-for="(order, index) in centerOrders" :key="order.sid" class="order-card" :bordered="true">
-          <div class="order-header">
-            <div class="match-teams">
-              <Tag color="blue">{{ order.info.leagueName }}</Tag>
-              <span class="team-names">{{ order.info.teamHomeName }} vs {{ order.info.teamAwayName }}</span>
-            </div>
-            <div class="match-info">
-              <Tag color="green">ID: {{ order.info.id }}</Tag>
-              <Tag color="orange">规则: {{ order.rule }}</Tag>
-              <Tag color="purple">比赛时间: {{ formatDate(order.info.timestamp) }}</Tag>
-              <Tag color="cyan">记录时间: {{ formatDate(order.timestamp) }}</Tag>
-            </div>
-          </div>
-
-          <Divider>投入金额与赔率</Divider>
 
-          <Row class="sol-info">
-            <Col :span="8">
-            <div class="sol-item">
-              <Text type="secondary">主队投入:</Text>
-              <Text strong>{{ order.sol.gold_home }}</Text>
-            </div>
-            <div class="sol-item">
-              <Text type="secondary">主队赔率:</Text>
-              <Text strong>{{ order.sol.odds_home }}</Text>
-            </div>
-            <div class="sol-item">
-              <Text type="secondary">主队收益:</Text>
-              <Text :type="order.sol.win_home >= 0 ? 'success' : 'danger'">{{ order.sol.win_home }}</Text>
-            </div>
-            </Col>
-            <Col :span="8">
-            <div class="sol-item">
-              <Text type="secondary">客队投入:</Text>
-              <Text strong>{{ order.sol.gold_away }}</Text>
-            </div>
-            <div class="sol-item">
-              <Text type="secondary">客队赔率:</Text>
-              <Text strong>{{ order.sol.odds_away }}</Text>
-            </div>
-            <div class="sol-item">
-              <Text type="secondary">客队收益:</Text>
-              <Text :type="order.sol.win_away >= 0 ? 'success' : 'danger'">{{ order.sol.win_away }}</Text>
-            </div>
-            </Col>
-            <Col :span="8">
-            <div class="sol-item">
-              <Text type="secondary">特殊投入:</Text>
-              <Text strong>{{ order.sol.gold_special }}</Text>
-            </div>
-            <div class="sol-item">
-              <Text type="secondary">特殊赔率:</Text>
-              <Text strong>{{ order.sol.odds_special }}</Text>
-            </div>
-            <div class="sol-item">
-              <Text type="secondary">特殊收益:</Text>
-              <Text :type="order.sol.win_special >= 0 ? 'success' : 'danger'">{{ order.sol.win_special }}</Text>
-            </div>
-            </Col>
-          </Row>
-
-          <div class="sol-summary">
-            <Text type="secondary">平均收益:</Text>
-            <Text :type="order.sol.win_average >= 0 ? 'success' : 'danger'" strong>{{ order.sol.win_average }}</Text>
-            <Text v-if="order.sol.reverse" type="warning" class="reverse-flag">逆向</Text>
-          </div>
-
-          <Divider>比赛投注数据</Divider>
-
-          <div class="cpr-container">
-            <Card v-for="(bet, betIndex) in order.cpr" :key="betIndex" class="bet-card" size="small">
-              <div class="bet-header">
-                <Tag color="blue">{{ getBetTypeName(bet.k) }}</Tag>
-                <Tag color="green">{{ getPlatformName(bet.p) }}</Tag>
-                <Text strong>赔率: {{ bet.v }}</Text>
-              </div>
-              <div class="bet-details">
-                <div v-for="(oddValue, oddKey) in bet.o" :key="oddKey" class="odd-item">
-                  <Text type="secondary">{{ getPlatformName(oddKey) }}:</Text>
-                  <Text :strong="oddKey === bet.p">{{ oddValue }}</Text>
-                </div>
-              </div>
-            </Card>
-          </div>
-        </Card>
-      </div>
     </div>
   </Page>
 </template>
 
 <style lang="scss" scoped>
-.center-order-container {
-  padding: 16px;
-
-  .order-card {
-    margin-bottom: 20px;
-
-    .order-header {
-      display: flex;
-      justify-content: space-between;
-      flex-wrap: wrap;
-      margin-bottom: 12px;
-
-      .match-teams {
-        font-size: 16px;
 
-        .team-names {
-          margin-left: 8px;
-          font-weight: bold;
-        }
-      }
-
-      .match-info {
-        :deep(.ant-tag) {
-          margin-right: 8px;
-        }
-      }
-    }
-
-    .sol-info {
-      margin-bottom: 16px;
-
-      .sol-item {
-        margin-bottom: 8px;
-        display: flex;
-        justify-content: space-between;
-        padding: 0 16px;
-      }
-    }
-
-    .sol-summary {
-      text-align: right;
-      font-size: 16px;
-      margin-top: 8px;
-      padding: 8px 16px;
-      background-color: #f8f8f8;
-      border-radius: 4px;
-
-      .reverse-flag {
-        margin-left: 16px;
-        font-weight: bold;
-      }
-    }
-
-    .cpr-container {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 16px;
-
-      .bet-card {
-        width: calc(33.33% - 11px);
-
-        .bet-header {
-          margin-bottom: 8px;
-          display: flex;
-          align-items: center;
-          gap: 8px;
-        }
-
-        .bet-details {
-          .odd-item {
-            display: flex;
-            justify-content: space-between;
-            margin-bottom: 4px;
-          }
-        }
-      }
-    }
-  }
-}
-
-@media (max-width: 768px) {
-  .center-order-container {
-    .order-card {
-      .cpr-container {
-        .bet-card {
-          width: 100%;
-        }
-      }
-    }
-  }
-}
 </style>

+ 1 - 1
web/apps/web-antd/src/views/match/related/index.vue

@@ -1,7 +1,7 @@
 <script setup>
 import { Page } from '@vben/common-ui';
 import { requestClient } from '#/api/request';
-import { Button, Modal, message } from 'ant-design-vue';
+import { Button, message } from 'ant-design-vue';
 import { ref, reactive, computed, onMounted, useTemplateRef } from 'vue';
 import dayjs from 'dayjs';
 import MatchItem from '../components/match_item.vue';