328 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			328 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ### **轻量化账号管理平台 - 工程设计蓝图**
 | ||
| 
 | ||
| #### 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. 数据库设计 (单表模型)
 | ||
| 
 | ||
| ```typescript
 | ||
| // 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. 业务模型
 | ||
| 
 | ||
| ```typescript
 | ||
| // 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 通信规范
 | ||
| 
 | ||
| ```typescript
 | ||
| // 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 请求/响应体类型
 | ||
| 
 | ||
| ```typescript
 | ||
| // 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}` 进行身份验证和授权。
 | ||
| 
 | ||
| 1.  **获取账号**
 | ||
|     *   **Endpoint**: `GET /acquire`
 | ||
|     *   **描述**: 获取一个或多个可用的账号,并将其状态锁定。
 | ||
|     *   **Query Params**:
 | ||
|         *   `platform` (string, **必须**): 平台标识。
 | ||
|         *   `count` (number, 可选, 默认 1): 获取数量。
 | ||
|     *   **成功响应 (200 OK)**:
 | ||
|         ```json
 | ||
|         // 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)**:
 | ||
|         ```json
 | ||
|         // Body: ApiResponse<null>
 | ||
|         {
 | ||
|           "code": 1001, // BusinessCode.NoResource
 | ||
|           "message": "No available accounts found for platform 'google'.",
 | ||
|           "data": null
 | ||
|         }
 | ||
|         ```
 | ||
| 
 | ||
| 2.  **更新账号状态**
 | ||
|     *   **Endpoint**: `GET /update/{accountId}/{newStatus}`
 | ||
|     *   **描述**: 快速更新指定账号的状态。
 | ||
|     *   **Query Params**:
 | ||
|         *   `notes` (string, 可选): 添加备注信息。
 | ||
|     *   **成功响应 (200 OK)**:
 | ||
|         ```json
 | ||
|         // Body: ApiResponse<{ updatedId: number }>
 | ||
|         {
 | ||
|           "code": 0,
 | ||
|           "message": "Account status updated to 'banned'.",
 | ||
|           "data": { "updatedId": 12345 }
 | ||
|         }
 | ||
|         ```
 | ||
|     *   **传输错误响应 (403 Forbidden)**: 当 URL 中的 `ownerId` 与 `accountId` 对应的账号所有者不匹配时返回。
 | ||
| 
 | ||
| 3.  **上传/更新账号**
 | ||
|     *   **Endpoint**: `POST /upload`
 | ||
|     *   **描述**: 批量创建或更新账号。
 | ||
|     *   **Request Body**: `ScriptUploadItem[]`
 | ||
|     *   **成功响应 (200 OK)**:
 | ||
|         ```json
 | ||
|         // 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 }
 | ||
|         }
 | ||
|         ```
 | ||
| 
 | ||
| #### B. 前端管理 API (Base URL: `/web/v1`)
 | ||
| 
 | ||
| 1.  **获取账号列表 (复杂查询)**
 | ||
|     *   **Endpoint**: `POST /accounts/list`
 | ||
|     *   **描述**: 支持多维度筛选、分页和排序的账号查询。
 | ||
|     *   **Request Body**: `ListAccountsBody`
 | ||
|     *   **成功响应 (200 OK)**:
 | ||
|         ```json
 | ||
|         // Body: ApiResponse<ListAccountsResponse>
 | ||
|         {
 | ||
|           "code": 0,
 | ||
|           "message": "Success",
 | ||
|           "data": {
 | ||
|             "list": [ /* ... Account 对象数组 ... */ ],
 | ||
|             "pagination": { "page": 1, "pageSize": 50, "total": 123, "totalPages": 3 }
 | ||
|           }
 | ||
|         }
 | ||
|         ```
 | ||
| 
 | ||
| 2.  **批量删除账号**
 | ||
|     *   **Endpoint**: `POST /accounts/delete-batch`
 | ||
|     *   **描述**: 根据 ID 列表批量删除账号。
 | ||
|     *   **Request Body**: `BatchDeleteBody`
 | ||
|     *   **成功响应 (200 OK)**:
 | ||
|         ```json
 | ||
|         // Body: ApiResponse<{ deletedCount: number }>
 | ||
|         {
 | ||
|           "code": 0,
 | ||
|           "message": "Successfully deleted 5 accounts.",
 | ||
|           "data": { "deletedCount": 5 }
 | ||
|         }
 | ||
|         ```
 | ||
| 
 | ||
| 3.  **批量更新账号**
 | ||
|     *   **Endpoint**: `POST /accounts/update-batch`
 | ||
|     *   **描述**: 批量修改账号的状态、所有者或备注。
 | ||
|     *   **Request Body**: `BatchUpdateBody`
 | ||
|     *   **成功响应 (200 OK)**:
 | ||
|         ```json
 | ||
|         // Body: ApiResponse<{ updatedCount: number }>
 | ||
|         {
 | ||
|           "code": 0,
 | ||
|           "message": "Successfully updated 3 accounts.",
 | ||
|           "data": { "updatedCount": 3 }
 | ||
|         }
 | ||
|         ```
 | ||
| 
 | ||
| 4.  **核心统计接口**
 | ||
|     *   **Endpoint**: `GET /stats/overview`
 | ||
|     *   **描述**: 一次性获取仪表盘所需的全部聚合统计数据。
 | ||
|     *   **成功响应 (200 OK)**:
 | ||
|         ```json
 | ||
|         // Body: ApiResponse<StatsOverview>
 | ||
|         {
 | ||
|           "code": 0,
 | ||
|           "message": "Success",
 | ||
|           "data": { /* ... StatsOverview 对象 ... */ }
 | ||
|         }
 | ||
|         ```
 | ||
| 
 | ||
| ### **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';`
 | ||
| *   **目的**: 自动释放因脚本异常中断而未能解锁的账号,保证账号资源的流转性。 | 
