Spaces:
BG5
/
Running

BG5 commited on
Commit
afac4df
·
verified ·
1 Parent(s): 4783b70

Delete api_server.py

Browse files
Files changed (1) hide show
  1. api_server.py +0 -915
api_server.py DELETED
@@ -1,915 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
- import os
5
- import json
6
- import asyncio
7
- from typing import Dict, List, Optional, Any
8
- from datetime import datetime
9
-
10
- from fastapi import FastAPI, HTTPException, BackgroundTasks, Query, Depends, UploadFile, File, Body, Header
11
- from fastapi.middleware.cors import CORSMiddleware
12
- from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
13
- from pydantic import BaseModel, Field
14
- import uvicorn
15
-
16
- from unibo_jetbrains_activation import (
17
- DatabaseManager, JetbrainsSubmitter, LinkExtractor, ProcessController,
18
- STATUS_PENDING, STATUS_SUBMITTED, STATUS_FAILED, STATUS_LINK_EXTRACTED
19
- )
20
- from loguru import logger
21
-
22
- # 配置日志
23
- logger.remove()
24
- logger.add("api_server.log", rotation="1 MB", level="INFO")
25
- logger.add(lambda msg: print(msg, end=""), level="INFO")
26
-
27
- # 创建FastAPI应用
28
- app = FastAPI(
29
- title="JetBrains激活链接管理系统",
30
- description="管理JetBrains激活链接的获取流程,包括提交邮箱和提取链接",
31
- version="1.0.0"
32
- )
33
-
34
- # 允许跨域请求
35
- app.add_middleware(
36
- CORSMiddleware,
37
- allow_origins=["*"], # 在生产环境中应该替换为实际的前端域名
38
- allow_credentials=True,
39
- allow_methods=["*"],
40
- allow_headers=["*"],
41
- )
42
-
43
- # 创建全局的ProcessController实例
44
- controller = ProcessController()
45
-
46
- # 定义环境变量
47
- ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "admin123")
48
- # API_TOKEN = os.environ.get("API_TOKEN", "unibo_token")
49
-
50
- def verify_token(token: str = Header(None)):
51
- if token != ADMIN_PASSWORD:
52
- raise HTTPException(status_code=401, detail="无效的token")
53
-
54
- # 定义请求和响应模型
55
- class AccountItem(BaseModel):
56
- id: Optional[int] = None
57
- register_time: str
58
- username: str
59
- password: str
60
- security_email: Optional[str] = None
61
- status: int = 0
62
- activation_link: Optional[str] = None
63
- notes: Optional[str] = None
64
- updated_at: Optional[str] = None
65
- used: Optional[int] = 0
66
-
67
- class AccountsResponse(BaseModel):
68
- accounts: List[AccountItem]
69
- total: int
70
-
71
- class SubmitRequest(BaseModel):
72
- account_ids: List[int] = []
73
- max_workers: int = 3
74
- proxy: Optional[str] = None
75
-
76
- class ExtractRequest(BaseModel):
77
- account_ids: List[int] = []
78
- max_workers: int = 1
79
-
80
- class ProcessResponse(BaseModel):
81
- success_count: int
82
- error_count: int
83
- details: Optional[Dict[str, Any]] = None
84
-
85
- class ImportRequest(BaseModel):
86
- filepath: str
87
-
88
- class ImportResponse(BaseModel):
89
- imported_count: int
90
- total_accounts: int
91
-
92
- class ExportRequest(BaseModel):
93
- status: Optional[int] = None
94
- filename: Optional[str] = "export_accounts.txt"
95
-
96
- class ExportResponse(BaseModel):
97
- exported_count: int
98
- filepath: str
99
-
100
- class StatusCounts(BaseModel):
101
- total: int = 0
102
- pending: int = 0
103
- submitted: int = 0
104
- failed: int = 0
105
- link_extracted: int = 0
106
- unused: int = 0 # 新增
107
-
108
- class StatusUpdateRequest(BaseModel):
109
- status: int
110
- notes: Optional[str] = None
111
- activation_link: Optional[str] = None
112
-
113
- class BatchToggleUsedRequest(BaseModel):
114
- ids: list[int]
115
- used: int
116
-
117
- # 后台任务
118
- running_tasks = {}
119
-
120
- async def process_submit_task(task_id: str, account_ids: List[int], max_workers: int, proxy: Optional[str] = None):
121
- """后台提交邮箱任务(并发版)"""
122
- try:
123
- # 获取要处理的账号
124
- accounts = []
125
- with controller.db_manager.lock:
126
- cursor = controller.db_manager.conn.cursor()
127
- if account_ids:
128
- # 处理指定的账号
129
- for account_id in account_ids:
130
- cursor.execute(
131
- "SELECT id, register_time, username, password, security_email, status, activation_link FROM accounts WHERE id = ?",
132
- (account_id,)
133
- )
134
- row = cursor.fetchone()
135
- if row:
136
- accounts.append({
137
- 'id': row[0],
138
- 'register_time': row[1],
139
- 'username': row[2],
140
- 'password': row[3],
141
- 'security_email': row[4],
142
- 'status': row[5],
143
- 'activation_link': row[6]
144
- })
145
- else:
146
- # 处理所有未提交的账号
147
- cursor.execute(
148
- "SELECT id, register_time, username, password, security_email, status, activation_link FROM accounts WHERE status = ?",
149
- (STATUS_PENDING,)
150
- )
151
- for row in cursor.fetchall():
152
- accounts.append({
153
- 'id': row[0],
154
- 'register_time': row[1],
155
- 'username': row[2],
156
- 'password': row[3],
157
- 'security_email': row[4],
158
- 'status': row[5],
159
- 'activation_link': row[6]
160
- })
161
-
162
- # 设置代理(如果提供)
163
- if proxy:
164
- submitter = JetbrainsSubmitter(controller.db_manager, proxy)
165
- else:
166
- submitter = controller.submitter
167
-
168
- success_count = 0
169
- error_count = 0
170
- results = []
171
-
172
- sem = asyncio.Semaphore(max_workers)
173
-
174
- async def handle_account(account):
175
- nonlocal success_count, error_count
176
- try:
177
- async with sem:
178
- controller.db_manager.log_operation(
179
- account['username'],
180
- "submit_email_background",
181
- "processing",
182
- f"任务ID: {task_id}"
183
- )
184
- # 提交邮箱(同步方法用run_in_executor)
185
- loop = asyncio.get_running_loop()
186
- result = await loop.run_in_executor(None, submitter.submit_email, account)
187
- if result:
188
- success_count += 1
189
- else:
190
- error_count += 1
191
- await asyncio.sleep(1)
192
- except Exception as e:
193
- error_count += 1
194
- error_msg = str(e)
195
- controller.db_manager.update_account_status(
196
- account['id'],
197
- STATUS_FAILED,
198
- notes=f"后台任务异常: {error_msg[:100]}"
199
- )
200
- controller.db_manager.log_operation(
201
- account['username'],
202
- "submit_email_background",
203
- "error",
204
- error_msg[:200]
205
- )
206
-
207
- tasks_list = [handle_account(account) for account in accounts]
208
- await asyncio.gather(*tasks_list)
209
-
210
- running_tasks[task_id] = {
211
- "status": "completed",
212
- "success_count": success_count,
213
- "error_count": error_count,
214
- "total": len(accounts)
215
- }
216
- except Exception as e:
217
- logger.error(f"后台提交任务 {task_id} 执行失败: {str(e)}")
218
- running_tasks[task_id] = {
219
- "status": "failed",
220
- "error": str(e)
221
- }
222
-
223
- async def process_extract_task(task_id: str, account_ids: List[int], max_workers: int):
224
- """后台提取链接任务(并发版)"""
225
- try:
226
- # 获取要处理的账号
227
- accounts = []
228
- with controller.db_manager.lock:
229
- cursor = controller.db_manager.conn.cursor()
230
- if account_ids:
231
- # 处理指定的账号
232
- for account_id in account_ids:
233
- cursor.execute(
234
- "SELECT id, register_time, username, password, security_email, status, activation_link FROM accounts WHERE id = ?",
235
- (account_id,)
236
- )
237
- row = cursor.fetchone()
238
- if row and row[5] == STATUS_SUBMITTED: # 仅处理已提交的账号
239
- accounts.append({
240
- 'id': row[0],
241
- 'register_time': row[1],
242
- 'username': row[2],
243
- 'password': row[3],
244
- 'security_email': row[4],
245
- 'status': row[5],
246
- 'activation_link': row[6]
247
- })
248
- else:
249
- # 处理所有已提交但未提取链接的账号
250
- cursor.execute(
251
- "SELECT id, register_time, username, password, security_email, status, activation_link FROM accounts WHERE status = ? AND (activation_link IS NULL OR activation_link = '')",
252
- (STATUS_SUBMITTED,)
253
- )
254
- for row in cursor.fetchall():
255
- accounts.append({
256
- 'id': row[0],
257
- 'register_time': row[1],
258
- 'username': row[2],
259
- 'password': row[3],
260
- 'security_email': row[4],
261
- 'status': row[5],
262
- 'activation_link': row[6]
263
- })
264
-
265
- # 使用LinkExtractor提取链接
266
- extractor = controller.extractor
267
- success_count = 0
268
- error_count = 0
269
- sem = asyncio.Semaphore(max_workers)
270
-
271
- async def handle_account(account):
272
- nonlocal success_count, error_count
273
- try:
274
- async with sem:
275
- controller.db_manager.log_operation(
276
- account['username'],
277
- "extract_link_background",
278
- "processing",
279
- f"任务ID: {task_id}"
280
- )
281
- loop = asyncio.get_running_loop()
282
- link = await loop.run_in_executor(None, extractor.extract_link, account)
283
- if link:
284
- success_count += 1
285
- else:
286
- error_count += 1
287
- await asyncio.sleep(1)
288
- except Exception as e:
289
- error_count += 1
290
- error_msg = str(e)
291
- controller.db_manager.update_account_status(
292
- account['id'],
293
- STATUS_SUBMITTED,
294
- notes=f"后台任务异常: {error_msg[:100]}"
295
- )
296
- controller.db_manager.log_operation(
297
- account['username'],
298
- "extract_link_background",
299
- "error",
300
- error_msg[:200]
301
- )
302
-
303
- tasks_list = [handle_account(account) for account in accounts]
304
- await asyncio.gather(*tasks_list)
305
-
306
- running_tasks[task_id] = {
307
- "status": "completed",
308
- "success_count": success_count,
309
- "error_count": error_count,
310
- "total": len(accounts)
311
- }
312
- except Exception as e:
313
- logger.error(f"后台提取任务 {task_id} 执行失败: {str(e)}")
314
- running_tasks[task_id] = {
315
- "status": "failed",
316
- "error": str(e)
317
- }
318
-
319
- # API路由
320
- @app.get("/")
321
- async def root():
322
- current_dir = os.path.dirname(os.path.abspath(__file__))
323
- index_path = os.path.join(current_dir, "index.html")
324
- if os.path.exists(index_path):
325
- return FileResponse(index_path)
326
- return {"message": "index.html not found in the current directory"}
327
-
328
- @app.get("/accounts", response_model=AccountsResponse, dependencies=[Depends(verify_token)])
329
- async def get_accounts(
330
- status: Optional[int] = None,
331
- page: int = Query(1, ge=1),
332
- per_page: int = Query(10, ge=1, le=100),
333
- search: Optional[str] = None,
334
- register_time_start: Optional[str] = None,
335
- register_time_end: Optional[str] = None,
336
- id_min: Optional[int] = None,
337
- id_max: Optional[int] = None,
338
- has_activation_link: Optional[str] = None,
339
- used: Optional[int] = Query(None)
340
- ):
341
- """获取账号列表,支持多条件筛选"""
342
- offset = (page - 1) * per_page
343
- try:
344
- with controller.db_manager.lock:
345
- cursor = controller.db_manager.conn.cursor()
346
- query_conditions = []
347
- query_params = []
348
- if status is not None:
349
- query_conditions.append("status = ?")
350
- query_params.append(status)
351
- if search:
352
- query_conditions.append("(username LIKE ? OR security_email LIKE ? OR notes LIKE ? OR activation_link LIKE ?)")
353
- search_param = f"%{search}%"
354
- query_params.extend([search_param, search_param, search_param, search_param])
355
- if register_time_start:
356
- query_conditions.append("register_time >= ?")
357
- query_params.append(register_time_start)
358
- if register_time_end:
359
- query_conditions.append("register_time <= ?")
360
- query_params.append(register_time_end)
361
- if id_min:
362
- query_conditions.append("id >= ?")
363
- query_params.append(id_min)
364
- if id_max:
365
- query_conditions.append("id <= ?")
366
- query_params.append(id_max)
367
- if has_activation_link == 'yes':
368
- query_conditions.append("activation_link IS NOT NULL AND activation_link != ''")
369
- elif has_activation_link == 'no':
370
- query_conditions.append("(activation_link IS NULL OR activation_link = '')")
371
- if used is not None and used != '':
372
- query_conditions.append("used = ?")
373
- query_params.append(int(used))
374
- if query_conditions:
375
- where_clause = "WHERE " + " AND ".join(query_conditions)
376
- else:
377
- where_clause = ""
378
- count_query = f"SELECT COUNT(*) FROM accounts {where_clause}"
379
- cursor.execute(count_query, query_params)
380
- total = cursor.fetchone()[0]
381
- data_query = f"""
382
- SELECT id, register_time, username, password, security_email, status, activation_link, updated_at, notes, used
383
- FROM accounts
384
- {where_clause}
385
- ORDER BY id DESC
386
- LIMIT ? OFFSET ?
387
- """
388
- query_params_data = query_params + [per_page, offset]
389
- cursor.execute(data_query, query_params_data)
390
- accounts = []
391
- for row in cursor.fetchall():
392
- accounts.append(AccountItem(
393
- id=row[0],
394
- register_time=row[1],
395
- username=row[2],
396
- password=row[3],
397
- security_email=row[4],
398
- status=row[5],
399
- activation_link=row[6],
400
- updated_at=row[7],
401
- notes=row[8],
402
- used=row[9] if len(row) > 9 else 0
403
- ))
404
- return AccountsResponse(accounts=accounts, total=total)
405
- except Exception as e:
406
- logger.error(f"获取账号列表出错: {str(e)}")
407
- raise HTTPException(status_code=500, detail=f"获取账号列表出错: {str(e)}")
408
-
409
- @app.get("/accounts/{account_id}", response_model=AccountItem, dependencies=[Depends(verify_token)])
410
- async def get_account(account_id: int):
411
- """获取单个账号详情"""
412
- try:
413
- with controller.db_manager.lock:
414
- cursor = controller.db_manager.conn.cursor()
415
- cursor.execute(
416
- "SELECT id, register_time, username, password, security_email, status, activation_link, updated_at, notes FROM accounts WHERE id = ?",
417
- (account_id,)
418
- )
419
- row = cursor.fetchone()
420
-
421
- if not row:
422
- raise HTTPException(status_code=404, detail=f"未找到ID为{account_id}的账号")
423
-
424
- return AccountItem(
425
- id=row[0],
426
- register_time=row[1],
427
- username=row[2],
428
- password=row[3],
429
- security_email=row[4],
430
- status=row[5],
431
- activation_link=row[6],
432
- updated_at=row[7],
433
- notes=row[8]
434
- )
435
-
436
- except HTTPException:
437
- raise
438
- except Exception as e:
439
- logger.error(f"获取账号详情出错: {str(e)}")
440
- raise HTTPException(status_code=500, detail=f"获取账号详情出错: {str(e)}")
441
-
442
- @app.post("/accounts/{account_id}/status", response_model=AccountItem, dependencies=[Depends(verify_token)])
443
- async def update_account_status(account_id: int, status_update: StatusUpdateRequest):
444
- """更新账号状态"""
445
- try:
446
- username = None
447
- updated_account = None
448
-
449
- # 使用锁进行数据库操作
450
- with controller.db_manager.lock:
451
- cursor = controller.db_manager.conn.cursor()
452
-
453
- # 先检查账号是否存在
454
- cursor.execute("SELECT username FROM accounts WHERE id = ?", (account_id,))
455
- result = cursor.fetchone()
456
- if not result:
457
- raise HTTPException(status_code=404, detail=f"未找到ID为{account_id}的账号")
458
-
459
- username = result[0]
460
-
461
- # 直接在锁内执行更新操作,避免嵌套锁
462
- if status_update.activation_link and status_update.notes:
463
- cursor.execute('''
464
- UPDATE accounts
465
- SET status = ?, activation_link = ?, notes = ?, updated_at = datetime('now', 'localtime')
466
- WHERE id = ?
467
- ''', (status_update.status, status_update.activation_link, status_update.notes, account_id))
468
- elif status_update.activation_link:
469
- cursor.execute('''
470
- UPDATE accounts
471
- SET status = ?, activation_link = ?, updated_at = datetime('now', 'localtime')
472
- WHERE id = ?
473
- ''', (status_update.status, status_update.activation_link, account_id))
474
- elif status_update.notes:
475
- cursor.execute('''
476
- UPDATE accounts
477
- SET status = ?, notes = ?, updated_at = datetime('now', 'localtime')
478
- WHERE id = ?
479
- ''', (status_update.status, status_update.notes, account_id))
480
- else:
481
- cursor.execute('''
482
- UPDATE accounts
483
- SET status = ?, updated_at = datetime('now', 'localtime')
484
- WHERE id = ?
485
- ''', (status_update.status, account_id))
486
-
487
- # 提交事务
488
- controller.db_manager.conn.commit()
489
-
490
- # 返回更新后的账号
491
- cursor.execute(
492
- "SELECT id, register_time, username, password, security_email, status, activation_link, updated_at, notes FROM accounts WHERE id = ?",
493
- (account_id,)
494
- )
495
- row = cursor.fetchone()
496
-
497
- updated_account = AccountItem(
498
- id=row[0],
499
- register_time=row[1],
500
- username=row[2],
501
- password=row[3],
502
- security_email=row[4],
503
- status=row[5],
504
- activation_link=row[6],
505
- updated_at=row[7],
506
- notes=row[8]
507
- )
508
-
509
- # 锁外记录操作日志,避免死锁
510
- if username:
511
- controller.db_manager.log_operation(
512
- username,
513
- "update_status",
514
- "success",
515
- f"手动更新状态为: {status_update.status}"
516
- )
517
-
518
- return updated_account
519
-
520
- except HTTPException:
521
- raise
522
- except Exception as e:
523
- logger.error(f"更新账号状态出错: {str(e)}")
524
- raise HTTPException(status_code=500, detail=f"更新账号状态出错: {str(e)}")
525
-
526
- @app.post("/accounts/{account_id}/toggle-used", dependencies=[Depends(verify_token)])
527
- async def toggle_account_used(account_id: int):
528
- """切换账号的used状态"""
529
- try:
530
- with controller.db_manager.lock:
531
- cursor = controller.db_manager.conn.cursor()
532
- cursor.execute("SELECT used FROM accounts WHERE id = ?", (account_id,))
533
- row = cursor.fetchone()
534
- if not row:
535
- raise HTTPException(status_code=404, detail=f"未找到ID为{account_id}的账号")
536
- current = row[0] or 0
537
- new_value = 0 if current else 1
538
- cursor.execute("UPDATE accounts SET used = ?, updated_at = datetime('now', 'localtime') WHERE id = ?", (new_value, account_id))
539
- controller.db_manager.conn.commit()
540
- return {"id": account_id, "used": new_value}
541
- except Exception as e:
542
- logger.error(f"切换账号used状态出错: {str(e)}")
543
- raise HTTPException(status_code=500, detail=f"切换账号used状态出错: {str(e)}")
544
-
545
- @app.post("/accounts/batch-toggle-used", dependencies=[Depends(verify_token)])
546
- async def batch_toggle_used(req: BatchToggleUsedRequest = Body(...)):
547
- """批量修改账号的used状态"""
548
- try:
549
- with controller.db_manager.lock:
550
- cursor = controller.db_manager.conn.cursor()
551
- if not req.ids:
552
- raise HTTPException(status_code=400, detail="未指定账号ID")
553
- cursor.execute(f"UPDATE accounts SET used = ?, updated_at = datetime('now', 'localtime') WHERE id IN ({','.join(['?']*len(req.ids))})", [req.used, *req.ids])
554
- controller.db_manager.conn.commit()
555
- return {"updated": cursor.rowcount}
556
- except Exception as e:
557
- logger.error(f"批量切换账号used状态出错: {str(e)}")
558
- raise HTTPException(status_code=500, detail=f"批量切换账号used状态出错: {str(e)}")
559
-
560
- @app.delete("/accounts/{account_id}", dependencies=[Depends(verify_token)])
561
- async def delete_account(account_id: int):
562
- logger.info(f"收到删除账号请求: {account_id}")
563
- try:
564
- with controller.db_manager.lock:
565
- logger.info(f"已获取数据库锁,准备查询账号: {account_id}")
566
- cursor = controller.db_manager.conn.cursor()
567
- cursor.execute("SELECT username FROM accounts WHERE id = ?", (account_id,))
568
- row = cursor.fetchone()
569
- if not row:
570
- logger.warning(f"未找到账号: {account_id}")
571
- raise HTTPException(status_code=404, detail=f"未找到ID为{account_id}的账号")
572
- username = row[0]
573
- logger.info(f"准备删除账号: {account_id}, 用户名: {username}")
574
- cursor.execute("DELETE FROM accounts WHERE id = ?", (account_id,))
575
- controller.db_manager.conn.commit()
576
- logger.info(f"账号已删除: {account_id}")
577
- # 注意:操作日志写入要放到锁外,避免死锁
578
- controller.db_manager.log_operation(username, "delete_account", "success", f"删除账号ID: {account_id}")
579
- logger.info(f"删除账号流程结束: {account_id}")
580
- return {"message": f"账号 {account_id} 已删除"}
581
- except HTTPException:
582
- raise
583
- except Exception as e:
584
- logger.error(f"删除账号出错: {str(e)}")
585
- raise HTTPException(status_code=500, detail=f"删除账号出错: {str(e)}")
586
-
587
- @app.post("/submit", response_model=Dict, dependencies=[Depends(verify_token)])
588
- async def submit_emails(request: SubmitRequest, background_tasks: BackgroundTasks):
589
- """提交邮箱到JetBrains(异步任务)"""
590
- try:
591
- task_id = f"submit_{datetime.now().strftime('%Y%m%d%H%M%S')}_{len(running_tasks) + 1}"
592
-
593
- # 设置初始任务状态
594
- running_tasks[task_id] = {
595
- "status": "running",
596
- "type": "submit",
597
- "start_time": datetime.now().isoformat(),
598
- "account_ids": request.account_ids,
599
- "max_workers": request.max_workers
600
- }
601
-
602
- # 启动后台任务
603
- background_tasks.add_task(
604
- process_submit_task,
605
- task_id=task_id,
606
- account_ids=request.account_ids,
607
- max_workers=request.max_workers,
608
- proxy=request.proxy
609
- )
610
-
611
- return {
612
- "task_id": task_id,
613
- "message": "邮箱提交任务已启动",
614
- "status": "running"
615
- }
616
-
617
- except Exception as e:
618
- logger.error(f"启动提交任务出错: {str(e)}")
619
- raise HTTPException(status_code=500, detail=f"启动提交任务出错: {str(e)}")
620
-
621
- @app.post("/extract", response_model=Dict, dependencies=[Depends(verify_token)])
622
- async def extract_links(request: ExtractRequest, background_tasks: BackgroundTasks):
623
- """从邮箱提取激活链接(异步任务)"""
624
- try:
625
- task_id = f"extract_{datetime.now().strftime('%Y%m%d%H%M%S')}_{len(running_tasks) + 1}"
626
-
627
- # 设置初始任务状态
628
- running_tasks[task_id] = {
629
- "status": "running",
630
- "type": "extract",
631
- "start_time": datetime.now().isoformat(),
632
- "account_ids": request.account_ids,
633
- "max_workers": request.max_workers
634
- }
635
-
636
- # 启动后台任务
637
- background_tasks.add_task(
638
- process_extract_task,
639
- task_id=task_id,
640
- account_ids=request.account_ids,
641
- max_workers=request.max_workers
642
- )
643
-
644
- return {
645
- "task_id": task_id,
646
- "message": "链接提取任务已启动",
647
- "status": "running"
648
- }
649
-
650
- except Exception as e:
651
- logger.error(f"启动提取任务出错: {str(e)}")
652
- raise HTTPException(status_code=500, detail=f"启动提取任务出错: {str(e)}")
653
-
654
- @app.get("/tasks/{task_id}", dependencies=[Depends(verify_token)])
655
- async def get_task_status(task_id: str):
656
- """获取任务状态"""
657
- if task_id not in running_tasks:
658
- raise HTTPException(status_code=404, detail=f"未找到任务ID: {task_id}")
659
-
660
- return running_tasks[task_id]
661
-
662
- @app.get("/tasks", dependencies=[Depends(verify_token)])
663
- async def get_all_tasks():
664
- """获取所有任务状态"""
665
- return running_tasks
666
-
667
- @app.post("/import", response_model=ImportResponse, dependencies=[Depends(verify_token)])
668
- async def import_accounts(file: UploadFile = File(...)):
669
- """从上传的文件导入账号"""
670
- try:
671
- content = await file.read()
672
- # 假设文件是utf-8编码文本
673
- text = content.decode("utf-8")
674
- # 将内容写入临时文件,再用原有的import_data逻辑导入
675
- import tempfile
676
- with tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8", suffix=".txt") as tmp:
677
- tmp.write(text)
678
- tmp_path = tmp.name
679
- imported_count = controller.import_data(tmp_path)
680
- # 获取总账号数
681
- with controller.db_manager.lock:
682
- cursor = controller.db_manager.conn.cursor()
683
- cursor.execute("SELECT COUNT(*) FROM accounts")
684
- total = cursor.fetchone()[0]
685
- # 删除临时文件
686
- import os
687
- os.remove(tmp_path)
688
- return ImportResponse(
689
- imported_count=imported_count,
690
- total_accounts=total
691
- )
692
- except HTTPException:
693
- raise
694
- except Exception as e:
695
- logger.error(f"导入账号出错: {str(e)}")
696
- raise HTTPException(status_code=500, detail=f"导入账号出错: {str(e)}")
697
-
698
- @app.post("/export", dependencies=[Depends(verify_token)])
699
- async def export_accounts(request: dict = Body(...)):
700
- """导出账号到文件并下载到本地,支持所有筛选条件"""
701
- try:
702
- import io
703
- buffer = io.StringIO()
704
- # 动态拼接筛选条件
705
- query_conditions = []
706
- query_params = []
707
- status = request.get('status', None)
708
- used = request.get('used', None)
709
- search = request.get('search', None)
710
- has_activation_link = request.get('has_activation_link', None)
711
- id_min = request.get('id_min', None)
712
- id_max = request.get('id_max', None)
713
- register_time_start = request.get('register_time_start', None)
714
- register_time_end = request.get('register_time_end', None)
715
- if status is not None and status != '':
716
- query_conditions.append("status = ?")
717
- query_params.append(status)
718
- if search:
719
- query_conditions.append("(username LIKE ? OR security_email LIKE ? OR notes LIKE ? OR activation_link LIKE ?)")
720
- search_param = f"%{search}%"
721
- query_params.extend([search_param, search_param, search_param, search_param])
722
- if register_time_start:
723
- query_conditions.append("register_time >= ?")
724
- query_params.append(register_time_start)
725
- if register_time_end:
726
- query_conditions.append("register_time <= ?")
727
- query_params.append(register_time_end)
728
- if id_min:
729
- query_conditions.append("id >= ?")
730
- query_params.append(id_min)
731
- if id_max:
732
- query_conditions.append("id <= ?")
733
- query_params.append(id_max)
734
- if has_activation_link == 'yes':
735
- query_conditions.append("activation_link IS NOT NULL AND activation_link != ''")
736
- elif has_activation_link == 'no':
737
- query_conditions.append("(activation_link IS NULL OR activation_link = '')")
738
- if used is not None and used != '':
739
- query_conditions.append("used = ?")
740
- query_params.append(int(used))
741
- if query_conditions:
742
- where_clause = "WHERE " + " AND ".join(query_conditions)
743
- else:
744
- where_clause = ""
745
- with controller.db_manager.lock:
746
- cursor = controller.db_manager.conn.cursor()
747
- cursor.execute(f"""
748
- SELECT activation_link
749
- FROM accounts
750
- {where_clause}
751
- ORDER BY id DESC
752
- """, query_params)
753
- for row in cursor.fetchall():
754
- if row[0]:
755
- buffer.write(f"{row[0]}\n")
756
- buffer.seek(0)
757
- content = buffer.getvalue().encode('utf-8')
758
- byte_io = io.BytesIO(content)
759
- filename = request.get('filename', 'export_accounts.txt')
760
- headers = {
761
- 'Content-Disposition': f'attachment; filename="{filename}"'
762
- }
763
- return StreamingResponse(
764
- byte_io,
765
- media_type="text/plain",
766
- headers=headers
767
- )
768
- except Exception as e:
769
- logger.error(f"导出账号出错: {str(e)}")
770
- raise HTTPException(status_code=500, detail=f"导出账号出错: {str(e)}")
771
-
772
- @app.get("/statistics", response_model=StatusCounts, dependencies=[Depends(verify_token)])
773
- async def get_statistics():
774
- """获取账号统计信息"""
775
- try:
776
- with controller.db_manager.lock:
777
- cursor = controller.db_manager.conn.cursor()
778
-
779
- # 获取总账号数
780
- cursor.execute("SELECT COUNT(*) FROM accounts")
781
- total = cursor.fetchone()[0]
782
-
783
- # 获取各状态账号数
784
- cursor.execute("SELECT COUNT(*) FROM accounts WHERE status = ?", (STATUS_PENDING,))
785
- pending = cursor.fetchone()[0]
786
-
787
- cursor.execute("SELECT COUNT(*) FROM accounts WHERE status = ?", (STATUS_SUBMITTED,))
788
- submitted = cursor.fetchone()[0]
789
-
790
- cursor.execute("SELECT COUNT(*) FROM accounts WHERE status = ?", (STATUS_FAILED,))
791
- failed = cursor.fetchone()[0]
792
-
793
- cursor.execute("SELECT COUNT(*) FROM accounts WHERE status = ?", (STATUS_LINK_EXTRACTED,))
794
- link_extracted = cursor.fetchone()[0]
795
-
796
- cursor.execute("SELECT COUNT(*) FROM accounts WHERE used = 0")
797
- unused = cursor.fetchone()[0]
798
-
799
- return StatusCounts(
800
- total=total,
801
- pending=pending,
802
- submitted=submitted,
803
- failed=failed,
804
- link_extracted=link_extracted,
805
- unused=unused
806
- )
807
-
808
- except Exception as e:
809
- logger.error(f"获取统计信息出错: {str(e)}")
810
- raise HTTPException(status_code=500, detail=f"获取统计信息出错: {str(e)}")
811
-
812
- @app.post("/reset-failed", dependencies=[Depends(verify_token)])
813
- async def reset_failed_accounts():
814
- """将失败的账号状态重置为未提交状态"""
815
- try:
816
- with controller.db_manager.lock:
817
- cursor = controller.db_manager.conn.cursor()
818
- cursor.execute(
819
- "UPDATE accounts SET status = ?, notes = 'Reset from failed status', updated_at = datetime('now', 'localtime') WHERE status = ?",
820
- (STATUS_PENDING, STATUS_FAILED)
821
- )
822
- controller.db_manager.conn.commit()
823
-
824
- # 获取重置的账号数
825
- reset_count = cursor.rowcount
826
-
827
- return {"reset_count": reset_count, "message": f"已将{reset_count}个失败账号重置为未提交状态"}
828
-
829
- except Exception as e:
830
- logger.error(f"重置失败账号出错: {str(e)}")
831
- raise HTTPException(status_code=500, detail=f"重置失败账号出错: {str(e)}")
832
-
833
- @app.get("/logs", dependencies=[Depends(verify_token)])
834
- async def get_logs(
835
- username: Optional[str] = None,
836
- operation: Optional[str] = None,
837
- status: Optional[str] = None,
838
- page: int = Query(1, ge=1),
839
- per_page: int = Query(20, ge=1, le=100)
840
- ):
841
- """获取操作日志"""
842
- offset = (page - 1) * per_page
843
-
844
- try:
845
- with controller.db_manager.lock:
846
- cursor = controller.db_manager.conn.cursor()
847
-
848
- # 构建查询条件
849
- query_conditions = []
850
- query_params = []
851
-
852
- if username:
853
- query_conditions.append("username LIKE ?")
854
- query_params.append(f"%{username}%")
855
-
856
- if operation:
857
- query_conditions.append("operation = ?")
858
- query_params.append(operation)
859
-
860
- if status:
861
- query_conditions.append("status = ?")
862
- query_params.append(status)
863
-
864
- # 构建完整查询
865
- if query_conditions:
866
- where_clause = "WHERE " + " AND ".join(query_conditions)
867
- else:
868
- where_clause = ""
869
-
870
- # 计算总记录数
871
- count_query = f"SELECT COUNT(*) FROM operation_logs {where_clause}"
872
- cursor.execute(count_query, query_params)
873
- total = cursor.fetchone()[0]
874
-
875
- # 获取分页数据
876
- data_query = f"""
877
- SELECT id, username, operation, status, message, created_at
878
- FROM operation_logs
879
- {where_clause}
880
- ORDER BY id DESC
881
- LIMIT ? OFFSET ?
882
- """
883
-
884
- # 添加分页参数
885
- query_params.extend([per_page, offset])
886
- cursor.execute(data_query, query_params)
887
-
888
- logs = []
889
- for row in cursor.fetchall():
890
- logs.append({
891
- "id": row[0],
892
- "username": row[1],
893
- "operation": row[2],
894
- "status": row[3],
895
- "message": row[4],
896
- "created_at": row[5]
897
- })
898
-
899
- return {"logs": logs, "total": total}
900
-
901
- except Exception as e:
902
- logger.error(f"获取操作日志出错: {str(e)}")
903
- raise HTTPException(status_code=500, detail=f"获取操作日志出错: {str(e)}")
904
-
905
- @app.post("/login")
906
- async def login(data: dict = Body(...)):
907
- password = data.get("password", "")
908
- if password == ADMIN_PASSWORD:
909
- return {"token": ADMIN_PASSWORD}
910
- else:
911
- raise HTTPException(status_code=401, detail="密码错误")
912
-
913
- if __name__ == "__main__":
914
- # 运行FastAPI应用
915
- uvicorn.run("api_server:app", host="0.0.0.0", port=8000, reload=True)