From c1a493222c970e29c2908a04d45837e02c18783e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=B4=BA?= Date: Fri, 5 Sep 2025 21:07:34 +0800 Subject: [PATCH] first commit --- .gitignore | 50 +++++ README.md | 175 ++++++++++++++++++ frontend/index.html | 13 ++ frontend/package.json | 31 ++++ frontend/postcss.config.js | 6 + frontend/src/App.tsx | 145 +++++++++++++++ frontend/src/components/ApiKeyCard.tsx | 147 +++++++++++++++ frontend/src/components/ApiKeyForm.tsx | 105 +++++++++++ frontend/src/components/Stats.tsx | 72 +++++++ frontend/src/index.css | 53 ++++++ frontend/src/main.tsx | 10 + frontend/src/services/api.ts | 56 ++++++ frontend/src/types/api.ts | 29 +++ frontend/src/utils/cn.ts | 6 + frontend/tailwind.config.js | 45 +++++ frontend/tsconfig.json | 21 +++ frontend/tsconfig.node.json | 10 + frontend/vite.config.ts | 19 ++ package.json | 45 +++++ .../20250905094912_init/migration.sql | 17 ++ .../migration.sql | 2 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 27 +++ quick-start.bat | 22 +++ src/config/siliconflow.ts | 115 ++++++++++++ src/routes/apiKeys.ts | 113 +++++++++++ src/server.ts | 55 ++++++ src/services/apiKeyService.ts | 140 ++++++++++++++ src/utils/errors.ts | 13 ++ tsconfig.json | 26 +++ tsconfig.server.json | 9 + 31 files changed, 1580 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/components/ApiKeyCard.tsx create mode 100644 frontend/src/components/ApiKeyForm.tsx create mode 100644 frontend/src/components/Stats.tsx create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/services/api.ts create mode 100644 frontend/src/types/api.ts create mode 100644 frontend/src/utils/cn.ts create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 package.json create mode 100644 prisma/migrations/20250905094912_init/migration.sql create mode 100644 prisma/migrations/20250905105000_remove_currency/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 quick-start.bat create mode 100644 src/config/siliconflow.ts create mode 100644 src/routes/apiKeys.ts create mode 100644 src/server.ts create mode 100644 src/services/apiKeyService.ts create mode 100644 src/utils/errors.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.server.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..574e485 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Dependencies +node_modules/ +frontend/node_modules/ +package-lock.json +frontend/package-lock.json + +# Build outputs +dist/ +frontend/dist/ + +# Environment files +.env +.env.local +.env.*.local + +# OS files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# Database +prisma/dev.db +prisma/dev.db-journal + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Coverage +coverage/ +*.lcov + +# Temporary files +tmp/ +temp/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..46781ba --- /dev/null +++ b/README.md @@ -0,0 +1,175 @@ +# SiliconFlow API Key 验证器 + +这是一个全栈应用程序,用于验证和管理 SiliconFlow API Key,实时监控账户余额状态。 + +## ✨ 功能特性 + +- 🔐 **API Key 管理**: 安全地存储和管理多个API Key +- ✅ **实时验证**: 批量或单独验证API Key有效性 +- 💰 **余额监控**: 查看实时余额信息 +- 📊 **统计面板**: 一目了然的账户状态统计 +- 🎨 **现代界面**: 美观易用的用户界面 +- 🔒 **数据安全**: 本地数据库存储,确保数据私密性 + +## 🚀 快速开始 + +### 1️⃣ 克隆和安装依赖 + +```bash +# 克隆仓库 +git clone [your-repo-url] +cd siliconflow-api-key-validator + +# 安装后端依赖 +npm install + +# 安装前端依赖 +cd frontend +npm install +cd .. +``` + +### 2️⃣ 数据库初始化 + +```bash +# 创建数据库 +npx prisma generate +npx prisma migrate dev --name init +``` + +### 3️⃣ 启动应用 + +#### 开发模式 + +```bash +# 同时启动后端和前端开发服务器 +npm run dev + +# 或者分别启动 +npm run server:dev # 后端 (端口3000) +cd frontend && npm run dev # 前端 (端口3001) +``` + +#### 生产模式 + +```bash +# 构建 +npm run build + +# 启动生产服务器 +npm start +``` + +## 🌐 访问应用 + +- **首页**: http://localhost:3001 +- **API 地址**: http://localhost:3000/api +- **健康检查**: http://localhost:3000/health + +## 📋 API 端点 + +### API Keys 管理 + +- `GET /api/api-keys` - 获取所有API Key +- `POST /api/api-keys` - 添加新API Key +- `POST /api/api-keys/:id/validate` - 验证单个API Key +- `POST /api/api-keys/validate-all` - 批量验证所有API Key +- `DELETE /api/api-keys/:id` - 删除API Key + +## 🛠️ 技术栈 + +### 后端 +- **Node.js** + **Express** +- **TypeScript** - 类型安全 +- **Prisma** - 数据库ORM +- **SQLite** - 轻量级数据库 +- **Axios** - HTTP客户端 +- **CORS** + **Helmet** + **Compression** - 安全与性能优化 + +### 前端 +- **React 18** + **TypeScript** +- **Vite** - 现代构建工具 +- **TailwindCSS** - 样式框架 +- **Axios** - HTTP客户端 +- **Lucide React** - 图标库 + +## 📁 项目结构 + +``` +siliconflow-api-key-validator/ +├── src/ +│ ├── config/ +│ │ └── siliconflow.ts # SiliconFlow API 配置和验证逻辑 +│ ├── services/ +│ │ └── apiKeyService.ts # 业务逻辑服务 +│ ├── routes/ +│ │ └── apiKeys.ts # API 路由定义 +│ ├── utils/ +│ │ └── errors.ts # 自定义错误类 +│ └── server.ts # Express 服务器入口 +├── frontend/ +│ ├── src/ +│ │ ├── components/ # React 组件 +│ │ ├── services/ # 前端 API 服务 +│ │ ├── types/ # TypeScript 类型定义 +│ │ ├── utils/ # 工具函数 +│ │ └── App.tsx # 主应用组件 +│ ├── package.json +│ ├── vite.config.ts +│ └── tailwind.config.js +├── prisma/ +│ └── schema.prisma # 数据模型定义 +└── README.md +``` + +## 🔧 环境变量配置 + +### .env +```env +PORT=3000 +NODE_ENV=development +API_TIMEOUT=10000 +``` + +## 📝 使用说明 + +### 添加API Key +1. 在主页的"添加新API Key"表单中输入: + - API Key名称(如"生产环境") + - 实际的API Key + - 可选描述信息 + +### 验证API Key +- **单独验证**: 点击卡片右上角的刷新图标 +- **批量验证**: 使用页面顶部"批量验证"按钮 + +### 查看账户信息 +- **状态**: 绿色表示有效,红色表示无效 +- **余额**: 显示实时账户余额 +- **用户信息**: 显示关联的账户邮箱和名称 +- **验证统计**: 显示验证次数和最后验证时间 + +## 🐛 故障排除 + +### 常见问题 + +1. **端口占用**: 确保3000和3001端口未被占用 +2. **数据库连接**: 检查`prisma/dev.db`文件是否存在 +3. **构建错误**: 确保所有依赖已安装: `npm install` 和 `cd frontend && npm install` +4. **API验证失败**: 检查网络连接和API Key格式 + +### 重置数据 + +```bash +# 删除数据库重新初始化 +rm prisma/dev.db +npx prisma migrate dev --name init +``` + +## 🤝 贡献 + +欢迎提交Issue和Pull Request! + +## 📄 许可证 + +MIT License - 详见 LICENSE 文件 \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..6093472 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + SiliconFlow API Key 验证器 + + +
+ + + \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..5ab474a --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,31 @@ +{ + "name": "siliconflow-api-key-validator-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.6.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", + "lucide-react": "^0.292.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tailwind-merge": "^2.0.0" + }, + "devDependencies": { + "@types/react": "^18.3.24", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^4.7.0", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.3.6", + "typescript": "^5.2.2", + "vite": "^5.4.19" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..e99ebc2 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..e2cf001 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,145 @@ +import React, { useState, useEffect } from 'react'; +import { AlertCircle, RefreshCw } from 'lucide-react'; +import { ApiKeyForm } from './components/ApiKeyForm'; +import { ApiKeyCard } from './components/ApiKeyCard'; +import { Stats } from './components/Stats'; +import { apiKeyService } from './services/api'; +import type { ApiKey } from './types/api'; + +function App() { + const [apiKeys, setApiKeys] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [isValidatingAll, setIsValidatingAll] = useState(false); + + const loadApiKeys = async () => { + setIsLoading(true); + setError(null); + try { + const data = await apiKeyService.getAllApiKeys(); + setApiKeys(data); + } catch (err) { + setError(err instanceof Error ? err.message : '加载失败'); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + loadApiKeys(); + }, []); + + const handleAddSuccess = async () => { + await loadApiKeys(); + }; + + const handleUpdate = (id: string, updated: ApiKey) => { + setApiKeys(prev => prev.map(key => key.id === id ? updated : key)); + }; + + const handleDelete = (id: string) => { + setApiKeys(prev => prev.filter(key => key.id !== id)); + }; + + const handleValidateAll = async () => { + setIsValidatingAll(true); + try { + const results = await apiKeyService.validateAllApiKeys(); + setApiKeys(results); + } catch (err) { + setError(err instanceof Error ? err.message : '批量验证失败'); + } finally { + setIsValidatingAll(false); + } + }; + + return ( +
+
+
+

+ SiliconFlow API Key 管理器 +

+

+ 管理和验证您的 SiliconFlow API Key,实时监控账户状态和余额。 +

+
+ + {isLoading ? ( +
+
+
+

加载中...

+
+
+ ) : error ? ( +
+
+ +
+

加载失败

+

{error}

+
+
+
+ ) : ( + <> + + +
+

+ API Keys ({apiKeys.length}) +

+ {apiKeys.length > 0 && ( + + )} +
+ + {apiKeys.length === 0 ? ( +
+
+ + + +
+

暂无 API Key

+

添加您的第一个API Key开始使用

+
+ ) : ( +
+ {apiKeys.map((apiKey) => ( + + ))} +
+ )} + + )} + +
+
+
+

+ 添加新 API Key +

+ +
+
+
+
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/frontend/src/components/ApiKeyCard.tsx b/frontend/src/components/ApiKeyCard.tsx new file mode 100644 index 0000000..38c019b --- /dev/null +++ b/frontend/src/components/ApiKeyCard.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import { CheckCircle2, XCircle, Loader2, Trash2, RefreshCw } from 'lucide-react'; +import { apiKeyService } from '../services/api'; +import type { ApiKey } from '../types/api'; + +interface ApiKeyCardProps { + apiKey: ApiKey; + onUpdate: (id: string, updated: ApiKey) => void; + onDelete: (id: string) => void; +} + +export function ApiKeyCard({ apiKey, onUpdate, onDelete }: ApiKeyCardProps) { + const [isValidating, setIsValidating] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + + const handleValidate = async () => { + setIsValidating(true); + try { + const updated = await apiKeyService.validateApiKey(apiKey.id); + onUpdate(apiKey.id, updated); + } catch (error) { + console.error('验证失败:', error); + } finally { + setIsValidating(false); + } + }; + + const handleDelete = async () => { + if (!confirm("确定要删除这个API Key吗?此操作不可恢复。")) { + return; + } + + setIsDeleting(true); + try { + await apiKeyService.deleteApiKey(apiKey.id); + onDelete(apiKey.id); + } catch (error) { + console.error('删除失败:', error); + } finally { + setIsDeleting(false); + } + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('zh-CN', { + style: 'currency', + currency: 'CNY', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(amount); + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleString('zh-CN'); + }; + + return ( +
+
+
+
+

+ {apiKey.name} +

+ {apiKey.description && ( +

+ {apiKey.description} +

+ )} +
+ +
+ + +
+
+ +
+ {apiKey.isValid ? ( + + + 有效 + + ) : ( + + + 无效 + + )} +
+ + {/* 直接显示完整的API Key */} +
+ +
+ {apiKey.key} +
+
+ + {apiKey.error ? ( +
+

{apiKey.error}

+
+ ) : null} + + {apiKey.balance !== undefined && ( +
+

+ 账户余额: + + {formatCurrency(apiKey.balance)} + +

+ {apiKey.user && ( +

+ 用户: {apiKey.user.name} ({apiKey.user.email}) +

+ )} +
+ )} + +
+ + 最后验证: {formatDate(apiKey.lastChecked)} + + + 验证次数: {apiKey.checkCount} + +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/ApiKeyForm.tsx b/frontend/src/components/ApiKeyForm.tsx new file mode 100644 index 0000000..e71fb95 --- /dev/null +++ b/frontend/src/components/ApiKeyForm.tsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react'; +import { Plus, Loader2 } from 'lucide-react'; +import { apiKeyService } from '../services/api'; +import { cn } from '../utils/cn'; + +interface ApiKeyFormProps { + onSuccess: () => void; +} + +export function ApiKeyForm({ onSuccess }: ApiKeyFormProps) { + const [formData, setFormData] = useState({ + name: '', + key: '', + description: '', + }); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + setError(null); + + try { + await apiKeyService.createApiKey(formData); + setFormData({ name: '', key: '', description: '' }); + onSuccess(); + } catch (err) { + setError(err instanceof Error ? err.message : '未知错误'); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+
+ + setFormData({ ...formData, name: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="例如: 生产环境 API" + /> +
+ +
+ + setFormData({ ...formData, key: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-sm" + placeholder="sk-... 或sf-xxxxx" + /> +
+ +
+ +