Преглед на файлове

✨ feat: Add `controller/console_migrate.go` providing `/api/option/migrate_console_setting` endpoint for one-off data migration.

Apple\Apple преди 6 месеца
родител
ревизия
a9cdbce9de
променени са 3 файла, в които са добавени 143 реда и са изтрити 3 реда
  1. 88 0
      controller/console_migrate.go
  2. 1 0
      router/api-router.go
  3. 54 3
      web/src/components/settings/DashboardSetting.js

+ 88 - 0
controller/console_migrate.go

@@ -0,0 +1,88 @@
+// 用于迁移检测的旧键,该文件下个版本会删除
+
+package controller
+
+import (
+    "encoding/json"
+    "net/http"
+    "one-api/common"
+    "one-api/model"
+    "github.com/gin-gonic/gin"
+)
+
+// MigrateConsoleSetting 迁移旧的控制台相关配置到 console_setting.*
+func MigrateConsoleSetting(c *gin.Context) {
+    // 读取全部 option
+    opts, err := model.AllOption()
+    if err != nil {
+        c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()})
+        return
+    }
+    // 建立 map
+    valMap := map[string]string{}
+    for _, o := range opts {
+        valMap[o.Key] = o.Value
+    }
+
+    // 处理 APIInfo
+    if v := valMap["ApiInfo"]; v != "" {
+        var arr []map[string]interface{}
+        if err := json.Unmarshal([]byte(v), &arr); err == nil {
+            if len(arr) > 50 {
+                arr = arr[:50]
+            }
+            bytes, _ := json.Marshal(arr)
+            model.UpdateOption("console_setting.api_info", string(bytes))
+        }
+        model.UpdateOption("ApiInfo", "")
+    }
+    // Announcements 直接搬
+    if v := valMap["Announcements"]; v != "" {
+        model.UpdateOption("console_setting.announcements", v)
+        model.UpdateOption("Announcements", "")
+    }
+    // FAQ 转换
+    if v := valMap["FAQ"]; v != "" {
+        var arr []map[string]interface{}
+        if err := json.Unmarshal([]byte(v), &arr); err == nil {
+            out := []map[string]interface{}{}
+            for _, item := range arr {
+                q, _ := item["question"].(string)
+                if q == "" {
+                    q, _ = item["title"].(string)
+                }
+                a, _ := item["answer"].(string)
+                if a == "" {
+                    a, _ = item["content"].(string)
+                }
+                if q != "" && a != "" {
+                    out = append(out, map[string]interface{}{"question": q, "answer": a})
+                }
+            }
+            if len(out) > 50 {
+                out = out[:50]
+            }
+            bytes, _ := json.Marshal(out)
+            model.UpdateOption("console_setting.faq", string(bytes))
+        }
+        model.UpdateOption("FAQ", "")
+    }
+    // Uptime
+    if v := valMap["UptimeKumaUrl"]; v != "" {
+        model.UpdateOption("console_setting.uptime_kuma_url", v)
+        model.UpdateOption("UptimeKumaUrl", "")
+    }
+    if v := valMap["UptimeKumaSlug"]; v != "" {
+        model.UpdateOption("console_setting.uptime_kuma_slug", v)
+        model.UpdateOption("UptimeKumaSlug", "")
+    }
+
+    // 删除旧键记录
+    oldKeys := []string{"ApiInfo", "Announcements", "FAQ", "UptimeKumaUrl", "UptimeKumaSlug"}
+    model.DB.Where("key IN ?", oldKeys).Delete(&model.Option{})
+
+    // 重新加载 OptionMap
+    model.InitOptionMap()
+    common.SysLog("console setting migrated")
+    c.JSON(http.StatusOK, gin.H{"success": true, "message": "migrated"})
+} 

+ 1 - 0
router/api-router.go

