10 KiB
10 KiB
轻量化账号管理平台 - 工程设计蓝图
1. 核心设计哲学
- 单表驱动: 所有核心数据聚合在
accounts表,简化数据库模型。 - OwnerID 即身份:
ownerId同时作为拥有者标识和API认证凭证。 - 配置硬编码: 关键状态(如
available,locked)硬编码在业务逻辑中,降低系统复杂性。 - 职责分离的API: 为自动化脚本和管理后台提供两套独立的、高度优化的API。
- 规范化通信: 所有API遵循统一的响应结构,业务结果通过
code字段传递。 - 高性能与健壮性: 依赖PostgreSQL原生特性(事务、行锁、索引)和专用的后台任务确保系统稳定。
2. 技术栈
- 运行时: Node.js (v18+)
- 语言: TypeScript
- Web 框架: Fastify
- 数据库: PostgreSQL (v14+)
- ORM/查询构建器: Drizzle ORM
- 数据校验: Zod
3. 项目结构
采用模块化、可扩展的目录结构,清晰分离各项职责。
.
├── drizzle/ # Drizzle ORM 迁移文件
├── src/
│ ├── api/ # API 路由定义
│ │ └── v1/
│ │ ├── web/ # 前端管理后台 API
│ │ │ ├── accounts.ts # 账号增删改查
│ │ │ └── stats.ts # 统计数据
│ │ └── script/ # 自动化脚本 API
│ │ └── actions.ts # 获取、更新、上传
│ ├── core/ # 核心业务逻辑 (Services)
│ │ └── AccountService.ts # 封装所有与账号相关的业务操作
│ ├── db/ # 数据库配置与 Schema
│ │ ├── index.ts # Drizzle 客户端实例
│ │ └── schema.ts # 数据库表定义
│ ├── jobs/ # 后台定时任务
│ │ └── staleLockCleanup.ts # 清理超时锁定的任务
│ ├── lib/ # 通用库、工具函数
│ │ └── apiResponse.ts # 统一响应格式的辅助函数
│ ├── types/ # 全局类型定义
│ │ ├── api.ts # API 请求/响应体类型
│ │ └── index.ts # 核心业务模型类型
│ ├── index.ts # 应用入口,启动 Fastify 服务器
│ └── config.ts # 应用配置 (数据库连接、端口等)
├── .env # 环境变量
├── .env.example
├── package.json
└── tsconfig.json
4. 数据库设计 (单表模型)
// file: src/db/schema.ts
import { pgTable, serial, varchar, timestamp, uniqueIndex, text, index } from 'drizzle-orm/pg-core';
export const accounts = pgTable('accounts', {
id: serial('id').primaryKey(),
ownerId: varchar('owner_id', { length: 128 }).notNull(),
platform: varchar('platform', { length: 100 }).notNull(),
customId: varchar('custom_id', { length: 255 }).notNull(),
data: text('data').notNull(),
status: varchar('status', { length: 50 }).notNull(),
notes: text('notes'),
lockedAt: timestamp('locked_at'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => ({
platformCustomIdIdx: uniqueIndex('platform_custom_id_idx').on(table.platform, table.customId),
ownerIdStatusIdx: index('owner_id_status_idx').on(table.ownerId, table.status),
platformOwnerIdx: index('platform_owner_idx').on(table.platform, table.ownerId),
}));
5. 核心类型与接口定义
5.1. 业务模型
// file: src/types/index.ts
import { accounts } from '../db/schema';
import { InferSelectModel } from 'drizzle-orm';
// 从数据库 Schema 自动推断出的 Account 类型,确保与数据库一致
export type Account = InferSelectModel<typeof accounts>;
5.2. API 通信规范
// file: src/types/api.ts
/**
* 业务状态码枚举,避免使用魔法数字
*/
export enum BusinessCode {
Success = 0,
NoResource = 1001,
InvalidParams = 2001,
ResourceConflict = 3001,
PermissionDenied = 4001, // 业务层权限拒绝
BusinessError = 5001,
}
/**
* 统一的 API 响应体结构
*/
export interface ApiResponse<T> {
code: BusinessCode;
message: string;
data: T | null;
}
/**
* 分页查询结果
*/
export interface PaginationResult {
page: number;
pageSize: number;
total: number;
totalPages: number;
}
5.3. API 请求/响应体类型
// file: src/types/api.ts
// --- 脚本 API ---
export type ScriptAcquireResponse = Pick<Account, 'id' | 'customId' | 'data'>[];
export interface ScriptUploadItem {
platform: string;
customId: string;
data: string;
status?: string;
}
// --- 前端管理 API ---
export interface ListAccountsFilters {
platform?: string;
status?: string[];
ownerId?: string;
search?: string; // 模糊搜索 customId 或 notes
}
export interface ListAccountsBody {
filters: ListAccountsFilters;
pagination: {
page: number;
pageSize: number;
};
sort: {
field: keyof Account;
order: 'asc' | 'desc';
};
}
export interface ListAccountsResponse {
list: Account[];
pagination: PaginationResult;
}
export interface BatchDeleteBody {
ids: number[];
}
export interface BatchUpdateBody {
ids: number[];
payload: Partial<Pick<Account, 'status' | 'ownerId' | 'notes'>>;
}
export interface StatsOverview {
totalAccounts: number;
platformSummary: Record<string, number>;
ownerSummary: Record<string, number>;
statusSummary: Record<string, number>;
detailedBreakdown: {
platform: string;
ownerId: string;
status: string;
count: number;
}[];
}
6. API 设计
A. 脚本专用 API (Base URL: /s/v1/{ownerId})
认证: 所有请求通过 URL 中的 {ownerId} 进行身份验证和授权。
-
获取账号
- Endpoint:
GET /acquire - 描述: 获取一个或多个可用的账号,并将其状态锁定。
- Query Params:
platform(string, 必须): 平台标识。count(number, 可选, 默认 1): 获取数量。
- 成功响应 (200 OK):
// Body: ApiResponse<ScriptAcquireResponse> { "code": 0, // BusinessCode.Success "message": "Successfully acquired 2 accounts.", "data": [ { "id": 101, "customId": "user1@gmail.com", "data": "cookie_string_1" }, { "id": 102, "customId": "user2@gmail.com", "data": "cookie_string_2" } ] } - 业务失败响应 (200 OK):
// Body: ApiResponse<null> { "code": 1001, // BusinessCode.NoResource "message": "No available accounts found for platform 'google'.", "data": null }
- Endpoint:
-
更新账号状态
- Endpoint:
GET /update/{accountId}/{newStatus} - 描述: 快速更新指定账号的状态。
- Query Params:
notes(string, 可选): 添加备注信息。
- 成功响应 (200 OK):
// Body: ApiResponse<{ updatedId: number }> { "code": 0, "message": "Account status updated to 'banned'.", "data": { "updatedId": 12345 } } - 传输错误响应 (403 Forbidden): 当 URL 中的
ownerId与accountId对应的账号所有者不匹配时返回。
- Endpoint:
-
上传/更新账号
- Endpoint:
POST /upload - 描述: 批量创建或更新账号。
- Request Body:
ScriptUploadItem[] - 成功响应 (200 OK):
// Body: ApiResponse<{ processedCount: number, createdCount: number, updatedCount: number }> { "code": 0, "message": "Successfully processed 10 accounts (5 created, 5 updated).", "data": { "processedCount": 10, "createdCount": 5, "updatedCount": 5 } }
- Endpoint:
B. 前端管理 API (Base URL: /web/v1)
-
获取账号列表 (复杂查询)
- Endpoint:
POST /accounts/list - 描述: 支持多维度筛选、分页和排序的账号查询。
- Request Body:
ListAccountsBody - 成功响应 (200 OK):
// Body: ApiResponse<ListAccountsResponse> { "code": 0, "message": "Success", "data": { "list": [ /* ... Account 对象数组 ... */ ], "pagination": { "page": 1, "pageSize": 50, "total": 123, "totalPages": 3 } } }
- Endpoint:
-
批量删除账号
- Endpoint:
POST /accounts/delete-batch - 描述: 根据 ID 列表批量删除账号。
- Request Body:
BatchDeleteBody - 成功响应 (200 OK):
// Body: ApiResponse<{ deletedCount: number }> { "code": 0, "message": "Successfully deleted 5 accounts.", "data": { "deletedCount": 5 } }
- Endpoint:
-
批量更新账号
- Endpoint:
POST /accounts/update-batch - 描述: 批量修改账号的状态、所有者或备注。
- Request Body:
BatchUpdateBody - 成功响应 (200 OK):
// Body: ApiResponse<{ updatedCount: number }> { "code": 0, "message": "Successfully updated 3 accounts.", "data": { "updatedCount": 3 } }
- Endpoint:
-
核心统计接口
- Endpoint:
GET /stats/overview - 描述: 一次性获取仪表盘所需的全部聚合统计数据。
- 成功响应 (200 OK):
// Body: ApiResponse<StatsOverview> { "code": 0, "message": "Success", "data": { /* ... StatsOverview 对象 ... */ } }
- Endpoint:
7. 关键后台任务
- 任务名称: Stale Lock Cleanup (超时锁定清理)
- 执行频率: 建议每 1 分钟执行一次。
- 核心逻辑:
- 查找所有
status为locked且lockedAt时间早于预设阈值(例如 5 分钟前)的账号。 - 执行 SQL:
UPDATE accounts SET status = 'available', lockedAt = NULL WHERE status = 'locked' AND lockedAt < NOW() - INTERVAL '5 minutes';
- 查找所有
- 目的: 自动释放因脚本异常中断而未能解锁的账号,保证账号资源的流转性。