mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-09-07 00:36:36 +08:00
feat(gemini): 添加 API 密钥验证功能
- 在 gemini_routes.py 中添加 verify_key 路由,用于验证 API 密钥的有效性 - 在 keys_status 页面中添加验证按钮和相关逻辑 - 优化 keys_status 页面的样式,增加密钥验证相关 CSS 类 - 在 config.py 中添加 TEST_MODEL 设置,用于密钥验证测试
This commit is contained in:
@@ -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)})
|
||||||
|
|||||||
@@ -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__()
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user