@@ -81,6 +81,7 @@ func SetApiRouter(router *gin.Engine) {
 			optionRoute.GET("/", controller.GetOptions)
 			optionRoute.PUT("/", controller.UpdateOption)
 			optionRoute.POST("/rest_model_ratio", controller.ResetModelRatio)
+			optionRoute.POST("/migrate_console_setting", controller.MigrateConsoleSetting) // 用于迁移检测的旧键,下个版本会删除
 		}
 		channelRoute := apiRouter.Group("/channel")
 		channelRoute.Use(middleware.AdminAuth())

+ 54 - 3
web/src/components/settings/DashboardSetting.js

@@ -1,6 +1,6 @@
-import React, { useEffect, useState } from 'react';
-import { Card, Spin } from '@douyinfe/semi-ui';
-import { API, showError } from '../../helpers';
+import React, { useEffect, useState, useMemo } from 'react';
+import { Card, Spin, Button, Modal } from '@douyinfe/semi-ui';
+import { API, showError, showSuccess } from '../../helpers';
 import SettingsAPIInfo from '../../pages/Setting/Dashboard/SettingsAPIInfo.js';
 import SettingsAnnouncements from '../../pages/Setting/Dashboard/SettingsAnnouncements.js';
 import SettingsFAQ from '../../pages/Setting/Dashboard/SettingsFAQ.js';
@@ -13,9 +13,17 @@ const DashboardSetting = () => {
     'console_setting.faq': '',
     'console_setting.uptime_kuma_url': '',
     'console_setting.uptime_kuma_slug': '',
+
+    // 用于迁移检测的旧键,下个版本会删除
+    ApiInfo: '',
+    Announcements: '',
+    FAQ: '',
+    UptimeKumaUrl: '',
+    UptimeKumaSlug: '',
   });
 
   let [loading, setLoading] = useState(false);
+  const [showMigrateModal, setShowMigrateModal] = useState(false); // 下个版本会删除
 
   const getOptions = async () => {
     const res = await API.get('/api/option/');
@@ -49,9 +57,52 @@ const DashboardSetting = () => {
     onRefresh();
   }, []);
 
+  // 用于迁移检测的旧键,下个版本会删除
+  const hasLegacyData = useMemo(() => {
+    const legacyKeys = ['ApiInfo', 'Announcements', 'FAQ', 'UptimeKumaUrl', 'UptimeKumaSlug'];
+    return legacyKeys.some(k => inputs[k]);
+  }, [inputs]);
+
+  useEffect(() => {
+    if (hasLegacyData) {
+      setShowMigrateModal(true);
+    }
+  }, [hasLegacyData]);
+
+  const handleMigrate = async () => {
+    try {
+      setLoading(true);
+      await API.post('/api/option/migrate_console_setting');
+      showSuccess('旧配置迁移完成');
+      await onRefresh();
+      setShowMigrateModal(false);
+    } catch (err) {
+      console.error(err);
+      showError('迁移失败: ' + (err.message || '未知错误'));
+    } finally {
+      setLoading(false);
+    }
+  };
+
   return (
     <>
       <Spin spinning={loading} size='large'>
+        {/* 用于迁移检测的旧键模态框,下个版本会删除 */}
+        <Modal
+          title="配置迁移确认"
+          visible={showMigrateModal}
+          onOk={handleMigrate}
+          onCancel={() => setShowMigrateModal(false)}
+          confirmLoading={loading}
+          okText="确认迁移"
+          cancelText="取消"
+        >
+          <p>检测到旧版本的配置数据,是否要迁移到新的配置格式?</p>
+          <p style={{ color: '#f57c00', marginTop: '10px' }}>
+            <strong>注意:</strong>迁移过程中会自动处理数据格式转换,迁移完成后旧配置将被清除,请在迁移前在数据库中备份好旧配置。
+          </p>
+        </Modal>
+
         {/* API信息管理 */}
         <Card style={{ marginTop: '10px' }}>
           <SettingsAPIInfo options={inputs} refresh={onRefresh} />