diff --git a/backend/api/handlers.go b/backend/api/handlers.go
index 9261ee8..13eb07f 100644
--- a/backend/api/handlers.go
+++ b/backend/api/handlers.go
@@ -555,3 +555,120 @@ func (h *APIHandler) ForceLogCleanupHandler(c *gin.Context) {
"success": report.Success,
})
}
+
+
+// ============ API Key 管理相关处理器 ============
+
+// APIKeyListResponse API Key列表响应
+type APIKeyListResponse struct {
+ ID uint `json:"id"`
+ Key string `json:"key"`
+ CreatedAt time.Time `json:"created_at"`
+}
+
+// GetAPIKeysHandler 获取所有API Key列表
+func (h *APIHandler) GetAPIKeysHandler(c *gin.Context) {
+ var apiKeys []models.APIKey
+
+ // 查询所有API Key
+ if err := h.DB.Order("created_at DESC").Find(&apiKeys).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch API keys"})
+ return
+ }
+
+ // 转换为响应格式
+ response := make([]APIKeyListResponse, len(apiKeys))
+ for i, key := range apiKeys {
+ response[i] = APIKeyListResponse{
+ ID: key.ID,
+ Key: key.Key,
+ CreatedAt: key.CreatedAt,
+ }
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "api_keys": response,
+ "total": len(response),
+ })
+}
+
+// CreateAPIKeyRequest 创建API Key的请求结构
+type CreateAPIKeyRequest struct {
+ Key string `json:"key" binding:"required"`
+}
+
+// CreateAPIKeyHandler 创建新的API Key
+func (h *APIHandler) CreateAPIKeyHandler(c *gin.Context) {
+ var req CreateAPIKeyRequest
+
+ // 解析请求
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format: " + err.Error()})
+ return
+ }
+
+ // 验证Key不为空
+ if req.Key == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "API key cannot be empty"})
+ return
+ }
+
+ // 检查Key是否已存在
+ var existingKey models.APIKey
+ if err := h.DB.Where("key = ?", req.Key).First(&existingKey).Error; err == nil {
+ c.JSON(http.StatusConflict, gin.H{"error": "API key already exists"})
+ return
+ }
+
+ // 创建新的API Key
+ newAPIKey := models.APIKey{
+ Key: req.Key,
+ }
+
+ if err := h.DB.Create(&newAPIKey).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create API key"})
+ return
+ }
+
+ // 返回创建的API Key
+ c.JSON(http.StatusCreated, gin.H{
+ "message": "API key created successfully",
+ "api_key": APIKeyListResponse{
+ ID: newAPIKey.ID,
+ Key: newAPIKey.Key,
+ CreatedAt: newAPIKey.CreatedAt,
+ },
+ })
+}
+
+// DeleteAPIKeyHandler 删除指定的API Key
+func (h *APIHandler) DeleteAPIKeyHandler(c *gin.Context) {
+ // 获取API Key ID
+ keyID := c.Param("id")
+ if keyID == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "API key ID is required"})
+ return
+ }
+
+ // 查找API Key
+ var apiKey models.APIKey
+ if err := h.DB.First(&apiKey, keyID).Error; err != nil {
+ if err == gorm.ErrRecordNotFound {
+ c.JSON(http.StatusNotFound, gin.H{"error": "API key not found"})
+ return
+ }
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to find API key"})
+ return
+ }
+
+ // 删除API Key
+ if err := h.DB.Delete(&apiKey).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete API key"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "message": "API key deleted successfully",
+ "id": apiKey.ID,
+ })
+}
diff --git a/backend/main.go b/backend/main.go
index 4110999..a68ce2d 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -118,6 +118,11 @@ func main() {
// Log Cleaner Management
api_.GET("/log-cleaner/status", handler.GetLogCleanerStatusHandler)
api_.POST("/log-cleaner/force-cleanup", handler.ForceLogCleanupHandler)
+
+ // API Keys Management
+ api_.GET("/api-keys", handler.GetAPIKeysHandler)
+ api_.POST("/api-keys", handler.CreateAPIKeyHandler)
+ api_.DELETE("/api-keys/:id", handler.DeleteAPIKeyHandler)
}
// 设置优雅关闭信号处理
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index cb3a957..e65c632 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -3,6 +3,7 @@ import PageLayout from './components/layout/PageLayout';
import ProvidersPage from './features/providers';
import VirtualModelsPage from './features/virtual-models';
import LogsPage from './features/logs';
+import ApiKeysPage from './features/api-keys';
import './App.css';
function App() {
@@ -18,6 +19,10 @@ function App() {
{
label: '请求日志',
content:
+ },
+ {
+ label: 'API Key管理',
+ content:
}
];
diff --git a/frontend/src/features/api-keys/api/index.js b/frontend/src/features/api-keys/api/index.js
new file mode 100644
index 0000000..a2930a4
--- /dev/null
+++ b/frontend/src/features/api-keys/api/index.js
@@ -0,0 +1,30 @@
+import api from '../../../lib/api';
+
+/**
+ * 获取所有API Key列表
+ * @returns {Promise} API响应
+ */
+export const fetchAPIKeys = async () => {
+ const response = await api.get('/api/api-keys');
+ return response.data;
+};
+
+/**
+ * 创建新的API Key
+ * @param {string} key - API Key字符串
+ * @returns {Promise} API响应
+ */
+export const createAPIKey = async (key) => {
+ const response = await api.post('/api/api-keys', { key });
+ return response.data;
+};
+
+/**
+ * 删除指定的API Key
+ * @param {number} id - API Key的ID
+ * @returns {Promise} API响应
+ */
+export const deleteAPIKey = async (id) => {
+ const response = await api.delete(`/api/api-keys/${id}`);
+ return response.data;
+};
\ No newline at end of file
diff --git a/frontend/src/features/api-keys/components/ApiKeyForm.jsx b/frontend/src/features/api-keys/components/ApiKeyForm.jsx
new file mode 100644
index 0000000..a16b23c
--- /dev/null
+++ b/frontend/src/features/api-keys/components/ApiKeyForm.jsx
@@ -0,0 +1,128 @@
+import { useState } from 'react';
+
+const ApiKeyForm = ({ onSave, onCancel }) => {
+ const [key, setKey] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ // 生成随机API Key
+ const generateRandomKey = () => {
+ const prefix = 'sk-';
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ let randomPart = '';
+ for (let i = 0; i < 32; i++) {
+ randomPart += chars.charAt(Math.floor(Math.random() * chars.length));
+ }
+ return prefix + randomPart;
+ };
+
+ const handleGenerate = () => {
+ setKey(generateRandomKey());
+ setError('');
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (!key.trim()) {
+ setError('请输入API Key');
+ return;
+ }
+
+ // 验证API Key格式
+ if (!key.startsWith('sk-')) {
+ setError('API Key必须以 "sk-" 开头');
+ return;
+ }
+
+ if (key.length < 10) {
+ setError('API Key长度不能少于10个字符');
+ return;
+ }
+
+ setLoading(true);
+ setError('');
+
+ try {
+ await onSave({ key: key.trim() });
+ // 成功后会由父组件处理关闭弹窗
+ } catch (err) {
+ setError(err.message || '创建API Key失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default ApiKeyForm;
\ No newline at end of file
diff --git a/frontend/src/features/api-keys/components/ApiKeyList.jsx b/frontend/src/features/api-keys/components/ApiKeyList.jsx
new file mode 100644
index 0000000..79b982e
--- /dev/null
+++ b/frontend/src/features/api-keys/components/ApiKeyList.jsx
@@ -0,0 +1,231 @@
+import { useState, useEffect } from 'react';
+import { fetchAPIKeys, createAPIKey, deleteAPIKey } from '../api';
+import ApiKeyForm from './ApiKeyForm';
+import Modal from '../../../components/ui/Modal';
+
+const ApiKeyList = () => {
+ const [apiKeys, setApiKeys] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [showCreateForm, setShowCreateForm] = useState(false);
+
+ const fetchKeys = async () => {
+ try {
+ setLoading(true);
+ const data = await fetchAPIKeys();
+ setApiKeys(data.api_keys || []);
+ setError(null);
+ } catch (err) {
+ setError(err.message || '获取API Key列表失败');
+ console.error('Error fetching API keys:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchKeys();
+ }, []);
+
+ const handleCreate = async (keyData) => {
+ try {
+ await createAPIKey(keyData.key);
+ setShowCreateForm(false);
+ await fetchKeys();
+ // 显示成功消息
+ alert('API Key创建成功');
+ } catch (err) {
+ setError(err.message || '创建API Key失败');
+ throw err; // 向表单组件传递错误
+ }
+ };
+
+ const handleDelete = async (id) => {
+ if (!window.confirm('确定要删除这个API Key吗?删除后将无法使用该Key进行API调用。')) {
+ return;
+ }
+ try {
+ await deleteAPIKey(id);
+ await fetchKeys();
+ alert('API Key删除成功');
+ } catch (err) {
+ setError(err.message || '删除API Key失败');
+ }
+ };
+
+ const formatDate = (dateString) => {
+ if (!dateString) return 'N/A';
+ const date = new Date(dateString);
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ });
+ };
+
+ const copyToClipboard = (text) => {
+ navigator.clipboard.writeText(text).then(() => {
+ alert('API Key已复制到剪贴板');
+ }).catch(err => {
+ console.error('复制失败:', err);
+ alert('复制失败,请手动复制');
+ });
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return (
+ <>
+ {/* 创建API Key弹窗 */}
+ setShowCreateForm(false)}
+ title="创建新API Key"
+ size="md"
+ >
+ setShowCreateForm(false)}
+ />
+
+
+
+ {/* 页面标题和操作按钮 */}
+
+
+
API Key 管理
+
+ 管理用于访问AI Gateway的API密钥
+
+
+
+
+
+ {/* 错误提示 */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* API Key统计信息 */}
+
+
+
+
+ 当前共有 {apiKeys.length} 个API Key
+
+ · 每个API Key都可以独立用于认证请求
+
+
+
+
+
+ {/* API Key表格 */}
+
+
+
+
+ |
+ ID
+ |
+
+ API Key
+ |
+
+ 创建时间
+ |
+
+ 操作
+ |
+
+
+
+ {apiKeys.length === 0 ? (
+
+
+
+
+ 暂无API Key
+ 点击"创建API Key"按钮添加新的密钥
+
+ |
+
+ ) : (
+ apiKeys.map((apiKey, index) => (
+
+ |
+
+ #{apiKey.id || 'N/A'}
+
+ |
+
+
+
+ {apiKey.key || 'N/A'}
+
+
+
+ |
+
+
+ {formatDate(apiKey.created_at)}
+
+ |
+
+
+ |
+
+ ))
+ )}
+
+
+
+
+ {/* 使用说明 */}
+
+
使用说明
+
+ - API Key用于认证对AI Gateway的所有请求
+ - 在请求头中添加
Authorization: Bearer YOUR_API_KEY
+ - 请妥善保管API Key,避免泄露给未授权人员
+ - 如果API Key泄露,请立即删除并创建新的密钥
+
+
+
+ >
+ );
+};
+
+export default ApiKeyList;
\ No newline at end of file
diff --git a/frontend/src/features/api-keys/index.jsx b/frontend/src/features/api-keys/index.jsx
new file mode 100644
index 0000000..89a0dfd
--- /dev/null
+++ b/frontend/src/features/api-keys/index.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import ApiKeyList from './components/ApiKeyList';
+
+const ApiKeysPage = () => {
+ return (
+
+ );
+};
+
+export default ApiKeysPage;
\ No newline at end of file