Files
accounts-manager/docs/设计.md
Your Name 891ae27689 feat: initial commit with accounts manager project structure
- TypeScript项目基础架构
- API路由和账户管理服务
- 数据库模式和迁移
- 基础配置文件和文档

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-23 01:42:50 +08:00

10 KiB
Raw Permalink Blame History

轻量化账号管理平台 - 工程设计蓝图

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} 进行身份验证和授权。

  1. 获取账号

    • 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
      }
      
  2. 更新账号状态

    • 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 中的 ownerIdaccountId 对应的账号所有者不匹配时返回。
  3. 上传/更新账号

    • 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 }
      }
      

B. 前端管理 API (Base URL: /web/v1)

  1. 获取账号列表 (复杂查询)

    • 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 }
        }
      }
      
  2. 批量删除账号

    • 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 }
      }
      
  3. 批量更新账号

    • Endpoint: POST /accounts/update-batch
    • 描述: 批量修改账号的状态、所有者或备注。
    • Request Body: BatchUpdateBody
    • 成功响应 (200 OK):
      // Body: ApiResponse<{ updatedCount: number }>
      {
        "code": 0,
        "message": "Successfully updated 3 accounts.",
        "data": { "updatedCount": 3 }
      }
      
  4. 核心统计接口

    • Endpoint: GET /stats/overview
    • 描述: 一次性获取仪表盘所需的全部聚合统计数据。
    • 成功响应 (200 OK):
      // Body: ApiResponse<StatsOverview>
      {
        "code": 0,
        "message": "Success",
        "data": { /* ... StatsOverview 对象 ... */ }
      }
      

7. 关键后台任务

  • 任务名称: Stale Lock Cleanup (超时锁定清理)
  • 执行频率: 建议每 1 分钟执行一次。
  • 核心逻辑:
    • 查找所有 statuslockedlockedAt 时间早于预设阈值(例如 5 分钟前)的账号。
    • 执行 SQL: UPDATE accounts SET status = 'available', lockedAt = NULL WHERE status = 'locked' AND lockedAt < NOW() - INTERVAL '5 minutes';
  • 目的: 自动释放因脚本异常中断而未能解锁的账号,保证账号资源的流转性。