feat(gemini): 添加 API 密钥验证功能

- 在 gemini_routes.py 中添加 verify_key 路由,用于验证 API 密钥的有效性
- 在 keys_status 页面中添加验证按钮和相关逻辑
- 优化 keys_status 页面的样式,增加密钥验证相关 CSS 类
- 在 config.py 中添加 TEST_MODEL 设置,用于密钥验证测试
This commit is contained in:
yinpeng
2025-02-15 01:00:47 +08:00
parent c1893d918e
commit d566c28fa2
5 changed files with 133 additions and 15 deletions

View File

@@ -1,10 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import StreamingResponse
from fastapi.responses import StreamingResponse, JSONResponse
from app.core.config import settings
from app.core.logger import get_gemini_logger
from app.core.security import SecurityService
from app.schemas.gemini_models import GeminiRequest
from app.schemas.gemini_models import GeminiContent, GeminiRequest
from app.services.gemini_chat_service import GeminiChatService
from app.services.key_manager import KeyManager, get_key_manager_instance
from app.services.model_service import ModelService
@@ -102,3 +102,30 @@ async def stream_generate_content(
except Exception as e:
logger.error(f"Streaming request failed: {str(e)}")
@router.post("/verify-key/{api_key}")
async def verify_key(api_key: str):
key_manager = await get_key_manager()
chat_service = GeminiChatService(settings.BASE_URL, key_manager)
"""验证Gemini API密钥的有效性"""
logger.info("-" * 50 + "verify_gemini_key" + "-" * 50)
logger.info("Verifying API key validity")
try:
# 使用generate_content接口测试key的有效性
gemini_requset = GeminiRequest(
contents=[
GeminiContent(
role="user",
parts=[{"text": "hi"}]
)
]
)
response = chat_service.generate_content(settings.TEST_MODEL,gemini_requset, api_key)
if response:
return JSONResponse({"status": "valid"})
return JSONResponse({"status": "invalid"})
except Exception as e:
logger.error(f"Key verification failed: {str(e)}")
return JSONResponse({"status": "invalid", "error": str(e)})

View File

@@ -16,6 +16,7 @@ class Settings(BaseSettings):
CREATE_IMAGE_MODEL: str = "imagen-3.0-generate-002"
UPLOAD_PROVIDER: str = "smms"
SMMS_SECRET_TOKEN: str = ""
TEST_MODEL: str = "gemini-1.5-flash"
def __init__(self):
super().__init__()

View File

@@ -154,7 +154,13 @@ li:hover {
font-size: 12px;
}
.copy-btn {
.key-actions {
display: flex;
gap: 10px;
align-items: center;
}
.verify-btn, .copy-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
@@ -169,6 +175,26 @@ li:hover {
gap: 5px;
}
.verify-btn {
background: linear-gradient(135deg, #2ecc71, #27ae60);
}
.verify-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(46, 204, 113, 0.3);
}
.verify-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.verify-btn i {
font-size: 14px;
}
.copy-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(118, 75, 162, 0.3);
@@ -199,8 +225,6 @@ li:hover {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(39, 174, 96, 0.95);
color: white;
padding: 15px 30px;
border-radius: 25px;
font-weight: bold;
@@ -211,6 +235,15 @@ li:hover {
z-index: 1000;
text-align: center;
min-width: 200px;
color: white;
}
#copyStatus.success {
background: rgba(39, 174, 96, 0.95);
}
#copyStatus.error {
background: rgba(231, 76, 60, 0.95);
}
.status-badge {
@@ -375,7 +408,12 @@ li:hover {
flex-direction: column;
gap: 10px;
}
.copy-btn {
.key-actions {
width: 100%;
flex-direction: column;
}
.verify-btn, .copy-btn {
width: 100%;
justify-content: center;
}

View File

@@ -50,15 +50,55 @@ function copyKey(key) {
});
}
function showCopyStatus(message) {
function showCopyStatus(message, type = 'success') {
const statusElement = document.getElementById('copyStatus');
statusElement.textContent = message;
statusElement.className = type; // 设置样式类
statusElement.style.opacity = 1;
setTimeout(() => {
statusElement.style.opacity = 0;
setTimeout(() => {
statusElement.className = ''; // 清除样式类
}, 300);
}, 2000);
}
async function verifyKey(key, button) {
try {
// 禁用按钮并显示加载状态
button.disabled = true;
const originalHtml = button.innerHTML;
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 验证中';
const response = await fetch(`/gemini/v1beta/verify-key/${key}`, {
method: 'POST'
});
const data = await response.json();
// 根据验证结果更新UI
if (data.status === 'valid') {
showCopyStatus('密钥验证成功', 'success');
button.style.backgroundColor = '#27ae60';
} else {
showCopyStatus('密钥验证失败', 'error');
button.style.backgroundColor = '#e74c3c';
}
// 3秒后恢复按钮原始状态
setTimeout(() => {
button.innerHTML = originalHtml;
button.disabled = false;
button.style.backgroundColor = '';
}, 3000);
} catch (error) {
console.error('验证失败:', error);
showCopyStatus('验证请求失败', 'error');
button.disabled = false;
button.innerHTML = '<i class="fas fa-check-circle"></i> 验证';
}
}
function scrollToTop() {
const container = document.querySelector('.container');
container.scrollTo({

View File

@@ -46,10 +46,16 @@
失败: {{ fail_count }}
</span>
</div>
<button class="copy-btn" onclick="copyKey('{{ key }}')">
<i class="fas fa-copy"></i>
复制
</button>
<div class="key-actions">
<button class="verify-btn" onclick="verifyKey('{{ key }}', this)">
<i class="fas fa-check-circle"></i>
验证
</button>
<button class="copy-btn" onclick="copyKey('{{ key }}')">
<i class="fas fa-copy"></i>
复制
</button>
</div>
</li>
{% endfor %}
</ul>
@@ -81,10 +87,16 @@
失败: {{ fail_count }}
</span>
</div>
<button class="copy-btn" onclick="copyKey('{{ key }}')">
<i class="fas fa-copy"></i>
复制
</button>
<div class="key-actions">
<button class="verify-btn" onclick="verifyKey('{{ key }}', this)">
<i class="fas fa-check-circle"></i>
验证
</button>
<button class="copy-btn" onclick="copyKey('{{ key }}')">
<i class="fas fa-copy"></i>
复制
</button>
</div>
</li>
{% endfor %}
</ul>