Files
accounts-manager-web/docs/后端设计.md
2025-09-22 15:40:57 +08:00

328 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

### **轻量化账号管理平台 - 工程设计蓝图**
#### 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';`
* **目的**: 自动释放因脚本异常中断而未能解锁的账号保证账号资源的流转性