Upload 61 files
Browse files- app.py +12 -0
- config.py +16 -1
- core/__pycache__/api_manager.cpython-313.pyc +0 -0
- core/api/__pycache__/anthropic.cpython-313.pyc +0 -0
- core/api/__pycache__/deepseek.cpython-313.pyc +0 -0
- core/api/__pycache__/google.cpython-313.pyc +0 -0
- core/api/__pycache__/openai.cpython-313.pyc +0 -0
- core/api/google.py +1 -1
- core/api_manager.py +206 -0
- cron.py +88 -0
- models/__pycache__/api_key.cpython-313.pyc +0 -0
- models/api_key.py +260 -69
- routes/__pycache__/api.cpython-313.pyc +0 -0
- routes/__pycache__/web.cpython-313.pyc +0 -0
- routes/api.py +16 -0
- routes/web.py +5 -0
- static/js/api-key-manager/api-key-creator.js +25 -0
- static/js/api-key-manager/api-key-editor.js +19 -0
- templates/components/api_key_list.html +50 -5
- templates/update_test.html +294 -0
- update.py +139 -0
- utils/__pycache__/db.cpython-313.pyc +0 -0
- utils/__pycache__/migrate.cpython-313.pyc +0 -0
- utils/db.py +53 -0
app.py
CHANGED
@@ -12,6 +12,9 @@ from werkzeug.middleware.proxy_fix import ProxyFix
|
|
12 |
# 导入配置
|
13 |
from config import SECRET_KEY
|
14 |
|
|
|
|
|
|
|
15 |
# 设置时区为UTC+8 (亚洲/上海),兼容Linux和Windows环境
|
16 |
os.environ['TZ'] = 'Asia/Shanghai'
|
17 |
try:
|
@@ -30,6 +33,8 @@ from routes.api import api_bp
|
|
30 |
|
31 |
# 导入认证模块
|
32 |
from utils.auth import AuthManager
|
|
|
|
|
33 |
|
34 |
# 创建Flask应用
|
35 |
app = Flask(__name__)
|
@@ -96,6 +101,13 @@ def authenticate():
|
|
96 |
app.register_blueprint(web_bp)
|
97 |
app.register_blueprint(api_bp)
|
98 |
|
|
|
|
|
|
|
|
|
99 |
# 入口点
|
100 |
if __name__ == '__main__':
|
|
|
|
|
|
|
101 |
app.run(debug=True, host='0.0.0.0', port=7860)
|
|
|
12 |
# 导入配置
|
13 |
from config import SECRET_KEY
|
14 |
|
15 |
+
# 导入API管理服务
|
16 |
+
from core.api_manager import start_service
|
17 |
+
|
18 |
# 设置时区为UTC+8 (亚洲/上海),兼容Linux和Windows环境
|
19 |
os.environ['TZ'] = 'Asia/Shanghai'
|
20 |
try:
|
|
|
33 |
|
34 |
# 导入认证模块
|
35 |
from utils.auth import AuthManager
|
36 |
+
# 导入数据库模块
|
37 |
+
from utils.db import init_db
|
38 |
|
39 |
# 创建Flask应用
|
40 |
app = Flask(__name__)
|
|
|
101 |
app.register_blueprint(web_bp)
|
102 |
app.register_blueprint(api_bp)
|
103 |
|
104 |
+
# 在应用启动时初始化数据库
|
105 |
+
with app.app_context():
|
106 |
+
init_db()
|
107 |
+
|
108 |
# 入口点
|
109 |
if __name__ == '__main__':
|
110 |
+
# 启动API管理服务
|
111 |
+
api_service_threads = start_service()
|
112 |
+
# 应用启动
|
113 |
app.run(debug=True, host='0.0.0.0', port=7860)
|
config.py
CHANGED
@@ -10,9 +10,12 @@ DATA_DIR = os.path.join(BASE_DIR, 'data')
|
|
10 |
os.makedirs(DATA_DIR, exist_ok=True)
|
11 |
|
12 |
# 数据文件路径
|
13 |
-
API_KEYS_FILE = os.path.join(DATA_DIR, 'api_keys.json')
|
14 |
AUTH_FILE = os.path.join(DATA_DIR, 'auth_tokens.json')
|
15 |
|
|
|
|
|
|
|
16 |
# 应用密钥配置
|
17 |
SECRET_KEY = os.environ.get('SECRET_KEY', secrets.token_hex(16))
|
18 |
ADMIN_PASSWORD = os.environ.get('PASSWORD', '123456')
|
@@ -51,3 +54,15 @@ PLATFORM_STYLES = {
|
|
51 |
"color": "#3246d3"
|
52 |
}
|
53 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
os.makedirs(DATA_DIR, exist_ok=True)
|
11 |
|
12 |
# 数据文件路径
|
13 |
+
API_KEYS_FILE = os.path.join(DATA_DIR, 'api_keys.json') # 保留以供兼容性
|
14 |
AUTH_FILE = os.path.join(DATA_DIR, 'auth_tokens.json')
|
15 |
|
16 |
+
# SQLite数据库配置
|
17 |
+
DATABASE_PATH = os.path.join(DATA_DIR, 'api_keys.db')
|
18 |
+
|
19 |
# 应用密钥配置
|
20 |
SECRET_KEY = os.environ.get('SECRET_KEY', secrets.token_hex(16))
|
21 |
ADMIN_PASSWORD = os.environ.get('PASSWORD', '123456')
|
|
|
54 |
"color": "#3246d3"
|
55 |
}
|
56 |
}
|
57 |
+
|
58 |
+
# API调用节流控制配置
|
59 |
+
PLATFORM_LIMITS = {
|
60 |
+
'openai': 30,
|
61 |
+
'anthropic': 30,
|
62 |
+
'google': 30,
|
63 |
+
'deepseek': 50,
|
64 |
+
'default': 10
|
65 |
+
}
|
66 |
+
|
67 |
+
# 请求节流时间窗口(秒)
|
68 |
+
TIME_WINDOW = 10
|
core/__pycache__/api_manager.cpython-313.pyc
ADDED
Binary file (8.62 kB). View file
|
|
core/api/__pycache__/anthropic.cpython-313.pyc
CHANGED
Binary files a/core/api/__pycache__/anthropic.cpython-313.pyc and b/core/api/__pycache__/anthropic.cpython-313.pyc differ
|
|
core/api/__pycache__/deepseek.cpython-313.pyc
CHANGED
Binary files a/core/api/__pycache__/deepseek.cpython-313.pyc and b/core/api/__pycache__/deepseek.cpython-313.pyc differ
|
|
core/api/__pycache__/google.cpython-313.pyc
CHANGED
Binary files a/core/api/__pycache__/google.cpython-313.pyc and b/core/api/__pycache__/google.cpython-313.pyc differ
|
|
core/api/__pycache__/openai.cpython-313.pyc
CHANGED
Binary files a/core/api/__pycache__/openai.cpython-313.pyc and b/core/api/__pycache__/openai.cpython-313.pyc differ
|
|
core/api/google.py
CHANGED
@@ -93,7 +93,7 @@ def check_paid_account(api_key):
|
|
93 |
"instances": [{"prompt": "Hi"}]
|
94 |
}
|
95 |
|
96 |
-
timeout =
|
97 |
|
98 |
try:
|
99 |
response = requests.post(imagen_url, params=params, json=payload, timeout=timeout)
|
|
|
93 |
"instances": [{"prompt": "Hi"}]
|
94 |
}
|
95 |
|
96 |
+
timeout = 3
|
97 |
|
98 |
try:
|
99 |
response = requests.post(imagen_url, params=params, json=payload, timeout=timeout)
|
core/api_manager.py
ADDED
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
API管理器 - 管理API请求的节流控制和队列调度
|
3 |
+
提供API调用的桥接功能和请求限流控制
|
4 |
+
"""
|
5 |
+
import time
|
6 |
+
import threading
|
7 |
+
import importlib
|
8 |
+
from typing import Any, Callable, Dict, List, Tuple, Union
|
9 |
+
import config
|
10 |
+
|
11 |
+
class ApiManager:
|
12 |
+
"""
|
13 |
+
API管理器类 - 负责处理API请求的节流控制和队列管理
|
14 |
+
|
15 |
+
为每个平台实现两个队列:
|
16 |
+
1. 完成队列: 记录发起请求的时间戳
|
17 |
+
2. 等待队列: 记录等待执行的请求
|
18 |
+
|
19 |
+
定时任务每秒执行一次,清理完成队列中的过期请求,
|
20 |
+
并处理等待队列中的请求(如果有空位)
|
21 |
+
"""
|
22 |
+
def __init__(self):
|
23 |
+
# 从config导入节流控制配置
|
24 |
+
self.platform_limits = config.PLATFORM_LIMITS
|
25 |
+
self.time_window = config.TIME_WINDOW
|
26 |
+
|
27 |
+
# 初始化平台队列
|
28 |
+
self.completed_queues = {platform: [] for platform in self.platform_limits}
|
29 |
+
self.waiting_queues = {platform: [] for platform in self.platform_limits}
|
30 |
+
|
31 |
+
# 线程安全锁
|
32 |
+
self.locks = {platform: threading.Lock() for platform in self.platform_limits}
|
33 |
+
|
34 |
+
# 启动调度器
|
35 |
+
self._scheduler_active = True
|
36 |
+
self._start_scheduler()
|
37 |
+
|
38 |
+
def _start_scheduler(self):
|
39 |
+
"""启动定时调度器,每秒执行一次队列管理"""
|
40 |
+
self._process_queues()
|
41 |
+
if self._scheduler_active:
|
42 |
+
# 每秒执行一次
|
43 |
+
threading.Timer(1.0, self._start_scheduler).start()
|
44 |
+
|
45 |
+
def _process_queues(self):
|
46 |
+
"""处理所有平台的队列"""
|
47 |
+
current_time = time.time()
|
48 |
+
|
49 |
+
for platform in self.platform_limits:
|
50 |
+
with self.locks[platform]:
|
51 |
+
# 1. 移除已超过时间窗口的完成请求
|
52 |
+
self._clean_completed_queue(platform, current_time)
|
53 |
+
|
54 |
+
# 2. 处理等待队列中的请求(如果有空位)
|
55 |
+
self._process_waiting_queue(platform)
|
56 |
+
|
57 |
+
def _clean_completed_queue(self, platform: str, current_time: float):
|
58 |
+
"""清理完成队列中超时的请求"""
|
59 |
+
# 移除已经超过时间窗口的请求
|
60 |
+
self.completed_queues[platform] = [
|
61 |
+
timestamp for timestamp in self.completed_queues[platform]
|
62 |
+
if current_time - timestamp < self.time_window
|
63 |
+
]
|
64 |
+
|
65 |
+
def _process_waiting_queue(self, platform: str):
|
66 |
+
"""处理等待队列中的请求"""
|
67 |
+
# 获取当前可用的请求数量
|
68 |
+
available_slots = self._get_available_slots(platform)
|
69 |
+
|
70 |
+
# 处理等待队列中的请求
|
71 |
+
while available_slots > 0 and self.waiting_queues[platform]:
|
72 |
+
# 获取等待队列头部的请求
|
73 |
+
request_data = self.waiting_queues[platform].pop(0)
|
74 |
+
request_func, args, kwargs, result_event, result_container = request_data
|
75 |
+
|
76 |
+
# 在新线程中执行请求
|
77 |
+
threading.Thread(
|
78 |
+
target=self._execute_request,
|
79 |
+
args=(platform, request_func, args, kwargs, result_event, result_container)
|
80 |
+
).start()
|
81 |
+
|
82 |
+
# 减少可用槽位
|
83 |
+
available_slots -= 1
|
84 |
+
|
85 |
+
def _get_available_slots(self, platform: str) -> int:
|
86 |
+
"""计算平台当前可用的请求槽位数"""
|
87 |
+
limit = self.platform_limits.get(platform, self.platform_limits['default'])
|
88 |
+
return max(0, limit - len(self.completed_queues[platform]))
|
89 |
+
|
90 |
+
def _execute_request(self, platform: str, request_func: Callable,
|
91 |
+
args: Tuple, kwargs: Dict,
|
92 |
+
result_event: threading.Event,
|
93 |
+
result_container: List):
|
94 |
+
"""执行请求并存储结果"""
|
95 |
+
try:
|
96 |
+
# 立即记录请求时间(添加到完成队列)
|
97 |
+
with self.locks[platform]:
|
98 |
+
self.completed_queues[platform].append(time.time())
|
99 |
+
|
100 |
+
# 执行请求
|
101 |
+
result = request_func(*args, **kwargs)
|
102 |
+
|
103 |
+
# 存储结果
|
104 |
+
result_container.append(result)
|
105 |
+
except Exception as e:
|
106 |
+
# 存储异常
|
107 |
+
result_container.append(e)
|
108 |
+
finally:
|
109 |
+
# 通知等待线程结果已准备好
|
110 |
+
result_event.set()
|
111 |
+
|
112 |
+
def execute(self, platform: str, method_name: str, *args, **kwargs):
|
113 |
+
"""
|
114 |
+
执行API请求,处理节流控制
|
115 |
+
|
116 |
+
Args:
|
117 |
+
platform: API平台名称 (如 'openai', 'anthropic' 等)
|
118 |
+
method_name: 要调用的方法名称 (如 'validate_api_key')
|
119 |
+
*args, **kwargs: 传递给方法的参数
|
120 |
+
|
121 |
+
Returns:
|
122 |
+
方法的返回值
|
123 |
+
"""
|
124 |
+
# 确保平台支持
|
125 |
+
if platform not in self.platform_limits and platform != 'default':
|
126 |
+
# 如果不是已知平台,则使用默认限制
|
127 |
+
platform = 'default'
|
128 |
+
|
129 |
+
# 导入相应平台的模块
|
130 |
+
try:
|
131 |
+
module = importlib.import_module(f"core.api.{platform}")
|
132 |
+
except ImportError:
|
133 |
+
raise ValueError(f"不支持的平台: {platform}")
|
134 |
+
|
135 |
+
# 获取方法
|
136 |
+
if not hasattr(module, method_name):
|
137 |
+
raise ValueError(f"平台 {platform} 没有方法 {method_name}")
|
138 |
+
|
139 |
+
method = getattr(module, method_name)
|
140 |
+
|
141 |
+
# 创建结果容器和事件
|
142 |
+
result_container = []
|
143 |
+
result_event = threading.Event()
|
144 |
+
|
145 |
+
# 请求函数
|
146 |
+
request_func = lambda *a, **kw: method(*a, **kw)
|
147 |
+
|
148 |
+
with self.locks[platform]:
|
149 |
+
# 检查是否有可用槽位
|
150 |
+
if len(self.completed_queues[platform]) < self.platform_limits.get(platform, self.platform_limits['default']):
|
151 |
+
# 有槽位,立即执行
|
152 |
+
threading.Thread(
|
153 |
+
target=self._execute_request,
|
154 |
+
args=(platform, request_func, args, kwargs, result_event, result_container)
|
155 |
+
).start()
|
156 |
+
else:
|
157 |
+
# 没有槽位,添加到等待队列
|
158 |
+
self.waiting_queues[platform].append((request_func, args, kwargs, result_event, result_container))
|
159 |
+
|
160 |
+
# 等待结果(同步阻塞)
|
161 |
+
result_event.wait()
|
162 |
+
|
163 |
+
# 获取结果
|
164 |
+
if not result_container:
|
165 |
+
raise RuntimeError("请求执行失败,没有结果")
|
166 |
+
|
167 |
+
result = result_container[0]
|
168 |
+
if isinstance(result, Exception):
|
169 |
+
raise result
|
170 |
+
|
171 |
+
return result
|
172 |
+
|
173 |
+
def shutdown(self):
|
174 |
+
"""关闭调度器"""
|
175 |
+
self._scheduler_active = False
|
176 |
+
|
177 |
+
# 全局API管理器实例
|
178 |
+
_api_manager = None
|
179 |
+
|
180 |
+
def get_api_manager():
|
181 |
+
"""
|
182 |
+
获取全局API管理器实例
|
183 |
+
|
184 |
+
Returns:
|
185 |
+
ApiManager: API管理器实例
|
186 |
+
"""
|
187 |
+
global _api_manager
|
188 |
+
if _api_manager is None:
|
189 |
+
_api_manager = ApiManager()
|
190 |
+
return _api_manager
|
191 |
+
|
192 |
+
def start_service():
|
193 |
+
"""
|
194 |
+
启动API管理服务
|
195 |
+
|
196 |
+
Returns:
|
197 |
+
list: 服务相关的线程列表
|
198 |
+
"""
|
199 |
+
# 初始化API管理器
|
200 |
+
api_manager = get_api_manager()
|
201 |
+
|
202 |
+
# 此处可以添加其他需要启动的服务线程
|
203 |
+
# 例如: 监控线程、日志线程等
|
204 |
+
|
205 |
+
# 返回服务线程列表(目前API管理器的调度线程是内部管理的,所以返回空列表)
|
206 |
+
return []
|
cron.py
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
定时任务模块 - 定期更新所有API密钥
|
3 |
+
"""
|
4 |
+
import json
|
5 |
+
import os
|
6 |
+
import time
|
7 |
+
import sched
|
8 |
+
import threading
|
9 |
+
import sqlite3
|
10 |
+
from update import update
|
11 |
+
from utils.db import get_db_connection
|
12 |
+
from config import API_KEYS_FILE
|
13 |
+
|
14 |
+
# 定义更新间隔(12小时,单位:秒)
|
15 |
+
UPDATE_INTERVAL = 12 * 60 * 60
|
16 |
+
|
17 |
+
def update_all_keys():
|
18 |
+
"""
|
19 |
+
更新所有API密钥
|
20 |
+
"""
|
21 |
+
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 开始更新所有API密钥...")
|
22 |
+
|
23 |
+
# 从SQLite数据库获取所有密钥ID
|
24 |
+
conn = get_db_connection()
|
25 |
+
key_ids = []
|
26 |
+
|
27 |
+
try:
|
28 |
+
cursor = conn.cursor()
|
29 |
+
cursor.execute('SELECT id FROM api_keys')
|
30 |
+
rows = cursor.fetchall()
|
31 |
+
key_ids = [row['id'] for row in rows]
|
32 |
+
except sqlite3.Error as e:
|
33 |
+
print(f"从数据库获取密钥时出错: {str(e)}")
|
34 |
+
|
35 |
+
# 备用方案:尝试从JSON文件读取
|
36 |
+
if os.path.exists(API_KEYS_FILE):
|
37 |
+
try:
|
38 |
+
with open(API_KEYS_FILE, "r", encoding="utf-8") as f:
|
39 |
+
data = json.load(f)
|
40 |
+
key_ids = [key["id"] for key in data.get("api_keys", [])]
|
41 |
+
except Exception as e:
|
42 |
+
print(f"读取API密钥文件失败: {str(e)}")
|
43 |
+
return
|
44 |
+
finally:
|
45 |
+
if conn:
|
46 |
+
conn.close()
|
47 |
+
|
48 |
+
# 如果没有找到密钥,则退出
|
49 |
+
if not key_ids:
|
50 |
+
print("没有找到API密钥,跳过更新")
|
51 |
+
return
|
52 |
+
|
53 |
+
# 逐个更新API密钥
|
54 |
+
for key_id in key_ids:
|
55 |
+
result = update(key_id)
|
56 |
+
if result["success"]:
|
57 |
+
print(f" - 密钥 {key_id} 更新成功")
|
58 |
+
else:
|
59 |
+
print(f" - 密钥 {key_id} 更新失败: {result['message']}")
|
60 |
+
|
61 |
+
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 完成所有API密钥更新")
|
62 |
+
|
63 |
+
def run_scheduler():
|
64 |
+
"""
|
65 |
+
运行定时任务
|
66 |
+
"""
|
67 |
+
scheduler = sched.scheduler(time.time, time.sleep)
|
68 |
+
|
69 |
+
def scheduled_update():
|
70 |
+
update_all_keys()
|
71 |
+
# 再次安排下一次更新
|
72 |
+
scheduler.enter(UPDATE_INTERVAL, 1, scheduled_update, ())
|
73 |
+
|
74 |
+
# 首次安排更新
|
75 |
+
scheduler.enter(UPDATE_INTERVAL, 1, scheduled_update, ())
|
76 |
+
|
77 |
+
print(f"定时任务已启动,每 {UPDATE_INTERVAL // 3600} 小时更新一次API密钥")
|
78 |
+
scheduler.run()
|
79 |
+
|
80 |
+
if __name__ == "__main__":
|
81 |
+
# 在单独的线程中运行定时任务,避免阻塞主线程
|
82 |
+
scheduler_thread = threading.Thread(target=run_scheduler)
|
83 |
+
scheduler_thread.daemon = True # 设置为守护线程,当主线程退出时自动结束
|
84 |
+
scheduler_thread.start()
|
85 |
+
|
86 |
+
# 保持主线程运行(可选,根据实际需求)
|
87 |
+
while True:
|
88 |
+
time.sleep(60)
|
models/__pycache__/api_key.cpython-313.pyc
CHANGED
Binary files a/models/__pycache__/api_key.cpython-313.pyc and b/models/__pycache__/api_key.cpython-313.pyc differ
|
|
models/api_key.py
CHANGED
@@ -6,14 +6,16 @@ import uuid
|
|
6 |
from datetime import datetime
|
7 |
import os
|
8 |
import pytz
|
9 |
-
|
|
|
|
|
10 |
|
11 |
class ApiKeyManager:
|
12 |
"""管理API密钥的类"""
|
13 |
|
14 |
@staticmethod
|
15 |
def load_keys():
|
16 |
-
"""加载所有API密钥"""
|
17 |
if not os.path.exists(API_KEYS_FILE):
|
18 |
with open(API_KEYS_FILE, 'w', encoding='utf-8') as f:
|
19 |
json.dump({"api_keys": []}, f, ensure_ascii=False, indent=2)
|
@@ -27,49 +29,124 @@ class ApiKeyManager:
|
|
27 |
|
28 |
@staticmethod
|
29 |
def save_keys(data):
|
30 |
-
"""保存API密钥数据"""
|
31 |
with open(API_KEYS_FILE, 'w', encoding='utf-8') as f:
|
32 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
33 |
|
34 |
@staticmethod
|
35 |
def get_all_keys():
|
36 |
"""获取所有密钥"""
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
@staticmethod
|
40 |
def add_key(platform, name, key):
|
41 |
"""添加新的API密钥"""
|
42 |
-
api_keys_data = ApiKeyManager.load_keys()
|
43 |
-
|
44 |
# 过滤掉key中的单引号、双引号、小括号、方括号和空格,防止存储时出错
|
45 |
if key:
|
46 |
key = key.replace("'", "").replace('"', "").replace('(', "").replace(')', "").replace('[', "").replace(']', "").replace(' ', "")
|
47 |
|
|
|
|
|
|
|
48 |
new_key = {
|
49 |
-
"id":
|
50 |
"platform": platform,
|
51 |
"name": name,
|
52 |
"key": key,
|
53 |
-
"created_at":
|
|
|
|
|
|
|
|
|
|
|
54 |
}
|
55 |
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
@staticmethod
|
62 |
def delete_key(key_id):
|
63 |
"""删除指定的API密钥"""
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
@staticmethod
|
75 |
def bulk_delete(key_ids):
|
@@ -77,16 +154,38 @@ class ApiKeyManager:
|
|
77 |
if not key_ids:
|
78 |
return 0
|
79 |
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
|
91 |
@staticmethod
|
92 |
def bulk_add_keys(keys_data):
|
@@ -101,54 +200,146 @@ class ApiKeyManager:
|
|
101 |
if not keys_data:
|
102 |
return []
|
103 |
|
104 |
-
api_keys_data = ApiKeyManager.load_keys()
|
105 |
added_keys = []
|
106 |
-
|
107 |
now = datetime.now(pytz.timezone('Asia/Shanghai')).isoformat()
|
108 |
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
key = key_info.get("key")
|
113 |
-
|
114 |
-
# 过滤掉key中的单引号、双引号、小括号、方括号和空格,防止存储时出错
|
115 |
-
if key:
|
116 |
-
key = key.replace("'", "").replace('"', "").replace('(', "").replace(')', "").replace('[', "").replace(']', "").replace(' ', "")
|
117 |
-
|
118 |
-
new_key = {
|
119 |
-
"id": str(uuid.uuid4()),
|
120 |
-
"platform": platform,
|
121 |
-
"name": name,
|
122 |
-
"key": key,
|
123 |
-
"created_at": now
|
124 |
-
}
|
125 |
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
|
134 |
@staticmethod
|
135 |
def update_key(key_id, name, key):
|
136 |
"""更新API密钥信息"""
|
137 |
-
api_keys_data = ApiKeyManager.load_keys()
|
138 |
-
|
139 |
# 过滤掉key中的单引号、双引号、小括号、方括号和空格,防止存储时出错
|
140 |
if key:
|
141 |
key = key.replace("'", "").replace('"', "").replace('(', "").replace(')', "").replace('[', "").replace(']', "").replace(' ', "")
|
142 |
-
|
143 |
-
updated_key = None
|
144 |
-
for k in api_keys_data["api_keys"]:
|
145 |
-
if k.get("id") == key_id:
|
146 |
-
k["name"] = name
|
147 |
-
k["key"] = key
|
148 |
-
updated_key = k
|
149 |
-
break
|
150 |
|
151 |
-
|
152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
|
154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
6 |
from datetime import datetime
|
7 |
import os
|
8 |
import pytz
|
9 |
+
import sqlite3
|
10 |
+
from utils.db import get_db_connection
|
11 |
+
from config import API_KEYS_FILE, DATABASE_PATH
|
12 |
|
13 |
class ApiKeyManager:
|
14 |
"""管理API密钥的类"""
|
15 |
|
16 |
@staticmethod
|
17 |
def load_keys():
|
18 |
+
"""加载所有API密钥 (兼容旧的JSON方式)"""
|
19 |
if not os.path.exists(API_KEYS_FILE):
|
20 |
with open(API_KEYS_FILE, 'w', encoding='utf-8') as f:
|
21 |
json.dump({"api_keys": []}, f, ensure_ascii=False, indent=2)
|
|
|
29 |
|
30 |
@staticmethod
|
31 |
def save_keys(data):
|
32 |
+
"""保存API密钥数据 (兼容旧的JSON方式)"""
|
33 |
with open(API_KEYS_FILE, 'w', encoding='utf-8') as f:
|
34 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
35 |
|
36 |
@staticmethod
|
37 |
def get_all_keys():
|
38 |
"""获取所有密钥"""
|
39 |
+
conn = get_db_connection()
|
40 |
+
try:
|
41 |
+
cursor = conn.cursor()
|
42 |
+
cursor.execute('SELECT * FROM api_keys')
|
43 |
+
rows = cursor.fetchall()
|
44 |
+
|
45 |
+
# 转换为字典列表
|
46 |
+
api_keys = []
|
47 |
+
for row in rows:
|
48 |
+
key_dict = dict(row)
|
49 |
+
# 转换success字段从整数为布尔值
|
50 |
+
key_dict['success'] = bool(key_dict['success'])
|
51 |
+
api_keys.append(key_dict)
|
52 |
+
|
53 |
+
return {"api_keys": api_keys}
|
54 |
+
except sqlite3.Error as e:
|
55 |
+
print(f"获取所有密钥时出错: {str(e)}")
|
56 |
+
# 如果数据库出错,尝试从JSON文件加载
|
57 |
+
return ApiKeyManager.load_keys()
|
58 |
+
finally:
|
59 |
+
conn.close()
|
60 |
|
61 |
@staticmethod
|
62 |
def add_key(platform, name, key):
|
63 |
"""添加新的API密钥"""
|
|
|
|
|
64 |
# 过滤掉key中的单引号、双引号、小括号、方括号和空格,防止存储时出错
|
65 |
if key:
|
66 |
key = key.replace("'", "").replace('"', "").replace('(', "").replace(')', "").replace('[', "").replace(']', "").replace(' ', "")
|
67 |
|
68 |
+
current_time = datetime.now(pytz.timezone('Asia/Shanghai')).isoformat()
|
69 |
+
new_key_id = str(uuid.uuid4())
|
70 |
+
|
71 |
new_key = {
|
72 |
+
"id": new_key_id,
|
73 |
"platform": platform,
|
74 |
"name": name,
|
75 |
"key": key,
|
76 |
+
"created_at": current_time,
|
77 |
+
"updated_at": current_time,
|
78 |
+
"success": False,
|
79 |
+
"return_message": "等待测试",
|
80 |
+
"states": "",
|
81 |
+
"balance": 0
|
82 |
}
|
83 |
|
84 |
+
conn = get_db_connection()
|
85 |
+
try:
|
86 |
+
cursor = conn.cursor()
|
87 |
+
cursor.execute('''
|
88 |
+
INSERT INTO api_keys
|
89 |
+
(id, platform, name, key, created_at, updated_at, success, return_message, states, balance)
|
90 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
91 |
+
''', (
|
92 |
+
new_key_id,
|
93 |
+
platform,
|
94 |
+
name,
|
95 |
+
key,
|
96 |
+
current_time,
|
97 |
+
current_time,
|
98 |
+
0, # success是整数: 0表示False
|
99 |
+
"等待测试",
|
100 |
+
"",
|
101 |
+
0
|
102 |
+
))
|
103 |
+
conn.commit()
|
104 |
+
|
105 |
+
# 自动调用update函数验证密钥
|
106 |
+
from update import update
|
107 |
+
try:
|
108 |
+
print(f"[自动验证] 正在验证密钥 ID: {new_key_id}")
|
109 |
+
update_result = update(new_key_id)
|
110 |
+
print(f"[自动验证] 密钥验证结果: {update_result}")
|
111 |
+
except Exception as e:
|
112 |
+
print(f"[自动验证] 验证密钥时出错: {str(e)}")
|
113 |
+
|
114 |
+
return new_key
|
115 |
+
except sqlite3.Error as e:
|
116 |
+
print(f"添加密钥时出错: {str(e)}")
|
117 |
+
conn.rollback()
|
118 |
+
# 如果数据库出错,尝试使用JSON方式保存
|
119 |
+
api_keys_data = ApiKeyManager.load_keys()
|
120 |
+
api_keys_data["api_keys"].append(new_key)
|
121 |
+
ApiKeyManager.save_keys(api_keys_data)
|
122 |
+
return new_key
|
123 |
+
finally:
|
124 |
+
conn.close()
|
125 |
|
126 |
@staticmethod
|
127 |
def delete_key(key_id):
|
128 |
"""删除指定的API密钥"""
|
129 |
+
conn = get_db_connection()
|
130 |
+
try:
|
131 |
+
cursor = conn.cursor()
|
132 |
+
cursor.execute('DELETE FROM api_keys WHERE id = ?', (key_id,))
|
133 |
+
deleted = cursor.rowcount > 0
|
134 |
+
conn.commit()
|
135 |
+
return deleted
|
136 |
+
except sqlite3.Error as e:
|
137 |
+
print(f"删除密钥时出错: {str(e)}")
|
138 |
+
conn.rollback()
|
139 |
+
# 如果数据库出错,尝试从JSON文件删除
|
140 |
+
api_keys_data = ApiKeyManager.load_keys()
|
141 |
+
original_count = len(api_keys_data["api_keys"])
|
142 |
+
api_keys_data["api_keys"] = [k for k in api_keys_data["api_keys"] if k.get("id") != key_id]
|
143 |
+
|
144 |
+
if len(api_keys_data["api_keys"]) < original_count:
|
145 |
+
ApiKeyManager.save_keys(api_keys_data)
|
146 |
+
return True
|
147 |
+
return False
|
148 |
+
finally:
|
149 |
+
conn.close()
|
150 |
|
151 |
@staticmethod
|
152 |
def bulk_delete(key_ids):
|
|
|
154 |
if not key_ids:
|
155 |
return 0
|
156 |
|
157 |
+
conn = get_db_connection()
|
158 |
+
try:
|
159 |
+
cursor = conn.cursor()
|
160 |
+
# 获取当前的密钥数量
|
161 |
+
cursor.execute('SELECT COUNT(*) FROM api_keys')
|
162 |
+
original_count = cursor.fetchone()[0]
|
163 |
+
|
164 |
+
# 使用参数化查询构建占位符
|
165 |
+
placeholders = ','.join(['?'] * len(key_ids))
|
166 |
+
cursor.execute(f'DELETE FROM api_keys WHERE id IN ({placeholders})', key_ids)
|
167 |
+
|
168 |
+
# 获取删除后的密钥数量
|
169 |
+
cursor.execute('SELECT COUNT(*) FROM api_keys')
|
170 |
+
new_count = cursor.fetchone()[0]
|
171 |
+
|
172 |
+
conn.commit()
|
173 |
+
return original_count - new_count
|
174 |
+
except sqlite3.Error as e:
|
175 |
+
print(f"批量删除密钥时出错: {str(e)}")
|
176 |
+
conn.rollback()
|
177 |
+
# 如果数据库出错,尝试从JSON文件删除
|
178 |
+
api_keys_data = ApiKeyManager.load_keys()
|
179 |
+
original_count = len(api_keys_data["api_keys"])
|
180 |
+
api_keys_data["api_keys"] = [k for k in api_keys_data["api_keys"] if k.get("id") not in key_ids]
|
181 |
+
|
182 |
+
deleted_count = original_count - len(api_keys_data["api_keys"])
|
183 |
+
if deleted_count > 0:
|
184 |
+
ApiKeyManager.save_keys(api_keys_data)
|
185 |
+
|
186 |
+
return deleted_count
|
187 |
+
finally:
|
188 |
+
conn.close()
|
189 |
|
190 |
@staticmethod
|
191 |
def bulk_add_keys(keys_data):
|
|
|
200 |
if not keys_data:
|
201 |
return []
|
202 |
|
|
|
203 |
added_keys = []
|
|
|
204 |
now = datetime.now(pytz.timezone('Asia/Shanghai')).isoformat()
|
205 |
|
206 |
+
conn = get_db_connection()
|
207 |
+
try:
|
208 |
+
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
|
210 |
+
for key_info in keys_data:
|
211 |
+
platform = key_info.get("platform")
|
212 |
+
name = key_info.get("name")
|
213 |
+
key = key_info.get("key")
|
214 |
+
|
215 |
+
# 过滤掉key中的单引号、双引号、小括号、方括号和空格,防止存储时出错
|
216 |
+
if key:
|
217 |
+
key = key.replace("'", "").replace('"', "").replace('(', "").replace(')', "").replace('[', "").replace(']', "").replace(' ', "")
|
218 |
+
|
219 |
+
new_key_id = str(uuid.uuid4())
|
220 |
+
|
221 |
+
new_key = {
|
222 |
+
"id": new_key_id,
|
223 |
+
"platform": platform,
|
224 |
+
"name": name,
|
225 |
+
"key": key,
|
226 |
+
"created_at": now,
|
227 |
+
"updated_at": now,
|
228 |
+
"success": False,
|
229 |
+
"return_message": "等待测试",
|
230 |
+
"states": "",
|
231 |
+
"balance": 0
|
232 |
+
}
|
233 |
+
|
234 |
+
cursor.execute('''
|
235 |
+
INSERT INTO api_keys
|
236 |
+
(id, platform, name, key, created_at, updated_at, success, return_message, states, balance)
|
237 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
238 |
+
''', (
|
239 |
+
new_key_id,
|
240 |
+
platform,
|
241 |
+
name,
|
242 |
+
key,
|
243 |
+
now,
|
244 |
+
now,
|
245 |
+
0, # success是整数: 0表示False
|
246 |
+
"等待测试",
|
247 |
+
"",
|
248 |
+
0
|
249 |
+
))
|
250 |
+
|
251 |
+
added_keys.append(new_key)
|
252 |
+
|
253 |
+
conn.commit()
|
254 |
+
|
255 |
+
# 在批量添加完成后,启动一个单独的线程来验证所有新添加的密钥
|
256 |
+
from update import update
|
257 |
+
from threading import Thread
|
258 |
+
|
259 |
+
def validate_keys():
|
260 |
+
for new_key in added_keys:
|
261 |
+
try:
|
262 |
+
key_id = new_key["id"]
|
263 |
+
print(f"[自动验证] 正在验证密钥 ID: {key_id}")
|
264 |
+
update_result = update(key_id)
|
265 |
+
print(f"[自动验证] 密钥验证结果: {update_result}")
|
266 |
+
except Exception as e:
|
267 |
+
print(f"[自动验证] 验证密钥时出错: {str(e)}")
|
268 |
+
|
269 |
+
# 启动验证线程
|
270 |
+
print(f"[自动验证] 启动验证线程,验证 {len(added_keys)} 个新添加的密钥")
|
271 |
+
validation_thread = Thread(target=validate_keys)
|
272 |
+
validation_thread.daemon = True # 设置为守护线程,当主线程退出时自动结束
|
273 |
+
validation_thread.start()
|
274 |
+
|
275 |
+
return added_keys
|
276 |
+
except sqlite3.Error as e:
|
277 |
+
print(f"批量添加密钥时出错: {str(e)}")
|
278 |
+
conn.rollback()
|
279 |
+
# 如果数据库出错,尝试使用JSON方式保存
|
280 |
+
api_keys_data = ApiKeyManager.load_keys()
|
281 |
+
for key in added_keys:
|
282 |
+
api_keys_data["api_keys"].append(key)
|
283 |
+
ApiKeyManager.save_keys(api_keys_data)
|
284 |
+
return added_keys
|
285 |
+
finally:
|
286 |
+
conn.close()
|
287 |
|
288 |
@staticmethod
|
289 |
def update_key(key_id, name, key):
|
290 |
"""更新API密钥信息"""
|
|
|
|
|
291 |
# 过滤掉key中的单引号、双引号、小括号、方括号和空格,防止存储时出错
|
292 |
if key:
|
293 |
key = key.replace("'", "").replace('"', "").replace('(', "").replace(')', "").replace('[', "").replace(']', "").replace(' ', "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
|
295 |
+
updated_at = datetime.now(pytz.timezone('Asia/Shanghai')).isoformat()
|
296 |
+
|
297 |
+
conn = get_db_connection()
|
298 |
+
try:
|
299 |
+
cursor = conn.cursor()
|
300 |
+
|
301 |
+
# 更新密钥
|
302 |
+
cursor.execute('''
|
303 |
+
UPDATE api_keys
|
304 |
+
SET name = ?, key = ?, updated_at = ?, success = ?, return_message = ?
|
305 |
+
WHERE id = ?
|
306 |
+
''', (name, key, updated_at, 0, "等待测试", key_id))
|
307 |
+
|
308 |
+
if cursor.rowcount > 0:
|
309 |
+
conn.commit()
|
310 |
+
|
311 |
+
# 获取更新后的密钥信息
|
312 |
+
cursor.execute('SELECT * FROM api_keys WHERE id = ?', (key_id,))
|
313 |
+
row = cursor.fetchone()
|
314 |
+
|
315 |
+
if row:
|
316 |
+
updated_key = dict(row)
|
317 |
+
# 转换success字段从整数为布尔值
|
318 |
+
updated_key['success'] = bool(updated_key['success'])
|
319 |
+
return updated_key
|
320 |
+
|
321 |
+
return None
|
322 |
+
except sqlite3.Error as e:
|
323 |
+
print(f"更新密钥时出错: {str(e)}")
|
324 |
+
conn.rollback()
|
325 |
+
|
326 |
+
# 如果数据库出错,尝试使用JSON方式更新
|
327 |
+
api_keys_data = ApiKeyManager.load_keys()
|
328 |
+
updated_key = None
|
329 |
+
for k in api_keys_data["api_keys"]:
|
330 |
+
if k.get("id") == key_id:
|
331 |
+
k["name"] = name
|
332 |
+
k["key"] = key
|
333 |
+
k["updated_at"] = updated_at
|
334 |
+
# 重置验证状态
|
335 |
+
k["success"] = False
|
336 |
+
k["return_message"] = "等待测试"
|
337 |
+
updated_key = k
|
338 |
+
break
|
339 |
|
340 |
+
if updated_key:
|
341 |
+
ApiKeyManager.save_keys(api_keys_data)
|
342 |
+
|
343 |
+
return updated_key
|
344 |
+
finally:
|
345 |
+
conn.close()
|
routes/__pycache__/api.cpython-313.pyc
CHANGED
Binary files a/routes/__pycache__/api.cpython-313.pyc and b/routes/__pycache__/api.cpython-313.pyc differ
|
|
routes/__pycache__/web.cpython-313.pyc
CHANGED
Binary files a/routes/__pycache__/web.cpython-313.pyc and b/routes/__pycache__/web.cpython-313.pyc differ
|
|
routes/api.py
CHANGED
@@ -67,6 +67,22 @@ def bulk_add_api_keys():
|
|
67 |
"message": f"成功添加 {len(added_keys)} 个API密钥"
|
68 |
})
|
69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
@api_bp.route('/keys/<key_id>', methods=['PUT'])
|
71 |
def edit_api_key(key_id):
|
72 |
"""更新API密钥信息"""
|
|
|
67 |
"message": f"成功添加 {len(added_keys)} 个API密钥"
|
68 |
})
|
69 |
|
70 |
+
@api_bp.route('/keys/update/<key_id>', methods=['POST', 'GET'])
|
71 |
+
def update_api_key_status(key_id):
|
72 |
+
"""更新API密钥状态"""
|
73 |
+
from update import update
|
74 |
+
try:
|
75 |
+
print(f"正在更新密钥ID: {key_id}")
|
76 |
+
result = update(key_id)
|
77 |
+
print(f"更新结果: {result}")
|
78 |
+
return jsonify({"success": True, "data": result})
|
79 |
+
except Exception as e:
|
80 |
+
import traceback
|
81 |
+
error_msg = str(e)
|
82 |
+
trace = traceback.format_exc()
|
83 |
+
print(f"更新密钥时出错: {error_msg}\n{trace}")
|
84 |
+
return jsonify({"success": False, "error": error_msg, "traceback": trace}), 500
|
85 |
+
|
86 |
@api_bp.route('/keys/<key_id>', methods=['PUT'])
|
87 |
def edit_api_key(key_id):
|
88 |
"""更新API密钥信息"""
|
routes/web.py
CHANGED
@@ -33,6 +33,11 @@ def index():
|
|
33 |
is_ajax = request.args.get('ajax', '0') == '1'
|
34 |
return render_template('index.html', platforms=PLATFORMS, grouped_keys=grouped_keys, platform_styles=PLATFORM_STYLES)
|
35 |
|
|
|
|
|
|
|
|
|
|
|
36 |
@web_bp.route('/login', methods=['GET', 'POST'])
|
37 |
def login():
|
38 |
"""用户登录"""
|
|
|
33 |
is_ajax = request.args.get('ajax', '0') == '1'
|
34 |
return render_template('index.html', platforms=PLATFORMS, grouped_keys=grouped_keys, platform_styles=PLATFORM_STYLES)
|
35 |
|
36 |
+
@web_bp.route('/update-test')
|
37 |
+
def update_test():
|
38 |
+
"""API密钥更新测试页面"""
|
39 |
+
return render_template('update_test.html')
|
40 |
+
|
41 |
@web_bp.route('/login', methods=['GET', 'POST'])
|
42 |
def login():
|
43 |
"""用户登录"""
|
static/js/api-key-manager/api-key-creator.js
CHANGED
@@ -4,6 +4,7 @@
|
|
4 |
*/
|
5 |
|
6 |
// 添加API密钥
|
|
|
7 |
async function addApiKey() {
|
8 |
if (!this.newKey.platform || !this.newKey.key) {
|
9 |
this.errorMessage = '请填写所有必填字段。';
|
@@ -120,6 +121,30 @@ async function addApiKey() {
|
|
120 |
|
121 |
// 重新加载API密钥数据而不刷新页面
|
122 |
this.loadApiKeys();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
// 构建通知消息
|
125 |
let title = `已添加 ${addedCount} 个API密钥`;
|
|
|
4 |
*/
|
5 |
|
6 |
// 添加API密钥
|
7 |
+
|
8 |
async function addApiKey() {
|
9 |
if (!this.newKey.platform || !this.newKey.key) {
|
10 |
this.errorMessage = '请填写所有必填字段。';
|
|
|
121 |
|
122 |
// 重新加载API密钥数据而不刷新页面
|
123 |
this.loadApiKeys();
|
124 |
+
|
125 |
+
// 为每个新添加的API密钥调用update API
|
126 |
+
if (data.keys && data.keys.length > 0) {
|
127 |
+
// 延迟200ms确保UI更新
|
128 |
+
setTimeout(() => {
|
129 |
+
data.keys.forEach(key => {
|
130 |
+
console.log(`正在请求更新API密钥: ${key.id}`);
|
131 |
+
fetch(`/api/keys/update/${key.id}`, {
|
132 |
+
method: 'POST'
|
133 |
+
}).then(response => {
|
134 |
+
if (!response.ok) {
|
135 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
136 |
+
}
|
137 |
+
return response.json();
|
138 |
+
}).then(data => {
|
139 |
+
console.log(`API密钥 ${key.id} 更新成功:`, data);
|
140 |
+
// 更新成功后重新加载密钥列表
|
141 |
+
this.loadApiKeys();
|
142 |
+
}).catch(error => {
|
143 |
+
console.error(`自动更新API密钥 ${key.id} 时出错:`, error);
|
144 |
+
});
|
145 |
+
});
|
146 |
+
}, 200);
|
147 |
+
}
|
148 |
|
149 |
// 构建通知消息
|
150 |
let title = `已添加 ${addedCount} 个API密钥`;
|
static/js/api-key-manager/api-key-editor.js
CHANGED
@@ -130,6 +130,25 @@ async function updateApiKey() {
|
|
130 |
// 重新加载API密钥数据而不刷新页面
|
131 |
this.loadApiKeys();
|
132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
Toast.fire({
|
134 |
icon: 'success',
|
135 |
title: 'API密钥已更新',
|
|
|
130 |
// 重新加载API密钥数据而不刷新页面
|
131 |
this.loadApiKeys();
|
132 |
|
133 |
+
// 调用update API更新密钥状态
|
134 |
+
setTimeout(() => {
|
135 |
+
console.log(`正在请求更新API密钥: ${this.editKey.id}`);
|
136 |
+
fetch(`/api/keys/update/${this.editKey.id}`, {
|
137 |
+
method: 'POST'
|
138 |
+
}).then(response => {
|
139 |
+
if (!response.ok) {
|
140 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
141 |
+
}
|
142 |
+
return response.json();
|
143 |
+
}).then(data => {
|
144 |
+
console.log(`API密钥 ${this.editKey.id} 更新成功:`, data);
|
145 |
+
// 更新成功后重新加载密钥列表
|
146 |
+
this.loadApiKeys();
|
147 |
+
}).catch(error => {
|
148 |
+
console.error(`自动更新API密钥 ${this.editKey.id} 时出错:`, error);
|
149 |
+
});
|
150 |
+
}, 200);
|
151 |
+
|
152 |
Toast.fire({
|
153 |
icon: 'success',
|
154 |
title: 'API密钥已更新',
|
templates/components/api_key_list.html
CHANGED
@@ -213,9 +213,9 @@
|
|
213 |
<!-- 密钥名称 -->
|
214 |
<h3 class="text-sm font-medium text-gray-900 truncate">{{ key.name }}</h3>
|
215 |
|
216 |
-
<!--
|
217 |
<div class="mt-1.5 flex items-center">
|
218 |
-
<!--
|
219 |
<div class="flex items-center mr-2">
|
220 |
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-gray-500 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
221 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
@@ -223,14 +223,59 @@
|
|
223 |
<span class="text-xs text-gray-600">{{ key.created_at.split('T')[0] }}</span>
|
224 |
</div>
|
225 |
|
226 |
-
<!--
|
227 |
-
<div class="flex items-center">
|
228 |
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-gray-500 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
229 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
230 |
</svg>
|
231 |
-
<span class="text-xs text-gray-600"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
</div>
|
233 |
</div>
|
|
|
234 |
|
235 |
<!-- 密钥值 -->
|
236 |
<div class="mt-1 relative group key-scroll-container custom-scrollbar overflow-x-auto">
|
|
|
213 |
<!-- 密钥名称 -->
|
214 |
<h3 class="text-sm font-medium text-gray-900 truncate">{{ key.name }}</h3>
|
215 |
|
216 |
+
<!-- 创建和更新时间 -->
|
217 |
<div class="mt-1.5 flex items-center">
|
218 |
+
<!-- 创建时间部分 -->
|
219 |
<div class="flex items-center mr-2">
|
220 |
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-gray-500 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
221 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
|
223 |
<span class="text-xs text-gray-600">{{ key.created_at.split('T')[0] }}</span>
|
224 |
</div>
|
225 |
|
226 |
+
<!-- 创建时间 -->
|
227 |
+
<div class="flex items-center mr-2">
|
228 |
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-gray-500 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
229 |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
230 |
</svg>
|
231 |
+
<span class="text-xs text-gray-600">创建: {{ key.created_at.split('T')[1].split('.')[0] }}</span>
|
232 |
+
</div>
|
233 |
+
|
234 |
+
<!-- 如果存在更新时间,显示更新时间 -->
|
235 |
+
{% if key.updated_at %}
|
236 |
+
<div class="flex items-center">
|
237 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-blue-500 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
238 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
239 |
+
</svg>
|
240 |
+
<span class="text-xs text-blue-600">更新: {{ key.updated_at.split('T')[1].split('.')[0] }}</span>
|
241 |
+
</div>
|
242 |
+
{% endif %}
|
243 |
+
</div>
|
244 |
+
|
245 |
+
<!-- 显示API状态信息 -->
|
246 |
+
{% if key.success == True %}
|
247 |
+
<div class="mt-1 flex flex-wrap gap-2">
|
248 |
+
<!-- 如果有余额信息,显示余额 -->
|
249 |
+
{% if key.balance %}
|
250 |
+
<div class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
251 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
252 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
253 |
+
</svg>
|
254 |
+
余额: {{ key.balance }}
|
255 |
+
</div>
|
256 |
+
{% endif %}
|
257 |
+
|
258 |
+
<!-- 如果有状态信息,显示状态 -->
|
259 |
+
{% if key.states %}
|
260 |
+
<div class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
261 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
262 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
263 |
+
</svg>
|
264 |
+
状态: {{ key.states }}
|
265 |
+
</div>
|
266 |
+
{% endif %}
|
267 |
+
</div>
|
268 |
+
{% elif key.success == False and key.return_message %}
|
269 |
+
<!-- 显示错误信息 -->
|
270 |
+
<div class="mt-1 flex items-center">
|
271 |
+
<div class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
272 |
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
273 |
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
274 |
+
</svg>
|
275 |
+
{{ key.return_message }}
|
276 |
</div>
|
277 |
</div>
|
278 |
+
{% endif %}
|
279 |
|
280 |
<!-- 密钥值 -->
|
281 |
<div class="mt-1 relative group key-scroll-container custom-scrollbar overflow-x-auto">
|
templates/update_test.html
ADDED
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="zh-CN">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>API密钥测试更新</title>
|
7 |
+
<link rel="stylesheet" href="/static/css/base.css">
|
8 |
+
<link rel="stylesheet" href="/static/css/style.css">
|
9 |
+
<style>
|
10 |
+
.container {
|
11 |
+
max-width: 800px;
|
12 |
+
margin: 0 auto;
|
13 |
+
padding: 20px;
|
14 |
+
}
|
15 |
+
.key-list {
|
16 |
+
margin-top: 20px;
|
17 |
+
border: 1px solid #e5e7eb;
|
18 |
+
border-radius: 8px;
|
19 |
+
overflow: hidden;
|
20 |
+
}
|
21 |
+
.key-item {
|
22 |
+
padding: 15px;
|
23 |
+
border-bottom: 1px solid #e5e7eb;
|
24 |
+
display: flex;
|
25 |
+
justify-content: space-between;
|
26 |
+
align-items: center;
|
27 |
+
}
|
28 |
+
.key-item:last-child {
|
29 |
+
border-bottom: none;
|
30 |
+
}
|
31 |
+
.key-item:hover {
|
32 |
+
background-color: #f9fafb;
|
33 |
+
}
|
34 |
+
.key-info {
|
35 |
+
flex-grow: 1;
|
36 |
+
}
|
37 |
+
.key-name {
|
38 |
+
font-weight: 500;
|
39 |
+
margin-bottom: 5px;
|
40 |
+
}
|
41 |
+
.key-value {
|
42 |
+
font-family: monospace;
|
43 |
+
color: #6b7280;
|
44 |
+
word-break: break-all;
|
45 |
+
}
|
46 |
+
.key-platform {
|
47 |
+
color: #4f46e5;
|
48 |
+
font-size: 0.875rem;
|
49 |
+
margin-bottom: 8px;
|
50 |
+
}
|
51 |
+
.key-status {
|
52 |
+
display: flex;
|
53 |
+
flex-direction: column;
|
54 |
+
align-items: flex-end;
|
55 |
+
min-width: 120px;
|
56 |
+
}
|
57 |
+
.update-btn {
|
58 |
+
padding: 6px 12px;
|
59 |
+
border-radius: 6px;
|
60 |
+
background-color: #4f46e5;
|
61 |
+
color: white;
|
62 |
+
font-size: 0.875rem;
|
63 |
+
border: none;
|
64 |
+
cursor: pointer;
|
65 |
+
transition: background-color 0.2s;
|
66 |
+
}
|
67 |
+
.update-btn:hover {
|
68 |
+
background-color: #4338ca;
|
69 |
+
}
|
70 |
+
.success-badge {
|
71 |
+
margin-top: 8px;
|
72 |
+
padding: 4px 8px;
|
73 |
+
border-radius: 9999px;
|
74 |
+
font-size: 0.75rem;
|
75 |
+
background-color: #dcfce7;
|
76 |
+
color: #16a34a;
|
77 |
+
}
|
78 |
+
.error-badge {
|
79 |
+
margin-top: 8px;
|
80 |
+
padding: 4px 8px;
|
81 |
+
border-radius: 9999px;
|
82 |
+
font-size: 0.75rem;
|
83 |
+
background-color: #fee2e2;
|
84 |
+
color: #dc2626;
|
85 |
+
}
|
86 |
+
.pending-badge {
|
87 |
+
margin-top: 8px;
|
88 |
+
padding: 4px 8px;
|
89 |
+
border-radius: 9999px;
|
90 |
+
font-size: 0.75rem;
|
91 |
+
background-color: #e0f2fe;
|
92 |
+
color: #0284c7;
|
93 |
+
}
|
94 |
+
#loading {
|
95 |
+
display: none;
|
96 |
+
margin: 20px auto;
|
97 |
+
text-align: center;
|
98 |
+
font-style: italic;
|
99 |
+
color: #6b7280;
|
100 |
+
}
|
101 |
+
.response-container {
|
102 |
+
margin-top: 15px;
|
103 |
+
background-color: #f9fafb;
|
104 |
+
border-radius: 6px;
|
105 |
+
padding: 10px;
|
106 |
+
font-family: monospace;
|
107 |
+
font-size: 0.875rem;
|
108 |
+
white-space: pre-wrap;
|
109 |
+
word-break: break-all;
|
110 |
+
display: none;
|
111 |
+
max-height: 200px;
|
112 |
+
overflow-y: auto;
|
113 |
+
}
|
114 |
+
</style>
|
115 |
+
</head>
|
116 |
+
<body>
|
117 |
+
<div class="container">
|
118 |
+
<h1>API密钥测试与更新</h1>
|
119 |
+
<p>在此页面可以测试update.py脚本的功能,验证API密钥并更新其状态。</p>
|
120 |
+
|
121 |
+
<div id="loading">正在加载API密钥列表...</div>
|
122 |
+
|
123 |
+
<div id="keyList" class="key-list"></div>
|
124 |
+
</div>
|
125 |
+
|
126 |
+
<script>
|
127 |
+
document.addEventListener('DOMContentLoaded', function() {
|
128 |
+
loadKeys();
|
129 |
+
|
130 |
+
// 添加回主页的按钮
|
131 |
+
const container = document.querySelector('.container');
|
132 |
+
const backButton = document.createElement('a');
|
133 |
+
backButton.href = '/';
|
134 |
+
backButton.textContent = '返回主页';
|
135 |
+
backButton.style.display = 'inline-block';
|
136 |
+
backButton.style.marginTop = '20px';
|
137 |
+
backButton.style.color = '#4f46e5';
|
138 |
+
backButton.style.textDecoration = 'none';
|
139 |
+
container.appendChild(backButton);
|
140 |
+
});
|
141 |
+
|
142 |
+
async function loadKeys() {
|
143 |
+
const loadingElement = document.getElementById('loading');
|
144 |
+
const keyListElement = document.getElementById('keyList');
|
145 |
+
|
146 |
+
loadingElement.style.display = 'block';
|
147 |
+
keyListElement.innerHTML = '';
|
148 |
+
|
149 |
+
try {
|
150 |
+
const response = await fetch('/api/keys');
|
151 |
+
const data = await response.json();
|
152 |
+
|
153 |
+
if (data && data.api_keys && data.api_keys.length > 0) {
|
154 |
+
data.api_keys.forEach(key => {
|
155 |
+
const keyElement = createKeyElement(key);
|
156 |
+
keyListElement.appendChild(keyElement);
|
157 |
+
});
|
158 |
+
} else {
|
159 |
+
keyListElement.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">没有找到API密钥</div>';
|
160 |
+
}
|
161 |
+
} catch (error) {
|
162 |
+
console.error('加载API密钥失败:', error);
|
163 |
+
keyListElement.innerHTML = `<div style="padding: 20px; text-align: center; color: #dc2626;">加载API密钥失败: ${error.message}</div>`;
|
164 |
+
} finally {
|
165 |
+
loadingElement.style.display = 'none';
|
166 |
+
}
|
167 |
+
}
|
168 |
+
|
169 |
+
function createKeyElement(key) {
|
170 |
+
const keyItem = document.createElement('div');
|
171 |
+
keyItem.className = 'key-item';
|
172 |
+
keyItem.id = `key-${key.id}`;
|
173 |
+
|
174 |
+
// 密钥信息区域
|
175 |
+
const keyInfo = document.createElement('div');
|
176 |
+
keyInfo.className = 'key-info';
|
177 |
+
|
178 |
+
// 平台信息
|
179 |
+
const platformElement = document.createElement('div');
|
180 |
+
platformElement.className = 'key-platform';
|
181 |
+
platformElement.textContent = getPlatformName(key.platform);
|
182 |
+
keyInfo.appendChild(platformElement);
|
183 |
+
|
184 |
+
// 密钥名称
|
185 |
+
const nameElement = document.createElement('div');
|
186 |
+
nameElement.className = 'key-name';
|
187 |
+
nameElement.textContent = key.name;
|
188 |
+
keyInfo.appendChild(nameElement);
|
189 |
+
|
190 |
+
// 密钥值
|
191 |
+
const valueElement = document.createElement('div');
|
192 |
+
valueElement.className = 'key-value';
|
193 |
+
valueElement.textContent = key.key;
|
194 |
+
keyInfo.appendChild(valueElement);
|
195 |
+
|
196 |
+
// 如果有更新时间,显示
|
197 |
+
if (key.updated_at) {
|
198 |
+
const updatedElement = document.createElement('div');
|
199 |
+
updatedElement.style.fontSize = '0.75rem';
|
200 |
+
updatedElement.style.color = '#6b7280';
|
201 |
+
updatedElement.style.marginTop = '5px';
|
202 |
+
|
203 |
+
const date = new Date(key.updated_at);
|
204 |
+
updatedElement.textContent = `上次更新: ${date.toLocaleString()}`;
|
205 |
+
keyInfo.appendChild(updatedElement);
|
206 |
+
}
|
207 |
+
|
208 |
+
// 响应容器
|
209 |
+
const responseContainer = document.createElement('div');
|
210 |
+
responseContainer.className = 'response-container';
|
211 |
+
responseContainer.id = `response-${key.id}`;
|
212 |
+
keyInfo.appendChild(responseContainer);
|
213 |
+
|
214 |
+
keyItem.appendChild(keyInfo);
|
215 |
+
|
216 |
+
// 状态和按钮区域
|
217 |
+
const keyStatus = document.createElement('div');
|
218 |
+
keyStatus.className = 'key-status';
|
219 |
+
|
220 |
+
// 更新按钮
|
221 |
+
const updateButton = document.createElement('button');
|
222 |
+
updateButton.className = 'update-btn';
|
223 |
+
updateButton.textContent = '更新状态';
|
224 |
+
updateButton.onclick = function() {
|
225 |
+
updateKeyStatus(key.id);
|
226 |
+
};
|
227 |
+
keyStatus.appendChild(updateButton);
|
228 |
+
|
229 |
+
// 状态标签
|
230 |
+
const statusBadge = document.createElement('div');
|
231 |
+
if (key.success === true) {
|
232 |
+
statusBadge.className = 'success-badge';
|
233 |
+
statusBadge.textContent = '验证成功';
|
234 |
+
} else if (key.success === false) {
|
235 |
+
statusBadge.className = 'error-badge';
|
236 |
+
statusBadge.textContent = key.return_message || '验证失败';
|
237 |
+
} else {
|
238 |
+
statusBadge.className = 'pending-badge';
|
239 |
+
statusBadge.textContent = '等待验证';
|
240 |
+
}
|
241 |
+
keyStatus.appendChild(statusBadge);
|
242 |
+
|
243 |
+
keyItem.appendChild(keyStatus);
|
244 |
+
|
245 |
+
return keyItem;
|
246 |
+
}
|
247 |
+
|
248 |
+
async function updateKeyStatus(keyId) {
|
249 |
+
const keyElement = document.getElementById(`key-${keyId}`);
|
250 |
+
const updateButton = keyElement.querySelector('.update-btn');
|
251 |
+
const responseContainer = document.getElementById(`response-${keyId}`);
|
252 |
+
|
253 |
+
// 禁用按钮并显示加载状态
|
254 |
+
updateButton.disabled = true;
|
255 |
+
updateButton.textContent = '更新中...';
|
256 |
+
|
257 |
+
try {
|
258 |
+
const response = await fetch(`/api/keys/update/${keyId}`, {
|
259 |
+
method: 'POST'
|
260 |
+
});
|
261 |
+
|
262 |
+
const result = await response.json();
|
263 |
+
|
264 |
+
// 显示响应数据
|
265 |
+
responseContainer.textContent = JSON.stringify(result, null, 2);
|
266 |
+
responseContainer.style.display = 'block';
|
267 |
+
|
268 |
+
// 刷新密钥列表
|
269 |
+
loadKeys();
|
270 |
+
|
271 |
+
} catch (error) {
|
272 |
+
console.error(`更新密钥 ${keyId} 失败:`, error);
|
273 |
+
responseContainer.textContent = `更新失败: ${error.message}`;
|
274 |
+
responseContainer.style.display = 'block';
|
275 |
+
} finally {
|
276 |
+
// 恢复按钮状态
|
277 |
+
updateButton.disabled = false;
|
278 |
+
updateButton.textContent = '更新状态';
|
279 |
+
}
|
280 |
+
}
|
281 |
+
|
282 |
+
function getPlatformName(platformId) {
|
283 |
+
const platforms = {
|
284 |
+
'openai': 'OpenAI',
|
285 |
+
'anthropic': 'Anthropic',
|
286 |
+
'google': 'Google',
|
287 |
+
'deepseek': 'DeepSeek'
|
288 |
+
};
|
289 |
+
|
290 |
+
return platforms[platformId] || platformId;
|
291 |
+
}
|
292 |
+
</script>
|
293 |
+
</body>
|
294 |
+
</html>
|
update.py
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
API密钥更新模块 - 提供API密钥的验证和更新功能
|
3 |
+
"""
|
4 |
+
import json
|
5 |
+
import os
|
6 |
+
import sqlite3
|
7 |
+
from datetime import datetime
|
8 |
+
from core.api_manager import get_api_manager
|
9 |
+
from utils.db import get_db_connection
|
10 |
+
from config import API_KEYS_FILE
|
11 |
+
|
12 |
+
def update(key_id):
|
13 |
+
"""
|
14 |
+
更新指定ID的API密钥
|
15 |
+
|
16 |
+
Args:
|
17 |
+
key_id (str): 要更新的API密钥ID
|
18 |
+
|
19 |
+
Returns:
|
20 |
+
dict: 包含更新结果的字典,成功时返回更新后的密钥信息,失败时返回错误信息
|
21 |
+
"""
|
22 |
+
# 从SQLite数据库中获取密钥信息
|
23 |
+
conn = get_db_connection()
|
24 |
+
try:
|
25 |
+
cursor = conn.cursor()
|
26 |
+
cursor.execute('SELECT * FROM api_keys WHERE id = ?', (key_id,))
|
27 |
+
row = cursor.fetchone()
|
28 |
+
|
29 |
+
if row is None:
|
30 |
+
# 数据库中找不到,尝试从JSON文件加载
|
31 |
+
# 这是为了支持旧版本的兼容
|
32 |
+
if os.path.exists(API_KEYS_FILE):
|
33 |
+
try:
|
34 |
+
with open(API_KEYS_FILE, "r", encoding="utf-8") as f:
|
35 |
+
data = json.load(f)
|
36 |
+
|
37 |
+
for key in data.get("api_keys", []):
|
38 |
+
if key.get("id") == key_id:
|
39 |
+
key_data = key
|
40 |
+
break
|
41 |
+
else:
|
42 |
+
return {"success": False, "message": f"未找到ID为 {key_id} 的API密钥"}
|
43 |
+
except Exception as e:
|
44 |
+
return {"success": False, "message": f"读取API密钥文件失败: {str(e)}"}
|
45 |
+
else:
|
46 |
+
return {"success": False, "message": f"未找到ID为 {key_id} 的API密钥"}
|
47 |
+
else:
|
48 |
+
key_data = dict(row)
|
49 |
+
|
50 |
+
# 获取平台和密钥
|
51 |
+
platform = key_data.get("platform")
|
52 |
+
api_key = key_data.get("key")
|
53 |
+
|
54 |
+
# 获取API管理器
|
55 |
+
api_manager = get_api_manager()
|
56 |
+
|
57 |
+
# 调用API管理器验证密钥
|
58 |
+
try:
|
59 |
+
result = api_manager.execute(platform, "validate_api_key", api_key)
|
60 |
+
except Exception as e:
|
61 |
+
return {"success": False, "message": f"验证API密钥时出错: {str(e)}"}
|
62 |
+
|
63 |
+
# 当前时间
|
64 |
+
current_time = datetime.now().isoformat()
|
65 |
+
|
66 |
+
# 将布尔值转换为整数
|
67 |
+
success_int = 1 if result.get("success", False) else 0
|
68 |
+
|
69 |
+
# 更新密钥信息到SQLite数据库
|
70 |
+
try:
|
71 |
+
cursor.execute('''
|
72 |
+
UPDATE api_keys
|
73 |
+
SET states = ?, balance = ?, success = ?, return_message = ?, updated_at = ?
|
74 |
+
WHERE id = ?
|
75 |
+
''', (
|
76 |
+
result.get("states", ""),
|
77 |
+
result.get("balance", 0),
|
78 |
+
success_int,
|
79 |
+
result.get("return_message", ""),
|
80 |
+
current_time,
|
81 |
+
key_id
|
82 |
+
))
|
83 |
+
|
84 |
+
# 如果数据库中没有此记录(可能是旧的JSON格式数据),则插入新记录
|
85 |
+
if cursor.rowcount == 0 and 'id' in key_data:
|
86 |
+
cursor.execute('''
|
87 |
+
INSERT OR REPLACE INTO api_keys
|
88 |
+
(id, platform, name, key, created_at, updated_at, success, return_message, states, balance)
|
89 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
90 |
+
''', (
|
91 |
+
key_data.get("id"),
|
92 |
+
key_data.get("platform"),
|
93 |
+
key_data.get("name"),
|
94 |
+
key_data.get("key"),
|
95 |
+
key_data.get("created_at", current_time),
|
96 |
+
current_time,
|
97 |
+
success_int,
|
98 |
+
result.get("return_message", ""),
|
99 |
+
result.get("states", ""),
|
100 |
+
result.get("balance", 0)
|
101 |
+
))
|
102 |
+
|
103 |
+
conn.commit()
|
104 |
+
|
105 |
+
# 获取更新后的完整记录
|
106 |
+
cursor.execute('SELECT * FROM api_keys WHERE id = ?', (key_id,))
|
107 |
+
updated_row = cursor.fetchone()
|
108 |
+
|
109 |
+
if updated_row:
|
110 |
+
updated_data = dict(updated_row)
|
111 |
+
# 将布尔值转换为布尔类型
|
112 |
+
updated_data['success'] = bool(updated_data['success'])
|
113 |
+
|
114 |
+
return {
|
115 |
+
"success": True,
|
116 |
+
"message": "API密钥更新成功",
|
117 |
+
"data": updated_data
|
118 |
+
}
|
119 |
+
else:
|
120 |
+
# 如果以某种方式在更新过程中密钥被删除
|
121 |
+
return {"success": False, "message": f"更新期间ID为 {key_id} 的API密钥已被删除"}
|
122 |
+
|
123 |
+
except sqlite3.Error as e:
|
124 |
+
conn.rollback()
|
125 |
+
return {"success": False, "message": f"更新数据库中的API密钥失败: {str(e)}"}
|
126 |
+
|
127 |
+
except sqlite3.Error as e:
|
128 |
+
return {"success": False, "message": f"从数据库获取API密钥时出错: {str(e)}"}
|
129 |
+
finally:
|
130 |
+
if conn:
|
131 |
+
conn.close()
|
132 |
+
|
133 |
+
if __name__ == "__main__":
|
134 |
+
# 可以在这里添加命令行参数解析的代码,用于直接从命令行调用
|
135 |
+
import sys
|
136 |
+
if len(sys.argv) > 1:
|
137 |
+
key_id = sys.argv[1]
|
138 |
+
result = update(key_id)
|
139 |
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
utils/__pycache__/db.cpython-313.pyc
ADDED
Binary file (2 kB). View file
|
|
utils/__pycache__/migrate.cpython-313.pyc
ADDED
Binary file (4.07 kB). View file
|
|
utils/db.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
数据库工具模块 - 处理SQLite数据库连接和初始化
|
3 |
+
"""
|
4 |
+
import sqlite3
|
5 |
+
import os
|
6 |
+
from config import DATABASE_PATH
|
7 |
+
|
8 |
+
def get_db_connection():
|
9 |
+
"""
|
10 |
+
获取数据库连接
|
11 |
+
|
12 |
+
Returns:
|
13 |
+
sqlite3.Connection: 数据库连接对象
|
14 |
+
"""
|
15 |
+
conn = sqlite3.connect(DATABASE_PATH)
|
16 |
+
conn.row_factory = sqlite3.Row # 使查询结果可以通过列名访问
|
17 |
+
return conn
|
18 |
+
|
19 |
+
def init_db():
|
20 |
+
"""
|
21 |
+
初始化数据库,创建必要的表
|
22 |
+
"""
|
23 |
+
# 确保数据目录存在
|
24 |
+
os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True)
|
25 |
+
|
26 |
+
conn = get_db_connection()
|
27 |
+
try:
|
28 |
+
cursor = conn.cursor()
|
29 |
+
|
30 |
+
# 创建API密钥表
|
31 |
+
cursor.execute('''
|
32 |
+
CREATE TABLE IF NOT EXISTS api_keys (
|
33 |
+
id TEXT PRIMARY KEY,
|
34 |
+
platform TEXT NOT NULL,
|
35 |
+
name TEXT NOT NULL,
|
36 |
+
key TEXT NOT NULL,
|
37 |
+
created_at TEXT NOT NULL,
|
38 |
+
updated_at TEXT NOT NULL,
|
39 |
+
success INTEGER DEFAULT 0,
|
40 |
+
return_message TEXT DEFAULT '等待测试',
|
41 |
+
states TEXT DEFAULT '',
|
42 |
+
balance REAL DEFAULT 0
|
43 |
+
)
|
44 |
+
''')
|
45 |
+
|
46 |
+
conn.commit()
|
47 |
+
except Exception as e:
|
48 |
+
print(f"初始化数据库时出错: {str(e)}")
|
49 |
+
finally:
|
50 |
+
conn.close()
|
51 |
+
|
52 |
+
# 确保模块被导入时初始化数据库
|
53 |
+
init_db()
|