zlwu hace 9 meses
padre
commit
f17821c9af

+ 16 - 7
backend/app/api/client/keys.py

@@ -50,10 +50,14 @@ async def get_public_key(
                 "timestamp": TimestampValidator.generate_timestamp()
             }
 
-        # 2. 获取当前激活的密钥
-        key = db.query(APIKey).filter(APIKey.is_active == 1).first()
+        # 2. 获取当前激活的密钥(主is_active,辅expire_at)
+        key = db.query(APIKey).filter(
+            APIKey.is_active == 1,
+            APIKey.is_deleted == 0,
+            APIKey.expire_at > beijing_now()
+        ).order_by(APIKey.key_version.desc()).first()
         if not key:
-            logger.error("未找到激活的密钥")
+            logger.error("未找到激活的有效密钥")
             return {
                 "code": 404,
                 "msg": "系统密钥服务暂不可用",
@@ -152,16 +156,21 @@ async def get_key_version(db: Session = Depends(get_db)):
     获取当前密钥版本信息
     返回当前激活的密钥版本,用于客户端配置验证
     """
-    # 获取当前激活的密钥
-    key = db.query(APIKey).filter(APIKey.is_active == 1).first()
+    # 获取当前激活的密钥(主is_active,辅expire_at)
+    key = db.query(APIKey).filter(
+        APIKey.is_active == 1,
+        APIKey.is_deleted == 0,
+        APIKey.expire_at > beijing_now()
+    ).order_by(APIKey.key_version.desc()).first()
 
     if not key:
-        return {"code": 404, "msg": "未找到激活的密钥"}
+        return {"code": 404, "msg": "未找到激活的有效密钥"}
 
     # 获取所有可用的密钥版本列表(包括当前激活的和新预热的)
     available_keys = db.query(APIKey).filter(
+        APIKey.is_active == 1,    # 必须激活
         APIKey.is_deleted == 0,
-        APIKey.expire_at.is_(None) | (APIKey.expire_at > beijing_now())
+        APIKey.expire_at > beijing_now()  # 且未过期
     ).order_by(APIKey.key_version.desc()).all()
 
     available_versions = [k.key_version for k in available_keys]

+ 22 - 12
backend/app/background_tasks.py

@@ -9,6 +9,7 @@ from app.core.logger import logger
 from app.services.key_rotation_service import KeyRotationService
 from app.services.session_service import SessionService
 from app.services.heartbeat_monitor import HeartbeatMonitor
+from app.models.system_config import SystemConfig
 from app.config import settings
 
 
@@ -61,8 +62,11 @@ class BackgroundTaskManager:
         logger.info("后台定时任务已停止")
 
     async def _key_rotation_task(self):
-        """密钥轮换任务 - 每5分钟执行一次"""
-        logger.info("启动密钥轮换定时任务 (间隔: 5分钟)")
+        """密钥轮换任务 - 根据配置间隔执行"""
+        # 获取检查间隔配置
+        check_interval_minutes = SystemConfig.get_config_value('key_rotation_check_interval_minutes', 5)
+        check_interval_seconds = check_interval_minutes * 60
+        logger.info(f"启动密钥轮换定时任务 (间隔: {check_interval_minutes}分钟)")
 
         while self.running:
             try:
@@ -92,12 +96,15 @@ class BackgroundTaskManager:
             except Exception as e:
                 logger.exception("密钥轮换任务异常")
 
-            # 等待5分钟
-            await asyncio.sleep(300)  # 5分钟 = 300秒
+            # 根据配置等待指定时间
+            await asyncio.sleep(check_interval_seconds)
 
     async def _session_cleanup_task(self):
-        """Session清理任务 - 每2小时执行一次"""
-        logger.info("启动Session清理定时任务 (间隔: 2小时)")
+        """Session清理任务 - 根据配置间隔执行"""
+        # 获取清理间隔配置
+        cleanup_interval_hours = SystemConfig.get_config_value('session_cleanup_interval_hours', 2)
+        cleanup_interval_seconds = cleanup_interval_hours * 3600
+        logger.info(f"启动Session清理定时任务 (间隔: {cleanup_interval_hours}小时)")
 
         while self.running:
             try:
@@ -117,12 +124,15 @@ class BackgroundTaskManager:
             except Exception as e:
                 logger.exception("Session清理任务异常")
 
-            # 等待2小时
-            await asyncio.sleep(7200)  # 2小时 = 7200秒
+            # 根据配置等待指定时间
+            await asyncio.sleep(cleanup_interval_seconds)
 
     async def _heartbeat_cleanup_task(self):
-        """心跳监控缓存清理任务 - 每1小时执行一次"""
-        logger.info("启动心跳缓存清理定时任务 (间隔: 1小时)")
+        """心跳监控缓存清理任务 - 根据配置间隔执行"""
+        # 获取清理间隔配置
+        cleanup_interval_hours = SystemConfig.get_config_value('heartbeat_cleanup_interval_hours', 1)
+        cleanup_interval_seconds = cleanup_interval_hours * 3600
+        logger.info(f"启动心跳缓存清理定时任务 (间隔: {cleanup_interval_hours}小时)")
 
         while self.running:
             try:
@@ -144,8 +154,8 @@ class BackgroundTaskManager:
             except Exception as e:
                 logger.exception("心跳缓存清理任务异常")
 
-            # 等待1小时
-            await asyncio.sleep(3600)  # 1小时 = 3600秒
+            # 根据配置等待指定时间
+            await asyncio.sleep(cleanup_interval_seconds)
 
 
 # 全局后台任务管理器实例

+ 12 - 8
backend/app/core/encrypt.py

@@ -17,6 +17,7 @@ from app.core.logger import logger
 from app.database import get_db
 from app.models.api_key import APIKey
 from app.utils.timestamp_validator import TimestampValidator
+from app.utils.timezone import beijing_now
 
 class RSAEncryptor:
     """RSA+AES混合加密器 - 支持多版本密钥"""
@@ -66,21 +67,23 @@ class RSAEncryptor:
             raise
 
     def load_key_by_version(self, db: Session, key_version: int) -> bool:
-        """根据版本号加载密钥"""
+        """根据版本号加载密钥(主is_active,辅expire_at)"""
         try:
             if key_version in self.key_cache:
                 private_pem, _ = self.key_cache[key_version]
                 self.load_private_key(private_pem, key_version)
                 return True
 
+            # 查找指定版本的密钥,必须激活、未删除且未过期
             api_key = db.query(APIKey).filter(
                 APIKey.key_version == key_version,
-                APIKey.is_active == 1,
-                APIKey.is_deleted == 0
+                APIKey.is_active == 1,      # 必须激活
+                APIKey.is_deleted == 0,
+                APIKey.expire_at > beijing_now()  # 且未过期
             ).first()
 
             if not api_key:
-                logger.warning(f"未找到版本为 {key_version} 的密钥")
+                logger.warning(f"未找到有效的密钥版本 {key_version}(可能未激活、已过期或已删除)")
                 return False
 
             self.load_private_key(api_key.private_key, key_version)
@@ -92,17 +95,18 @@ class RSAEncryptor:
             return False
 
     def get_current_public_key(self, db: Session) -> Tuple[str, int]:
-        """获取当前激活的公钥"""
+        """获取当前激活的公钥(主is_active,辅expire_at)"""
         try:
             api_key = db.query(APIKey).filter(
-                APIKey.is_active == 1,
-                APIKey.is_deleted == 0
+                APIKey.is_active == 1,      # 必须是激活状态
+                APIKey.is_deleted == 0,
+                APIKey.expire_at > beijing_now()  # 且未过期
             ).order_by(APIKey.key_version.desc()).first()
 
             if api_key:
                 return api_key.public_key, api_key.key_version
 
-            logger.warning("未找到激活的公钥")
+            logger.warning("未找到激活的有效公钥")
             return "", 0
 
         except Exception as e:

+ 6 - 2
backend/app/core/init_keys.py

@@ -4,6 +4,7 @@
 from sqlalchemy.orm import Session
 from app.database import SessionLocal
 from app.models.api_key import APIKey
+from app.models.system_config import SystemConfig
 from app.core.encrypt import encryptor
 from app.core.logger import logger
 from datetime import datetime, timedelta
@@ -28,9 +29,12 @@ def init_keys():
         # 获取当前最大版本号
         max_version = db.query(APIKey).order_by(APIKey.key_version.desc()).first()
         next_version = (max_version.key_version + 1) if max_version else 1
-        
+
+        # 从system_config表获取初始化密钥过期天数配置
+        expire_days = SystemConfig.get_config_value('init_key_expire_days', 10)
+
         # 计算过期时间
-        expire_at = beijing_now() + timedelta(days=settings.KEY_ROTATION_DAYS)
+        expire_at = beijing_now() + timedelta(days=expire_days)
         
         # 创建密钥记录
         api_key = APIKey(

+ 7 - 20
backend/app/services/key_rotation_service.py

@@ -35,17 +35,14 @@ class KeyRotationService:
         try:
             config = KeyRotationService.get_config_dict()
 
-            # 检查是否有激活的密钥
+            # 检查是否有激活的有效密钥(主is_active,辅expire_at)
             active_key = db.query(APIKey).filter(
                 and_(
                     APIKey.is_active == 1,
                     APIKey.is_deleted == 0,
-                    or_(
-                        APIKey.expire_at.is_(None),
-                        APIKey.expire_at > beijing_now()
-                    )
+                    APIKey.expire_at > beijing_now()  # 未过期
                 )
-            ).first()
+            ).order_by(APIKey.key_version.desc()).first()
 
             if not active_key:
                 logger.info("没有激活的密钥,需要生成新密钥")
@@ -107,17 +104,6 @@ class KeyRotationService:
                 created_at=beijing_now()
             )
 
-            # 停用旧密钥(如果存在)
-            db.query(APIKey).filter(
-                and_(
-                    APIKey.is_active == 1,
-                    APIKey.is_deleted == 0
-                )
-            ).update({
-                "is_active": 0,
-                "updated_at": beijing_now()
-            })
-
             # 保存新密钥
             db.add(new_key)
             db.commit()
@@ -173,14 +159,15 @@ class KeyRotationService:
 
     @staticmethod
     def get_active_key_version(db: Session) -> int:
-        """获取当前激活密钥的版本"""
+        """获取当前激活密钥的版本(主is_active,辅expire_at)"""
         try:
             active_key = db.query(APIKey).filter(
                 and_(
                     APIKey.is_active == 1,
-                    APIKey.is_deleted == 0
+                    APIKey.is_deleted == 0,
+                    APIKey.expire_at > beijing_now()
                 )
-            ).first()
+            ).order_by(APIKey.key_version.desc()).first()
 
             return active_key.key_version if active_key else 0
 

+ 21 - 20
backend/create_system_config.sql

@@ -38,23 +38,24 @@ CREATE TABLE IF NOT EXISTS `sessions` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户会话表';
 
 -- 插入默认配置数据(纵表结构)
-INSERT INTO `system_config` (`config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`) VALUES
-('heartbeat_interval_seconds', '30', 'int', '心跳间隔秒数(默认30秒)', 1, 1),
-('key_overlap_minutes', '30', 'int', '新旧密钥共存分钟数', 1, 1),
-('key_expire_minutes', '60', 'int', '密钥过期分钟数(从创建时间算起)', 1, 1),
-('max_heartbeat_failures', '10', 'int', '最大心跳失败次数,超过后暂停检测', 1, 1),
-('max_network_failures', '15', 'int', '网络异常最大失败次数,超过后登出', 1, 1),
-('prevent_duplicate_login', '1', 'bool', '是否防止重复登录:0-否,1-是', 1, 1),
-('timestamp_tolerance_seconds', '300', 'int', '时间戳容忍秒数(前后5分钟)', 1, 1),
-('session_extend_enabled', '1', 'bool', '是否启用心跳延长session:0-否,1-是', 1, 1),
-('session_extend_threshold_hours', '4', 'int', 'session延长阈值(小时),距离过期小于此值时延长', 1, 1),
-('session_extend_hours', '2', 'int', '每次心跳延长的小时数', 1, 1),
-('api_timeout_seconds', '30', 'int', 'API请求超时秒数', 1, 1),
-('client_heartbeat_interval_seconds', '30', 'int', '客户端心跳间隔秒数', 1, 1)
-ON DUPLICATE KEY UPDATE
-    `config_value` = VALUES(`config_value`),
-    `config_type` = VALUES(`config_type`),
-    `description` = VALUES(`description`),
-    `is_system` = VALUES(`is_system`),
-    `is_editable` = VALUES(`is_editable`),
-    `updated_at` = NOW();
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (1, 'heartbeat_interval_seconds', '30', 'int', '心跳间隔秒数(默认30秒)', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (2, 'key_overlap_minutes', '30', 'int', '新旧密钥共存分钟数', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (3, 'key_expire_minutes', '60', 'int', '密钥过期分钟数(从创建时间算起)', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (4, 'max_heartbeat_failures', '10', 'int', '最大心跳失败次数,超过后暂停检测', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (5, 'max_network_failures', '15', 'int', '网络异常最大失败次数,超过后登出', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (6, 'prevent_duplicate_login', '1', 'bool', '是否防止重复登录:0-否,1-是', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (7, 'timestamp_tolerance_seconds', '30', 'int', '时间戳容忍秒数(前后5分钟)', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-07 23:47:41');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (8, 'session_extend_enabled', '1', 'bool', '是否启用心跳延长session:0-否,1-是', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (9, 'session_extend_threshold_hours', '4', 'int', 'session延长阈值(小时),距离过期小于此值时延长', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (10, 'session_extend_hours', '2', 'int', '每次心跳延长的小时数', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (11, 'api_timeout_seconds', '30', 'int', 'API请求超时秒数', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (12, 'client_heartbeat_interval_seconds', '30', 'int', '客户端心跳间隔秒数', 1, 1, 0, '2026-01-06 17:21:40', '2026-01-06 17:21:40');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (13, 'sweep_batch_size', '10', 'int', '扫货批量大小', 1, 1, 0, '2026-01-06 20:09:54', '2026-01-06 20:09:54');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (14, 'sweep_purchase_interval', '30', 'int', '扫货购买间隔秒数', 1, 1, 0, '2026-01-06 20:09:54', '2026-01-06 20:09:54');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (15, 'sweep_max_purchases', '100', 'int', '扫货最大购买数量', 1, 1, 0, '2026-01-06 20:09:54', '2026-01-06 20:09:54');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (16, 'page_size', '10', 'int', '分页每页显示数量', 1, 1, 0, '2026-01-06 20:09:54', '2026-01-06 20:09:54');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (17, 'steam_auth_countdown_seconds', '30', 'int', 'Steam验证倒计时秒数', 1, 1, 0, '2026-01-06 20:11:16', '2026-01-06 20:11:16');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (19, 'init_key_expire_days', '1', 'int', '初始化密钥过期天数(应用启动时创建的密钥)', 1, 1, 0, '2026-01-08 09:52:03', '2026-01-08 09:52:31');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (20, 'key_rotation_check_interval_minutes', '5', 'int', '密钥轮换检查间隔分钟数', 1, 1, 0, '2026-01-08 09:57:32', '2026-01-08 09:57:48');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (21, 'session_cleanup_interval_hours', '2', 'int', 'Session清理间隔小时数', 1, 1, 0, '2026-01-08 09:58:57', '2026-01-08 09:59:22');
+INSERT INTO `buff`.`system_config` (`id`, `config_key`, `config_value`, `config_type`, `description`, `is_system`, `is_editable`, `is_deleted`, `created_at`, `updated_at`) VALUES (22, 'heartbeat_cleanup_interval_hours', '1', 'int', '心跳缓存清理间隔小时数', 1, 1, 0, '2026-01-08 09:58:57', '2026-01-08 09:59:22');

+ 6 - 0
client-tauri/src-tauri/src/commands.rs

@@ -1546,3 +1546,9 @@ pub async fn secure_get_announcements(request: AnnouncementRequest) -> Result<An
         Err("No encrypted_data in response".to_string())
     }
 }
+
+#[tauri::command]
+    unsafe {
+        STORED_RSA_KEY_VERSION.ok_or("RSA公钥版本不存在".to_string())
+    }
+}

+ 82 - 0
client-tauri/src/components/LoginPage.vue

@@ -339,6 +339,10 @@ const publicKeyFetchTimer = ref<number | null>(null)
 const publicKeyFetchFailures = ref(0)
 const maxPublicKeyFetchFailures = 1000 // 最多重试1000次
 const publicKeyFetchInterval = 5000 // 5秒重试一次
+// 公钥定期检查相关
+const publicKeyCheckTimer = ref<number | null>(null)
+const publicKeyCheckInterval = 30000 // 30秒检查一次
+const currentKeyVersion = ref<number | null>(null) // 当前本地公钥版本
 // 公钥初始化状态
 const isPublicKeyInitializing = ref(false)
 const publicKeyInitialized = ref(false)
@@ -933,6 +937,9 @@ const fetchPublicKey = async () => {
 
     const { keyVersion, expireAt } = result
 
+    // 保存当前公钥版本
+    currentKeyVersion.value = keyVersion
+
     // 更新初始化状态
     publicKeyInitialized.value = true
     isPublicKeyInitializing.value = false
@@ -943,6 +950,9 @@ const fetchPublicKey = async () => {
     // 清除初始化状态提示
     showLoginStatus('', 'info', 0)
 
+    // 启动定期检查机制
+    startPeriodicKeyCheck()
+
   } catch (error) {
     console.error('公钥获取失败:', error)
     publicKeyFetchFailures.value++
@@ -981,6 +991,77 @@ const stopPublicKeyFetch = () => {
   }
 }
 
+// 定期检查公钥更新
+const checkPublicKeyUpdate = async () => {
+  try {
+    console.log('🔄 开始定期检查公钥更新...')
+
+    // 获取API配置
+    const baseUrl = await invoke<string>('get_api_base_url')
+    if (!baseUrl) {
+      console.warn('API baseUrl 为空,跳过公钥检查')
+      return
+    }
+
+    const httpClient = new HttpClient(baseUrl, 30000)
+
+    // 获取最新的公钥信息
+    const result = await httpClient.getPublicKey()
+    const { keyVersion, expireAt } = result
+
+    console.log('📊 检查结果: 本地版本:', currentKeyVersion.value, '服务端版本:', keyVersion)
+
+    // 比较版本
+    if (currentKeyVersion.value !== keyVersion) {
+      console.log('🔄 检测到公钥版本更新,本地:', currentKeyVersion.value, '-> 服务端:', keyVersion)
+
+      // 更新本地版本
+      currentKeyVersion.value = keyVersion
+
+      // 重新加载公告(因为公钥可能影响加密通信)
+      if (!loadingAnnouncements.value) {
+        console.log('🔄 公钥更新,重新加载公告')
+        loadAnnouncements()
+      }
+
+      console.log('✅ 公钥已更新到最新版本:', keyVersion)
+    } else {
+      console.log('✅ 公钥版本已是最新')
+    }
+
+  } catch (error) {
+    console.error('定期检查公钥更新失败:', error)
+    // 检查失败不影响正常功能,只是记录错误
+  }
+}
+
+// 启动定期公钥检查
+const startPeriodicKeyCheck = () => {
+  console.log('🚀 启动定期公钥检查')
+
+  if (publicKeyCheckTimer.value) {
+    console.log('⏰ 定期公钥检查已在运行')
+    return
+  }
+
+  console.log(`⏳ ${publicKeyCheckInterval / 1000}秒后开始定期检查`)
+
+  // 设置定时器,每30秒检查一次
+  publicKeyCheckTimer.value = window.setInterval(() => {
+    checkPublicKeyUpdate()
+  }, publicKeyCheckInterval)
+}
+
+// 停止定期公钥检查
+const stopPeriodicKeyCheck = () => {
+  if (publicKeyCheckTimer.value) {
+    clearInterval(publicKeyCheckTimer.value)
+    publicKeyCheckTimer.value = null
+    console.log('停止定期公钥检查')
+  }
+}
+
+
 // 监听公钥初始化状态变化
 watch(publicKeyInitialized, (newValue) => {
   console.log('👀 公钥初始化状态变化:', newValue)
@@ -1020,6 +1101,7 @@ onUnmounted(() => {
   stopBackendStatusCheck()
   stopAnnouncementAutoRefresh()
   stopPublicKeyFetch()
+  stopPeriodicKeyCheck()
 })
 </script>
 

+ 32 - 0
client-tauri/src/utils/httpClient.ts

@@ -10,6 +10,37 @@ export class HttpClient {
     this.timeout = timeout;
   }
 
+  /**
+   * 发送GET请求
+   */
+  async get(endpoint: string): Promise<any> {
+    const url = `${this.baseUrl}${endpoint}`;
+
+    try {
+      const controller = new AbortController();
+      const timeoutId = setTimeout(() => controller.abort(), this.timeout);
+
+      const response = await fetch(url, {
+        method: 'GET',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        signal: controller.signal,
+      });
+
+      clearTimeout(timeoutId);
+
+      if (!response.ok) {
+        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+      }
+
+      return await response.json();
+    } catch (error) {
+      console.error(`GET ${url} failed:`, error);
+      throw error;
+    }
+  }
+
   /**
    * 发送POST请求
    */
@@ -116,6 +147,7 @@ export class HttpClient {
     };
   }
 
+
   /**
    * AES解密
    */