2
0
Эх сурвалжийг харах

将答题卡临时数据迁移到Redis

yourname 7 сар өмнө
parent
commit
aa8a6a1c1d

+ 2 - 1
client/mobile/components/Exam/ExamAdmin.tsx

@@ -192,6 +192,7 @@ export default function ExamAdmin() {
   };
 
   const handleRestart = async () => {
+    if (!classroom) return;
     try {
       await answerManagement.cleanupRoom(classroom);
       setAnswers([]);
@@ -343,7 +344,7 @@ export default function ExamAdmin() {
       key: 'cumulative',
       label: '累计结果',
       children: <CumulativeResults
-        results={calculateCumulativeResults(answers)}
+        results={calculateCumulativeResults(dailyAnswers)}
         columns={resultColumns}
       />,
     },

+ 42 - 0
deno.lock

@@ -1,12 +1,30 @@
 {
   "version": "5",
   "specifiers": {
+    "jsr:@std/async@1": "1.0.13",
+    "jsr:@std/bytes@1": "1.0.6",
+    "jsr:@std/bytes@^1.0.2-rc.3": "1.0.6",
+    "jsr:@std/io@0.224.5": "0.224.5",
     "npm:@alicloud/live-interaction20201214@2.1.6": "2.1.6",
     "npm:@alicloud/live20161101@^1.1.1": "1.1.1",
     "npm:@alicloud/openapi-client@~0.4.14": "0.4.14",
     "npm:@alicloud/pop-core@1.8.0": "1.8.0",
     "npm:@types/node@*": "22.12.0"
   },
+  "jsr": {
+    "@std/async@1.0.13": {
+      "integrity": "1d76ca5d324aef249908f7f7fe0d39aaf53198e5420604a59ab5c035adc97c96"
+    },
+    "@std/bytes@1.0.6": {
+      "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a"
+    },
+    "@std/io@0.224.5": {
+      "integrity": "cb84fe655d1273fca94efcff411465027a8b0b4225203f19d6ee98d9c8920a2d",
+      "dependencies": [
+        "jsr:@std/bytes@^1.0.2-rc.3"
+      ]
+    }
+  },
   "npm": {
     "@alicloud/credentials@2.4.3": {
       "integrity": "sha512-r2thNtthchTz/c8/HryGSey1vY0UZx2FkAvb+vd+j7xhD/v/KUwnp8RJNQKNG3E4kfs4wSx2bgDSkcPAiXHQLQ==",
@@ -291,6 +309,7 @@
     "https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts",
     "https://deno.land/std/uuid/mod.ts": "https://deno.land/std@0.224.0/uuid/mod.ts",
     "https://deno.land/x/denodb/mod.ts": "https://deno.land/x/denodb@v1.4.0/mod.ts",
+    "https://deno.land/x/redis/mod.ts": "https://deno.land/x/redis@v0.40.0/mod.ts",
     "https://esm.d8d.fun/@deno/shim-deno-test@^0.5.0?target=denonext": "https://esm.d8d.fun/@deno/shim-deno-test@0.5.0?target=denonext",
     "https://esm.d8d.fun/@deno/shim-deno@~0.18.0?target=denonext": "https://esm.d8d.fun/@deno/shim-deno@0.18.2?target=denonext",
     "https://esm.d8d.fun/@socket.io/component-emitter@~3.1.0?target=denonext": "https://esm.d8d.fun/@socket.io/component-emitter@3.1.2?target=denonext",
@@ -464,6 +483,29 @@
     "https://deno.land/x/deno_dom@v0.1.48/src/dom/utils-types.ts": "96db30e3e4a75b194201bb9fa30988215da7f91b380fca6a5143e51ece2a8436",
     "https://deno.land/x/deno_dom@v0.1.48/src/dom/utils.ts": "4c6206516fb8f61f37a209c829e812c4f5a183e46d082934dd14c91bde939263",
     "https://deno.land/x/deno_dom@v0.1.48/src/parser.ts": "e06b2300d693e6ae7564e53dfa5c9a9e97fdb8c044c39c52c8b93b5d60860be3",
+    "https://deno.land/x/redis@v0.40.0/backoff.ts": "33e4a6e245f8743fbae0ce583993a671a3ac2ecee433a3e7f0bd77b5dd541d84",
+    "https://deno.land/x/redis@v0.40.0/client.ts": "a64d010d6be7c05ec73002506ca19210ec8bf336987e2b954a12f7b8bdb395c7",
+    "https://deno.land/x/redis@v0.40.0/command.ts": "361d6509463a6c28c12195416c31de08d170fbe3703004f3c915fb7191f36853",
+    "https://deno.land/x/redis@v0.40.0/connection.ts": "0cb6b4f006051106229b0dbfa09be2ca3b65fec18899c4019b4ffbcdd549f14f",
+    "https://deno.land/x/redis@v0.40.0/deps/std/async.ts": "5a588aefb041cca49f0e6b7e3c397119693a3e07bea89c54cf7fe4a412e37bbf",
+    "https://deno.land/x/redis@v0.40.0/deps/std/bytes.ts": "f5b437ebcac77600101a81ef457188516e4944b3c2a931dff5ced3fa0c239b62",
+    "https://deno.land/x/redis@v0.40.0/deps/std/io.ts": "b7505c5e738384f5f7a021d7bbd78380490c059cc7c83cd8dada1f86ec16e835",
+    "https://deno.land/x/redis@v0.40.0/errors.ts": "8293f56a70ea8388cb80b6e1caa15d350ed1719529fc06573b01a443d0caad69",
+    "https://deno.land/x/redis@v0.40.0/events.ts": "704767b1beed2d5acfd5e86bd1ef93befdc8a8f8c8bb4ae1b4485664a8a6a625",
+    "https://deno.land/x/redis@v0.40.0/executor.ts": "b8935fa48f53032a37590606e9448fb10fb2dd1f537c5851ba0f45ae615b1973",
+    "https://deno.land/x/redis@v0.40.0/internal/encoding.ts": "0525f7f444a96b92cd36423abdfe221f8d8de4a018dc5cb6750a428a5fc897c2",
+    "https://deno.land/x/redis@v0.40.0/internal/symbols.ts": "e36097bab1da1c9fe84a3bb9cb0ed1ec10c3dc7dd0b557769c5c54e15d110dd2",
+    "https://deno.land/x/redis@v0.40.0/mod.ts": "48e4c8d4c5522766ec2a11059a4f3c48bf60ec618fca6444734beeafda308d9f",
+    "https://deno.land/x/redis@v0.40.0/pipeline.ts": "a32e5f1061982819e97af6ee15f76f75b7edb370800f612b66531558e94b5281",
+    "https://deno.land/x/redis@v0.40.0/protocol/deno_streams/command.ts": "5c5e5fb639cae22c1f9bfdc87631edcd67bb28bf8590161ae484d293b733aa01",
+    "https://deno.land/x/redis@v0.40.0/protocol/deno_streams/mod.ts": "b084bf64d6b795f6c1d0b360d9be221e246a9c033e5d88fd1e82fa14f711d25b",
+    "https://deno.land/x/redis@v0.40.0/protocol/deno_streams/reply.ts": "639de34541f207f793393a3cd45f9a23ef308f094d9d3d6ce62f84b175d3af47",
+    "https://deno.land/x/redis@v0.40.0/protocol/shared/command.ts": "e75f6be115ff73bd865e01be4e2a28077a9993b1e0c54ed96b6825bfe997d382",
+    "https://deno.land/x/redis@v0.40.0/protocol/shared/protocol.ts": "5b9284ee28ec74dfc723c7c7f07dca8d5f9d303414f36689503622dfdde12551",
+    "https://deno.land/x/redis@v0.40.0/protocol/shared/reply.ts": "3311ff66357bacbd60785cb43b97539c341d8a7d963bc5e80cb864ac81909ea5",
+    "https://deno.land/x/redis@v0.40.0/protocol/shared/types.ts": "c6bf2b9eafd69e358a972823d94b8b478c00bac195b87b33b7437de2a9bb7fb4",
+    "https://deno.land/x/redis@v0.40.0/redis.ts": "1f42f34578b318910e099dd6312e142cbb01d80660944675d31bd0ca739a0fff",
+    "https://deno.land/x/redis@v0.40.0/stream.ts": "d43076815d046eb8428fcd2799544a9fd07b3480099f5fc67d2ba12fdc73725f",
     "https://deno.land/x/socket_io@0.2.1/deps.ts": "2c9c7fd0f00c9f8774a7cbf6bea2e73b274989aacb3ebfbd289a3c1bbe632bcb",
     "https://deno.land/x/socket_io@0.2.1/mod.ts": "29050911ca6f9605623c672238bb209ca37ed23606c596d199e6e33de04168f9",
     "https://deno.land/x/socket_io@0.2.1/packages/engine.io-parser/base64-arraybuffer.ts": "57ccea6679609df5416159fcc8a47936ad28ad6fe32235ef78d9223a3a823407",

+ 39 - 88
server/routes_io_exam.ts

@@ -172,23 +172,19 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
         return;
       }
 
-      // 存储答案
-      await apiClient.database.table('exam_answers').insert({
-        room_id: roomId,
-        question_id: questionId,
-        user_id: user.id,
+      // 存储答案到Redis
+      const answerKey = `exam:${roomId}:${questionId}:answers`;
+      await apiClient.redis.hset(answerKey, String(user.id), JSON.stringify({
         username: user.username,
         date: answer.date,
         price: answer.price,
-        holding_stock: answer.holdingStock,
-        holding_cash: answer.holdingCash,
-        profit_amount: answer.profitAmount,
-        profit_percent: answer.profitPercent,
-        total_profit_amount: answer.totalProfitAmount,
-        total_profit_percent: answer.totalProfitPercent,
-        created_at: apiClient.database.fn.now(),
-        updated_at: apiClient.database.fn.now()
-      });
+        holdingStock: answer.holdingStock,
+        holdingCash: answer.holdingCash,
+        profitAmount: answer.profitAmount,
+        profitPercent: answer.profitPercent,
+        totalProfitAmount: answer.totalProfitAmount,
+        totalProfitPercent: answer.totalProfitPercent
+      }));
 
       // 广播答案更新到房间
       socket.to(roomId).emit('exam:answerUpdated', {
@@ -219,24 +215,18 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
         return;
       }
 
-      // 查询答案
-      const answers = await apiClient.database.table('exam_answers')
-        .where('room_id', roomId)
-        .where('question_id', questionId)
-        .select('*');
+      // 从Redis获取答案
+      const answerKey = `exam:${roomId}:${questionId}:answers`;
+      const answers = await apiClient.redis.hgetall(answerKey);
 
       // 转换为前端需要的格式
-      const formattedAnswers: Answer[] = answers.map((a: any) => ({
-        date: a.date,
-        price: a.price,
-        holdingStock: a.holding_stock,
-        holdingCash: a.holding_cash,
-        profitAmount: a.profit_amount,
-        profitPercent: a.profit_percent,
-        totalProfitAmount: a.total_profit_amount,
-        totalProfitPercent: a.total_profit_percent,
-        userId: a.user_id
-      }));
+      const formattedAnswers: Answer[] = Object.entries(answers).map(([userId, answerStr]) => {
+        const answer = JSON.parse(answerStr);
+        return {
+          ...answer,
+          userId
+        };
+      });
 
       callback(formattedAnswers);
     } catch (error) {
@@ -262,23 +252,17 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
 
       if (questionId) {
         // 清理特定问题的数据
-        await apiClient.database.table('exam_answers')
-          .where('room_id', roomId)
-          .where('question_id', questionId)
-          .delete();
+        const answerKey = `exam:${roomId}:${questionId}:answers`;
+        const keys = await apiClient.redis.keys(answerKey);
+        if (keys.length > 0) {
+          await Promise.all(keys.map((key) => apiClient.redis.del(key)))
+        }
       } else {
         // 清理整个房间的数据
-        await apiClient.database.table('exam_answers')
-          .where('room_id', roomId)
-          .delete();
-          
-        await apiClient.database.table('exam_questions')
-          .where('room_id', roomId)
-          .delete();
-          
-        await apiClient.database.table('exam_prices')
-          .where('room_id', roomId)
-          .delete();
+        const keys = await apiClient.redis.keys(`exam:${roomId}:*`);
+        if (keys.length > 0) {
+          await Promise.all(keys.map((key) => apiClient.redis.del(key)))
+        }
       }
 
       socket.emit('exam:cleaned', {
@@ -306,31 +290,9 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
         return;
       }
 
-      // 检查是否已存在该日期的价格
-      const existing = await apiClient.database.table('exam_prices')
-        .where('room_id', roomId)
-        .where('date', date)
-        .first();
-
-      if (existing) {
-        // 更新现有价格
-        await apiClient.database.table('exam_prices')
-          .where('room_id', roomId)
-          .where('date', date)
-          .update({
-            price,
-            updated_at: apiClient.database.fn.now()
-          });
-      } else {
-        // 插入新价格
-        await apiClient.database.table('exam_prices').insert({
-          room_id: roomId,
-          date,
-          price,
-          created_at: apiClient.database.fn.now(),
-          updated_at: apiClient.database.fn.now()
-        });
-      }
+      // 存储价格到Redis
+      const priceKey = `exam:${roomId}:prices`;
+      await apiClient.redis.hset(priceKey, date, price);
 
       console.log(`用户 ${user.username} 存储房间 ${roomId} 的价格历史: ${date} - ${price}`);
     } catch (error) {
@@ -353,14 +315,10 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
         return;
       }
 
-      // 查询价格
-      const priceData = await apiClient.database.table('exam_prices')
-        .where('room_id', roomId)
-        .where('date', date)
-        .select('price')
-        .first();
-
-      callback(priceData?.price || '0');
+      // 从Redis获取价格
+      const priceKey = `exam:${roomId}:prices`;
+      const price = await apiClient.redis.hget(priceKey, date);
+      callback(price || '0');
     } catch (error) {
       console.error('获取历史价格失败:', error);
       socket.emit('error', '获取历史价格失败');
@@ -381,16 +339,9 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
         return;
       }
 
-      // 查询所有价格
-      const prices = await apiClient.database.table('exam_prices')
-        .where('room_id', roomId)
-        .select('date', 'price');
-
-      // 转换为日期-价格的映射
-      const priceMap: Record<string, string> = {};
-      prices.forEach((p: any) => {
-        priceMap[p.date] = p.price;
-      });
+      // 从Redis获取所有价格
+      const priceKey = `exam:${roomId}:prices`;
+      const priceMap = await apiClient.redis.hgetall(priceKey);
 
       callback(priceMap);
     } catch (error) {