فهرست منبع

feat: enhance multi-key management with pagination and statistics

CaIon 7 ماه پیش
والد
کامیت
8357b15fec
3فایلهای تغییر یافته به همراه227 افزوده شده و 52 حذف شده
  1. 99 21
      controller/channel.go
  2. 6 6
      model/channel.go
  3. 122 25
      web/src/components/table/channels/modals/MultiKeyManageModal.jsx

+ 99 - 21
controller/channel.go

@@ -71,6 +71,13 @@ func parseStatusFilter(statusParam string) int {
 	}
 }
 
+func clearChannelInfo(channel *model.Channel) {
+	if channel.ChannelInfo.IsMultiKey {
+		channel.ChannelInfo.MultiKeyDisabledReason = nil
+		channel.ChannelInfo.MultiKeyDisabledTime = nil
+	}
+}
+
 func GetAllChannels(c *gin.Context) {
 	pageInfo := common.GetPageQuery(c)
 	channelData := make([]*model.Channel, 0)
@@ -145,6 +152,10 @@ func GetAllChannels(c *gin.Context) {
 		}
 	}
 
+	for _, datum := range channelData {
+		clearChannelInfo(datum)
+	}
+
 	countQuery := model.DB.Model(&model.Channel{})
 	if statusFilter == common.ChannelStatusEnabled {
 		countQuery = countQuery.Where("status = ?", common.ChannelStatusEnabled)
@@ -371,6 +382,10 @@ func SearchChannels(c *gin.Context) {
 
 	pagedData := channelData[startIdx:endIdx]
 
+	for _, datum := range pagedData {
+		clearChannelInfo(datum)
+	}
+
 	c.JSON(http.StatusOK, gin.H{
 		"success": true,
 		"message": "",
@@ -394,6 +409,9 @@ func GetChannel(c *gin.Context) {
 		common.ApiError(c, err)
 		return
 	}
+	if channel != nil {
+		clearChannelInfo(channel)
+	}
 	c.JSON(http.StatusOK, gin.H{
 		"success": true,
 		"message": "",
@@ -827,6 +845,7 @@ func UpdateChannel(c *gin.Context) {
 	}
 	model.InitChannelCache()
 	channel.Key = ""
+	clearChannelInfo(&channel.Channel)
 	c.JSON(http.StatusOK, gin.H{
 		"success": true,
 		"message": "",
@@ -1036,11 +1055,21 @@ type MultiKeyManageRequest struct {
 	ChannelId int    `json:"channel_id"`
 	Action    string `json:"action"`              // "disable_key", "enable_key", "delete_disabled_keys", "get_key_status"
 	KeyIndex  *int   `json:"key_index,omitempty"` // for disable_key and enable_key actions
+	Page      int    `json:"page,omitempty"`      // for get_key_status pagination
+	PageSize  int    `json:"page_size,omitempty"` // for get_key_status pagination
 }
 
 // MultiKeyStatusResponse represents the response for key status query
 type MultiKeyStatusResponse struct {
-	Keys []KeyStatus `json:"keys"`
+	Keys       []KeyStatus `json:"keys"`
+	Total      int         `json:"total"`
+	Page       int         `json:"page"`
+	PageSize   int         `json:"page_size"`
+	TotalPages int         `json:"total_pages"`
+	// Statistics
+	EnabledCount        int `json:"enabled_count"`
+	ManualDisabledCount int `json:"manual_disabled_count"`
+	AutoDisabledCount   int `json:"auto_disabled_count"`
 }
 
 type KeyStatus struct {
@@ -1080,8 +1109,35 @@ func ManageMultiKeys(c *gin.Context) {
 	switch request.Action {
 	case "get_key_status":
 		keys := channel.GetKeys()
-		var keyStatusList []KeyStatus
+		total := len(keys)
+
+		// Default pagination parameters
+		page := request.Page
+		pageSize := request.PageSize
+		if page <= 0 {
+			page = 1
+		}
+		if pageSize <= 0 {
+			pageSize = 50 // Default page size
+		}
+
+		// Calculate pagination
+		totalPages := (total + pageSize - 1) / pageSize
+		if page > totalPages && totalPages > 0 {
+			page = totalPages
+		}
+
+		// Calculate range
+		start := (page - 1) * pageSize
+		end := start + pageSize
+		if end > total {
+			end = total
+		}
+
+		// Statistics for all keys
+		var enabledCount, manualDisabledCount, autoDisabledCount int
 
+		var keyStatusList []KeyStatus
 		for i, key := range keys {
 			status := 1 // default enabled
 			var disabledTime int64
@@ -1093,34 +1149,56 @@ func ManageMultiKeys(c *gin.Context) {
 				}
 			}
 
-			if status != 1 {
-				if channel.ChannelInfo.MultiKeyDisabledTime != nil {
-					disabledTime = channel.ChannelInfo.MultiKeyDisabledTime[i]
+			// Count for statistics
+			switch status {
+			case 1:
+				enabledCount++
+			case 2:
+				manualDisabledCount++
+			case 3:
+				autoDisabledCount++
+			}
+
+			// Only include keys in current page
+			if i >= start && i < end {
+				if status != 1 {
+					if channel.ChannelInfo.MultiKeyDisabledTime != nil {
+						disabledTime = channel.ChannelInfo.MultiKeyDisabledTime[i]
+					}
+					if channel.ChannelInfo.MultiKeyDisabledReason != nil {
+						reason = channel.ChannelInfo.MultiKeyDisabledReason[i]
+					}
 				}
-				if channel.ChannelInfo.MultiKeyDisabledReason != nil {
-					reason = channel.ChannelInfo.MultiKeyDisabledReason[i]
+
+				// Create key preview (first 10 chars)
+				keyPreview := key
+				if len(key) > 10 {
+					keyPreview = key[:10] + "..."
 				}
-			}
 
-			// Create key preview (first 10 chars)
-			keyPreview := key
-			if len(key) > 10 {
-				keyPreview = key[:10] + "..."
+				keyStatusList = append(keyStatusList, KeyStatus{
+					Index:        i,
+					Status:       status,
+					DisabledTime: disabledTime,
+					Reason:       reason,
+					KeyPreview:   keyPreview,
+				})
 			}
-
-			keyStatusList = append(keyStatusList, KeyStatus{
-				Index:        i,
-				Status:       status,
-				DisabledTime: disabledTime,
-				Reason:       reason,
-				KeyPreview:   keyPreview,
-			})
 		}
 
 		c.JSON(http.StatusOK, gin.H{
 			"success": true,
 			"message": "",
-			"data":    MultiKeyStatusResponse{Keys: keyStatusList},
+			"data": MultiKeyStatusResponse{
+				Keys:                keyStatusList,
+				Total:               total,
+				Page:                page,
+				PageSize:            pageSize,
+				TotalPages:          totalPages,
+				EnabledCount:        enabledCount,
+				ManualDisabledCount: manualDisabledCount,
+				AutoDisabledCount:   autoDisabledCount,
+			},
 		})
 		return
 

+ 6 - 6
model/channel.go

@@ -53,12 +53,12 @@ type Channel struct {
 }
 
 type ChannelInfo struct {
-	IsMultiKey             bool                  `json:"is_multi_key"`              // 是否多Key模式
-	MultiKeySize           int                   `json:"multi_key_size"`            // 多Key模式下的Key数量
-	MultiKeyStatusList     map[int]int           `json:"multi_key_status_list"`     // key状态列表,key index -> status
-	MultiKeyDisabledReason map[int]string        `json:"multi_key_disabled_reason"` // key禁用原因列表,key index -> reason
-	MultiKeyDisabledTime   map[int]int64         `json:"multi_key_disabled_time"`   // key禁用时间列表,key index -> time
-	MultiKeyPollingIndex   int                   `json:"multi_key_polling_index"`   // 多Key模式下轮询的key索引
+	IsMultiKey             bool                  `json:"is_multi_key"`                        // 是否多Key模式
+	MultiKeySize           int                   `json:"multi_key_size"`                      // 多Key模式下的Key数量
+	MultiKeyStatusList     map[int]int           `json:"multi_key_status_list"`               // key状态列表,key index -> status
+	MultiKeyDisabledReason map[int]string        `json:"multi_key_disabled_reason,omitempty"` // key禁用原因列表,key index -> reason
+	MultiKeyDisabledTime   map[int]int64         `json:"multi_key_disabled_time,omitempty"`   // key禁用时间列表,key index -> time
+	MultiKeyPollingIndex   int                   `json:"multi_key_polling_index"`             // 多Key模式下轮询的key索引
 	MultiKeyMode           constant.MultiKeyMode `json:"multi_key_mode"`
 }
 

+ 122 - 25
web/src/components/table/channels/modals/MultiKeyManageModal.jsx

@@ -30,7 +30,9 @@ import {
   Popconfirm,
   Empty,
   Spin,
-  Banner
+  Banner,
+  Select,
+  Pagination
 } from '@douyinfe/semi-ui';
 import { 
   IconRefresh,
@@ -53,24 +55,48 @@ const MultiKeyManageModal = ({
   const [loading, setLoading] = useState(false);
   const [keyStatusList, setKeyStatusList] = useState([]);
   const [operationLoading, setOperationLoading] = useState({});
+  
+  // Pagination states
+  const [currentPage, setCurrentPage] = useState(1);
+  const [pageSize, setPageSize] = useState(50);
+  const [total, setTotal] = useState(0);
+  const [totalPages, setTotalPages] = useState(0);
+  
+  // Statistics states
+  const [enabledCount, setEnabledCount] = useState(0);
+  const [manualDisabledCount, setManualDisabledCount] = useState(0);
+  const [autoDisabledCount, setAutoDisabledCount] = useState(0);
 
   // Load key status data
-  const loadKeyStatus = async () => {
+  const loadKeyStatus = async (page = currentPage, size = pageSize) => {
     if (!channel?.id) return;
     
     setLoading(true);
     try {
       const res = await API.post('/api/channel/multi_key/manage', {
         channel_id: channel.id,
-        action: 'get_key_status'
+        action: 'get_key_status',
+        page: page,
+        page_size: size
       });
       
       if (res.data.success) {
-        setKeyStatusList(res.data.data.keys || []);
+        const data = res.data.data;
+        setKeyStatusList(data.keys || []);
+        setTotal(data.total || 0);
+        setCurrentPage(data.page || 1);
+        setPageSize(data.page_size || 50);
+        setTotalPages(data.total_pages || 0);
+        
+        // Update statistics
+        setEnabledCount(data.enabled_count || 0);
+        setManualDisabledCount(data.manual_disabled_count || 0);
+        setAutoDisabledCount(data.auto_disabled_count || 0);
       } else {
         showError(res.data.message);
       }
     } catch (error) {
+      console.error(error);
       showError(t('获取密钥状态失败'));
     } finally {
       setLoading(false);
@@ -91,7 +117,7 @@ const MultiKeyManageModal = ({
       
       if (res.data.success) {
         showSuccess(t('密钥已禁用'));
-        await loadKeyStatus(); // Reload data
+        await loadKeyStatus(currentPage, pageSize); // Reload current page
         onRefresh && onRefresh(); // Refresh parent component
       } else {
         showError(res.data.message);
@@ -117,7 +143,7 @@ const MultiKeyManageModal = ({
       
       if (res.data.success) {
         showSuccess(t('密钥已启用'));
-        await loadKeyStatus(); // Reload data
+        await loadKeyStatus(currentPage, pageSize); // Reload current page
         onRefresh && onRefresh(); // Refresh parent component
       } else {
         showError(res.data.message);
@@ -141,7 +167,9 @@ const MultiKeyManageModal = ({
       
       if (res.data.success) {
         showSuccess(res.data.message);
-        await loadKeyStatus(); // Reload data
+        // Reset to first page after deletion as data structure might change
+        setCurrentPage(1);
+        await loadKeyStatus(1, pageSize);
         onRefresh && onRefresh(); // Refresh parent component
       } else {
         showError(res.data.message);
@@ -153,13 +181,40 @@ const MultiKeyManageModal = ({
     }
   };
 
+  // Handle page change
+  const handlePageChange = (page) => {
+    setCurrentPage(page);
+    loadKeyStatus(page, pageSize);
+  };
+
+  // Handle page size change  
+  const handlePageSizeChange = (size) => {
+    setPageSize(size);
+    setCurrentPage(1); // Reset to first page
+    loadKeyStatus(1, size);
+  };
+
   // Effect to load data when modal opens
   useEffect(() => {
     if (visible && channel?.id) {
-      loadKeyStatus();
+      setCurrentPage(1); // Reset to first page when opening
+      loadKeyStatus(1, pageSize);
     }
   }, [visible, channel?.id]);
 
+  // Reset pagination when modal closes
+  useEffect(() => {
+    if (!visible) {
+      setCurrentPage(1);
+      setKeyStatusList([]);
+      setTotal(0);
+      setTotalPages(0);
+      setEnabledCount(0);
+      setManualDisabledCount(0);
+      setAutoDisabledCount(0);
+    }
+  }, [visible]);
+
   // Get status tag component
   const renderStatusTag = (status) => {
     switch (status) {
@@ -270,12 +325,6 @@ const MultiKeyManageModal = ({
     },
   ];
 
-  // Calculate statistics
-  const enabledCount = keyStatusList.filter(key => key.status === 1).length;
-  const manualDisabledCount = keyStatusList.filter(key => key.status === 2).length;
-  const autoDisabledCount = keyStatusList.filter(key => key.status === 3).length;
-  const totalCount = keyStatusList.length;
-
   return (
     <Modal
       title={
@@ -293,7 +342,7 @@ const MultiKeyManageModal = ({
           <Button onClick={onCancel}>{t('关闭')}</Button>
           <Button
             icon={<IconRefresh />}
-            onClick={loadKeyStatus}
+            onClick={() => loadKeyStatus(currentPage, pageSize)}
             loading={loading}
           >
             {t('刷新')}
@@ -325,7 +374,7 @@ const MultiKeyManageModal = ({
             <div>
               <Text>
                 {t('总共 {{total}} 个密钥,{{enabled}} 个已启用,{{manual}} 个手动禁用,{{auto}} 个自动禁用', {
-                  total: totalCount,
+                  total: total,
                   enabled: enabledCount,
                   manual: manualDisabledCount,
                   auto: autoDisabledCount
@@ -345,15 +394,63 @@ const MultiKeyManageModal = ({
         {/* Key Status Table */}
         <Spin spinning={loading}>
           {keyStatusList.length > 0 ? (
-            <Table
-              columns={columns}
-              dataSource={keyStatusList}
-              pagination={false}
-              size='small'
-              bordered
-              rowKey='index'
-              style={{ maxHeight: '400px', overflow: 'auto' }}
-            />
+            <>
+              <Table
+                columns={columns}
+                dataSource={keyStatusList}
+                pagination={false}
+                size='small'
+                bordered
+                rowKey='index'
+                style={{ marginBottom: '16px' }}
+              />
+              
+              {/* Pagination */}
+              {total > 0 && (
+                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+                  <Text type='quaternary' style={{ fontSize: '12px' }}>
+                    {t('显示第 {{start}}-{{end}} 条,共 {{total}} 条', {
+                      start: (currentPage - 1) * pageSize + 1,
+                      end: Math.min(currentPage * pageSize, total),
+                      total: total
+                    })}
+                  </Text>
+                  
+                  <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
+                    <Text type='quaternary' style={{ fontSize: '12px' }}>
+                      {t('每页显示')}:
+                    </Text>
+                    <Select
+                      value={pageSize}
+                      onChange={handlePageSizeChange}
+                      size='small'
+                      style={{ width: '80px' }}
+                    >
+                      <Select.Option value={50}>50</Select.Option>
+                      <Select.Option value={100}>100</Select.Option>
+                      <Select.Option value={200}>500</Select.Option>
+                      <Select.Option value={1000}>1000</Select.Option>
+                    </Select>
+                    
+                    <Pagination
+                      current={currentPage}
+                      total={total}
+                      pageSize={pageSize}
+                      showSizeChanger={false}
+                      showQuickJumper
+                      size='small'
+                      onChange={handlePageChange}
+                      showTotal={(total, range) => 
+                        t('第 {{current}} / {{total}} 页', {
+                          current: currentPage,
+                          total: totalPages
+                        })
+                      }
+                    />
+                  </div>
+                </div>
+              )}
+            </>
           ) : (
             !loading && (
               <Empty