yangtb24 commited on
Commit
99fc92f
·
verified ·
1 Parent(s): 5ad3e05

Upload 61 files

Browse files
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 = 1
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
- from config import API_KEYS_FILE
 
 
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
- return ApiKeyManager.load_keys()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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": str(uuid.uuid4()),
50
  "platform": platform,
51
  "name": name,
52
  "key": key,
53
- "created_at": datetime.now(pytz.timezone('Asia/Shanghai')).isoformat()
 
 
 
 
 
54
  }
55
 
56
- api_keys_data["api_keys"].append(new_key)
57
- ApiKeyManager.save_keys(api_keys_data)
58
-
59
- return new_key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  @staticmethod
62
  def delete_key(key_id):
63
  """删除指定的API密钥"""
64
- api_keys_data = ApiKeyManager.load_keys()
65
-
66
- original_count = len(api_keys_data["api_keys"])
67
- api_keys_data["api_keys"] = [k for k in api_keys_data["api_keys"] if k.get("id") != key_id]
68
-
69
- if len(api_keys_data["api_keys"]) < original_count:
70
- ApiKeyManager.save_keys(api_keys_data)
71
- return True
72
- return False
 
 
 
 
 
 
 
 
 
 
 
 
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
- api_keys_data = ApiKeyManager.load_keys()
81
-
82
- original_count = len(api_keys_data["api_keys"])
83
- api_keys_data["api_keys"] = [k for k in api_keys_data["api_keys"] if k.get("id") not in key_ids]
84
-
85
- deleted_count = original_count - len(api_keys_data["api_keys"])
86
- if deleted_count > 0:
87
- ApiKeyManager.save_keys(api_keys_data)
88
-
89
- return deleted_count
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- for key_info in keys_data:
110
- platform = key_info.get("platform")
111
- name = key_info.get("name")
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
- api_keys_data["api_keys"].append(new_key)
127
- added_keys.append(new_key)
128
-
129
- # 一次性保存所有添加的密钥
130
- ApiKeyManager.save_keys(api_keys_data)
131
-
132
- return added_keys
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- if updated_key:
152
- ApiKeyManager.save_keys(api_keys_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- return updated_key
 
 
 
 
 
 
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">{{ key.created_at.split('T')[1].split('.')[0] }}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()