add:新增账号使用记录

This commit is contained in:
Cordell Tippy
2025-03-29 00:29:49 +08:00
parent 43fea63534
commit 1244d37584
4 changed files with 195 additions and 13 deletions

73
api.py
View File

@@ -1,9 +1,9 @@
from fastapi import FastAPI, HTTPException, status, UploadFile
from fastapi import FastAPI, HTTPException, status, UploadFile, Request
from pydantic import BaseModel
from typing import Optional
from typing import Optional, List
from sqlalchemy import select, func, delete, desc
from pathlib import Path
from database import get_session, AccountModel, init_db
from database import get_session, AccountModel, AccountUsageRecordModel, init_db
from fastapi.middleware.cors import CORSMiddleware
from datetime import datetime
import uvicorn
@@ -1103,7 +1103,7 @@ async def import_accounts(file: UploadFile):
# 添加"使用Token"功能
@app.post("/account/use-token/{id}", tags=["Accounts"])
async def use_account_token(id: int):
async def use_account_token(id: int, request: Request):
"""使用指定账号的Token更新Cursor认证"""
try:
async with get_session() as session:
@@ -1130,6 +1130,25 @@ async def use_account_token(id: int):
resetter = CursorShadowPatcher()
patch_success = resetter.reset_machine_ids()
# 记录使用记录
if success:
# 获取请求客户端IP
client_ip = request.client.host
# 获取用户代理
user_agent = request.headers.get("User-Agent", "")
# 创建使用记录
usage_record = AccountUsageRecordModel(
id=int(time.time() * 1000), # 使用毫秒级时间戳作为ID
account_id=id,
email=account.email,
ip=client_ip,
user_agent=user_agent,
created_at=datetime.now().isoformat()
)
session.add(usage_record)
await session.commit()
if success and patch_success:
return {
@@ -1149,6 +1168,52 @@ async def use_account_token(id: int):
error(traceback.format_exc())
return {"success": False, "message": f"使用Token失败: {str(e)}"}
# 添加获取账号使用记录接口
@app.get("/account/{id}/usage-records", tags=["Accounts"])
async def get_account_usage_records(id: int):
"""获取指定账号的使用记录"""
try:
async with get_session() as session:
# 通过ID查询账号
result = await session.execute(
select(AccountModel).where(AccountModel.id == id)
)
account = result.scalar_one_or_none()
if not account:
return {"success": False, "message": f"ID为 {id} 的账号不存在"}
# 查询使用记录
result = await session.execute(
select(AccountUsageRecordModel)
.where(AccountUsageRecordModel.account_id == id)
.order_by(desc(AccountUsageRecordModel.created_at))
)
records = result.scalars().all()
# 转换记录为字典列表
records_list = []
for record in records:
records_list.append({
"id": record.id,
"account_id": record.account_id,
"email": record.email,
"ip": record.ip,
"user_agent": record.user_agent,
"created_at": record.created_at
})
return {
"success": True,
"records": records_list
}
except Exception as e:
error(f"获取账号使用记录失败: {str(e)}")
error(traceback.format_exc())
return {"success": False, "message": f"获取账号使用记录失败: {str(e)}"}
# 添加"重置设备id"功能
@app.get("/reset-machine", tags=["System"])
async def reset_machine():

View File

@@ -1,6 +1,6 @@
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy import Column, String, Text, text, BigInteger
from sqlalchemy import Column, String, Text, text, BigInteger, ForeignKey
from contextlib import asynccontextmanager
from logger import info, error
from config import DATABASE_URL
@@ -24,6 +24,17 @@ class AccountModel(Base):
id = Column(BigInteger, nullable=False, index=True) # 添加毫秒时间戳列并创建索引
# 账号使用记录模型
class AccountUsageRecordModel(Base):
__tablename__ = "account_usage_records"
id = Column(BigInteger, primary_key=True, autoincrement=True)
account_id = Column(BigInteger, nullable=False, index=True) # 账号ID
email = Column(String, nullable=False, index=True) # 账号邮箱
ip = Column(String, nullable=True) # 使用者IP
user_agent = Column(Text, nullable=True) # 使用者UA
created_at = Column(Text, nullable=False) # 创建时间
def create_engine():
"""创建数据库引擎"""
# 直接使用配置文件中的数据库URL

View File

@@ -589,6 +589,44 @@
</div>
</div>
<!-- 账号使用记录模态框 -->
<div class="modal fade" id="usageRecordModal" tabindex="-1" aria-labelledby="usageRecordModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="usageRecordModalLabel">账号使用记录</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<strong>账号邮箱:</strong> <span id="recordEmail"></span>
</div>
<div class="table-responsive">
<table class="table table-hover table-sm align-middle">
<thead>
<tr>
<th>使用时间</th>
<th>使用者IP</th>
<th>使用者UA</th>
</tr>
</thead>
<tbody id="usageRecordBody">
<!-- 记录将被动态填充 -->
</tbody>
</table>
</div>
<div id="no-records" class="text-center p-4" style="display: none;">
<i class="fas fa-history fa-2x text-muted mb-2"></i>
<p>暂无使用记录</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- 自动刷新指示器 -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 5">
<div class="d-flex align-items-center small text-muted">

View File

@@ -397,15 +397,17 @@ function updateAccountsTable(accounts) {
<i class="fas fa-eye toggle-password" data-password="${account.password}" title="显示/隐藏密码"></i>
<i class="fas fa-copy copy-btn ms-1" data-copy="${account.password}" title="复制密码"></i>
</td>
${renderTokenColumn(account.token, account.id)}
${renderTokenColumn(account.token, account.id, account.email)}
${renderUsageProgress(account.usage_limit)}
<td class="d-none d-lg-table-cell">
${account.created_at || '未知'}
</td>
<td>
<button class="btn btn-sm btn-outline-primary get-usage-btn" data-email="${account.email}" title="查询使用量">
<i class="fas fa-chart-pie"></i>
</button>
<div class="btn-group">
<button class="btn btn-sm btn-outline-primary get-usage-btn" data-email="${account.email}" title="查询使用量">
<i class="fas fa-chart-pie"></i>
</button>
</div>
</td>
<td class="operation-column">
<div class="d-flex flex-wrap gap-1">
@@ -493,9 +495,11 @@ function renderAccountsTable() {
${account.created_at || '未知'}
</td>
<td>
<button class="btn btn-sm btn-outline-primary get-usage-btn" data-email="${account.email}" title="查询使用量">
<i class="fas fa-chart-pie"></i>
</button>
<div class="btn-group">
<button class="btn btn-sm btn-outline-primary get-usage-btn" data-email="${account.email}" title="查询使用量">
<i class="fas fa-chart-pie"></i>
</button>
</div>
</td>
<td class="operation-column">
<div class="d-flex flex-wrap gap-1">
@@ -960,6 +964,13 @@ function bindTableEvents() {
const email = $(this).data('email');
getAccountUsage(email);
});
// 查看使用记录按钮
$('.view-records-btn').off('click').on('click', function() {
const email = $(this).data('email');
const id = $(this).data('id');
getAccountUsageRecords(email, id);
});
// 删除按钮
$('.delete-account-btn').off('click').on('click', function() {
@@ -1137,12 +1148,15 @@ function renderUsageProgress(usageLimit) {
}
// 修改Token列的渲染方式
function renderTokenColumn(token, accountId) {
function renderTokenColumn(token, accountId, email) {
return `
<td class="token-column">
<button class="btn btn-sm btn-outline-info view-token-btn" data-token="${token}" data-account-id="${accountId}">
<i class="fas fa-eye"></i> 查看Token
</button>
<button class="btn btn-sm btn-outline-info view-records-btn" data-email="${email}" data-id="${accountId}" title="查看使用记录">
<i class="fas fa-history"></i>
</button>
</td>
`;
}
@@ -1690,4 +1704,58 @@ function importAccounts(file) {
showAlert('danger', '导入账号失败: ' + (xhr.responseJSON?.detail || xhr.statusText));
}
});
}
// 获取账号使用记录
function getAccountUsageRecords(email, id) {
showLoading();
// 设置模态框中的账号邮箱
$('#recordEmail').text(email);
// 清空记录列表
$('#usageRecordBody').empty();
fetch(`/account/${id}/usage-records`)
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
const records = data.records;
if (records && records.length > 0) {
// 隐藏无记录提示
$('#no-records').hide();
// 显示记录
records.forEach(record => {
const row = `
<tr>
<td>${formatDateTime(record.created_at)}</td>
<td>${record.ip || '-'}</td>
<td class="small text-truncate" style="max-width: 300px;" title="${record.user_agent || ''}">
${record.user_agent || '-'}
</td>
</tr>
`;
$('#usageRecordBody').append(row);
});
} else {
// 显示无记录提示
$('#usageRecordBody').empty();
$('#no-records').show();
}
// 显示模态框
new bootstrap.Modal(document.getElementById('usageRecordModal')).show();
} else {
showAlert(`获取使用记录失败: ${data.message || '未知错误'}`, 'danger');
}
})
.catch(error => {
console.error('获取使用记录时发生错误:', error);
hideLoading();
showAlert('获取使用记录失败,请稍后重试', 'danger');
});
}