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
+29 -2
View File
@@ -1,10 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException 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.config import settings
from app.core.logger import get_gemini_logger from app.core.logger import get_gemini_logger
from app.core.security import SecurityService 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.gemini_chat_service import GeminiChatService
from app.services.key_manager import KeyManager, get_key_manager_instance from app.services.key_manager import KeyManager, get_key_manager_instance
from app.services.model_service import ModelService from app.services.model_service import ModelService
@@ -102,3 +102,30 @@ async def stream_generate_content(
except Exception as e: except Exception as e:
logger.error(f"Streaming request failed: {str(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)})
+1
View File
@@ -16,6 +16,7 @@ class Settings(BaseSettings):
CREATE_IMAGE_MODEL: str = "imagen-3.0-generate-002" CREATE_IMAGE_MODEL: str = "imagen-3.0-generate-002"
UPLOAD_PROVIDER: str = "smms" UPLOAD_PROVIDER: str = "smms"
SMMS_SECRET_TOKEN: str = "" SMMS_SECRET_TOKEN: str = ""
TEST_MODEL: str = "gemini-1.5-flash"
def __init__(self): def __init__(self):
super().__init__() super().__init__()
+42 -4
View File
@@ -154,7 +154,13 @@ li:hover {
font-size: 12px; 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%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;
border: none; border: none;
@@ -169,6 +175,26 @@ li:hover {
gap: 5px; 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 { .copy-btn:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(118, 75, 162, 0.3); box-shadow: 0 5px 15px rgba(118, 75, 162, 0.3);
@@ -199,8 +225,6 @@ li:hover {
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
background: rgba(39, 174, 96, 0.95);
color: white;
padding: 15px 30px; padding: 15px 30px;
border-radius: 25px; border-radius: 25px;
font-weight: bold; font-weight: bold;
@@ -211,6 +235,15 @@ li:hover {
z-index: 1000; z-index: 1000;
text-align: center; text-align: center;
min-width: 200px; 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 { .status-badge {
@@ -375,7 +408,12 @@ li:hover {
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
} }
.copy-btn { .key-actions {
width: 100%;
flex-direction: column;
}
.verify-btn, .copy-btn {
width: 100%; width: 100%;
justify-content: center; justify-content: center;
} }
+41 -1
View File
@@ -50,15 +50,55 @@ function copyKey(key) {
}); });
} }
function showCopyStatus(message) { function showCopyStatus(message, type = 'success') {
const statusElement = document.getElementById('copyStatus'); const statusElement = document.getElementById('copyStatus');
statusElement.textContent = message; statusElement.textContent = message;
statusElement.className = type; // 设置样式类
statusElement.style.opacity = 1; statusElement.style.opacity = 1;
setTimeout(() => { setTimeout(() => {
statusElement.style.opacity = 0; statusElement.style.opacity = 0;
setTimeout(() => {
statusElement.className = ''; // 清除样式类
}, 300);
}, 2000); }, 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() { function scrollToTop() {
const container = document.querySelector('.container'); const container = document.querySelector('.container');
container.scrollTo({ container.scrollTo({
+20 -8
View File
@@ -46,10 +46,16 @@
失败: {{ fail_count }} 失败: {{ fail_count }}
</span> </span>
</div> </div>
<button class="copy-btn" onclick="copyKey('{{ key }}')"> <div class="key-actions">
<i class="fas fa-copy"></i> <button class="verify-btn" onclick="verifyKey('{{ key }}', this)">
复制 <i class="fas fa-check-circle"></i>
</button> 验证
</button>
<button class="copy-btn" onclick="copyKey('{{ key }}')">
<i class="fas fa-copy"></i>
复制
</button>
</div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -81,10 +87,16 @@
失败: {{ fail_count }} 失败: {{ fail_count }}
</span> </span>
</div> </div>
<button class="copy-btn" onclick="copyKey('{{ key }}')"> <div class="key-actions">
<i class="fas fa-copy"></i> <button class="verify-btn" onclick="verifyKey('{{ key }}', this)">
复制 <i class="fas fa-check-circle"></i>
</button> 验证
</button>
<button class="copy-btn" onclick="copyKey('{{ key }}')">
<i class="fas fa-copy"></i>
复制
</button>
</div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>