Files
bps/doc/需求文档.md
T
2025-05-11 09:41:48 +08:00

179 lines
12 KiB
Markdown
Raw 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.
# ---
**百盘搜 \- V1.0 需求文档**
**最后更新时间:** 2025年5月6日
## **1\. 项目概述**
### **1.1. 目标**
构建一个Web应用程序,主要目标是聚合来自特定外部源的网盘资源搜索结果,提供统一的搜索界面。用户可以根据网盘类型筛选结果,并能为选定的资源触发一个“获取链接”操作,该操作通过后端管理的网盘账号进行文件转存,并生成一个有时效性的临时分享链接供用户使用。
### **1.2. 目标用户**
面向普通互联网用户,无需注册或登录即可使用核心搜索和获取链接功能。
### **1.3. 初始范围**
* **支持网盘:** 百度网盘 (baidu), 夸克网盘 (quark), UC网盘 (uc), 迅雷网盘 (xunlei)。搜索结果需能按这些类型筛选。
* **核心功能:**
* 关键词搜索。
* 按网盘类型筛选。
* 获取临时分享链接(通过后端转存)。
* **后台:** 实现必要的API、缓存、数据库记录和日志。
## **2\. 技术栈**
* **前端:** Nuxt.js (使用TypeScript)
* **后端:** Express.js (使用TypeScript)
* **数据库:** MySQL 8.0 (ORM: Prisma)
* **缓存:** Redis
## **3\. 架构概述**
* **前端:** 负责用户界面展示、接收用户输入、向后端请求数据、存储从后端获取的完整搜索结果列表、在本地实现所有筛选和分页逻辑。
* **后端:**
* 提供RESTful API供前端调用。
* 与唯一的外部搜索API (http://192.168.1.99:8008/api/search) 交互。
* 处理和缓存搜索结果。
* 管理系统自有的网盘账号(每种类型一个)。
* 实现核心的“转存与分享”逻辑(DriveAdapter模式)。
* 执行资源清理的后台任务。
* 记录详细的操作日志到数据库。
* **Drive Adapters:** 后端采用适配器模式,为每种支持的网盘(Baidu, Quark, UC, Xunlei)实现统一接口,封装其特定的API调用逻辑(如转存、生成分享链接、删除文件)。
## **4\. 功能需求 \- 前端**
### **4.1. 搜索页面 (/)**
* **界面元素:**
* 顶部包含一个关键词输入框和搜索按钮。
* 提供按**网盘类型**(百度、夸克、UC、迅雷)筛选的控件(如按钮组或复选框)。
* 搜索结果列表区域。
* 分页控件。
* **结果展示:**
* 搜索结果以列表形式展示。
* 每项结果至少清晰显示:
* **标题/文件名 (displayTitle)**
* **来源网盘 (driveType)**: 可以用文字或小图标表示。
* **发布日期 (publishDate)**
* 每项结果旁边提供一个 **“获取链接”** 按钮。
* **交互逻辑:**
* 用户输入关键词并点击搜索,前端调用后端 GET /api/search 接口。
* 前端接收并**存储全部**搜索结果数据。
* 用户点击网盘类型筛选控件,前端**在本地**对已存储的数据进行过滤显示。
* 用户使用分页控件,前端**在本地**计算并显示对应页码的数据。
* 用户点击某项结果的“获取链接”按钮,前端调用后端 POST /api/generate-link 接口,并传递该项对应的 itemId。前端需要处理等待状态,并在获取到链接或错误码后给用户反馈。
## **5\. 功能需求 \- 后端 API**
### **5.1. GET /api/search**
* **输入:** keyword (Query Parameter, String, Required)
* **外部API认证配置:**
* 后端应用需要安全地配置用于登录外部搜索接口 (http://192.168.1.99:8008/api/user/login) 的 username 和 password。这些凭证**绝不能**硬编码,应通过环境变量或加密配置文件提供。
* 后端需要一个机制来存储和管理从外部登录接口获取的当前有效的 token (例如,存储在内存变量中,并考虑持久化到Redis以支持多实例部署和重启后恢复,或者每次应用启动时重新登录获取)。
* **处理流程:**
1. **确保外部API Token有效:** a. 检查当前是否已持有有效的 token。可以通过检查 token 的过期时间(如果JWT token中包含exp声明)或通过一个简单的状态标记来判断。 b. **如果 token 不存在、无效或即将过期:** i. 调用外部登录接口 POST http://192.168.1.99:8008/api/user/login,请求体包含配置好的 username 和 password。 ii. 如果登录成功,获取返回的新 token,并更新后端持有的当前 token 及其过期信息。记录登录成功日志。 iii.如果登录失败,记录严重错误日志(例如,无法连接外部认证服务,凭证错误),并向上游(即我们的前端)返回一个特定的错误码,表明搜索服务暂时不可用。
2. **执行搜索(结合缓存逻辑):** a. 构建Redis缓存Key (例如: search:${keyword})。尝试从Redis获取缓存。若命中,直接返回缓存数据(包含itemId的列表)。 b. 若未命中缓存: i. **调用外部搜索API (携带Token):** GET http://192.168.1.99:8008/api/search?keyword={keyword} 在请求头中添加 Authorization: Bearer \<当前有效的token\>。 ii. **处理Token失效(重试机制):** 如果调用外部搜索API返回401或403错误(表示Token无效或过期): 1\. 立即执行上述**步骤 1.b (重新登录获取新Token)**。 2\. 如果成功获取新Token,则**重试一次**步骤 2.b.i (调用外部搜索API)。 3\. 如果重试仍然失败,或重新登录失败,则记录严重错误日志,并向上游返回错误。 iii.处理外部API的响应数据。对于返回的每个资源项: 1\. 生成一个全局唯一的 itemId (例如: UUID)。 2\. **数据提取与格式化:** 确保包含 driveType (统一格式), publishDate (ISO 8601), displayTitle, 以及后续“获取链接”所需的原始信息(如 cloudLinks, 原始 messageId)。 3\. 将该项的**完整处理后信息**存入Redis,Key为 itemId,**TTL设置为 1-2 天**。 iv. 记录本次搜索操作(成功或因外部API问题失败)到 ActivityLog 表(包含关键词、来源IP等)。 v. 如果外部API调用成功,将包含所有处理后结果项(至少含 itemId, driveType, publishDate, displayTitle)的**完整列表**存入Redis,Key为 search:${keyword},**TTL建议设置为 5 分钟**。 vi. 将此完整列表返回给前端(或在发生不可恢复错误时返回错误响应)。
* **输出:** \[{ itemId, displayTitle, driveType, publishDate, ... }, ...\] 或错误响应。
* **并发管理 (建议):** 当多个并发请求发现token失效时,应有机制避免同时多次调用外部登录接口(例如,使用一个简单的锁或请求队列来管理token的更新过程)。
### **5.2. POST /api/generate-link**
* **输入:** JSON Body { "itemId": "..." } (String, Required)
* **处理流程 (同步执行):**
1. 记录操作开始 (GET\_LINK\_START) 到 ActivityLog(包含 itemId, IP等)。
2. 使用 itemId 从Redis获取资源的完整信息。若获取失败(Key不存在或过期),返回错误码。
3. 从资源信息中确定所需的 driveType。
4. 从数据库 Account 表中查找该 driveType 对应的**唯一**管理账号。若找不到或账号状态无效,记录错误并返回错误码。
5. 根据 driveType 获取相应的 DriveAdapter 实例。
6. 调用 adapter.transferAndShare(sourceLink, sourceLinkType, managedAccount) 方法执行核心逻辑。
7. **若成功:** a. transferAndShare 方法应返回生成的 tempLink 和用于后续删除的 transferredFileId。 b. 在 TranscodeRecord 表中记录或更新本次操作的状态为 'success',并保存 tempLink, transferredFileId, 完成时间等信息。 c. 记录操作成功 (GET\_LINK\_SUCCESS) 到 ActivityLog。 d. 向前端返回 { "success": true, "tempLink": "..." }。
8. **若失败 (在任何步骤):** a. 在 TranscodeRecord 表中记录或更新本次操作的状态为 'failed',并保存错误信息。 b. 记录操作失败 (GET\_LINK\_FAILURE) 到 ActivityLog,包含返回给前端的错误码和详细的内部错误信息。 c. 向前端返回 { "success": false, "errorCode": "SOME\_ERROR\_CODE" } (错误码应预定义,不暴露内部细节)。
* **输出:** 成功时返回包含临时链接的JSON,失败时返回包含错误码的JSON。
## **6\. 功能需求 \- 后台任务**
### **6.1. 资源清理任务**
* **触发:** 定时执行(例如: 使用 node-cron 或类似库,每5分钟执行一次)。
* **逻辑:**
1. 查询 TranscodeRecord 表,筛选条件为:status \== 'success', cleanupStatus \== 'pending', 且 transferEndTime 早于当前时间减去约10分钟。
2. 对每个符合条件的记录: a. 获取 transferredFileId, driveType, managedAccountId。 b. 获取对应的 Account 信息和 DriveAdapter 实例。 c. 调用 adapter.delete(transferredFileId, managedAccount) 方法删除网盘中的文件。 d. **若删除成功:** 更新 TranscodeRecord 的 cleanupStatus 为 'done'。记录清理成功 (CLEANUP\_SUCCESS) 到 ActivityLog。 e. **若删除失败:** 更新 TranscodeRecord 的 cleanupStatus 为 'failed'。记录清理失败 (CLEANUP\_FAILURE) 到 ActivityLog,包含错误信息。
## **7\. 数据模型 (Database \- MySQL/Prisma)**
### **7.1. Account 表 (管理系统自有的网盘账号)**
* id: Int @id @default(autoincrement())
* driveType: String @unique (例如: 'baidu', 'quark', 'uc', 'xunlei')
* accountIdentifier: String (用于标识账号, 如用户名)
* credentials: String @db.Text (存储加密后的JSON字符串,包含API Key/Secret, Cookie等)
* status: String @default("active") (例如: 'active', 'inactive', 'error')
* lastChecked: DateTime?
* createdAt: DateTime @default(now())
* updatedAt: DateTime @updatedAt
### **7.2. TranscodeRecord 表 (跟踪转存和清理状态)**
* id: Int @id @default(autoincrement())
* itemId: String @index (关联的资源项ID)
* sourceLink: String @db.Text
* driveType: String
* managedAccountId: Int (外键关联 Account.id)
* status: String @default("pending") ('pending', 'success', 'failed')
* tempLink: String? @db.Text
* transferredFileId: String? (由Adapter返回,用于删除)
* transferStartTime: DateTime @default(now())
* transferEndTime: DateTime?
* errorCode: String?
* errorMessage: String? @db.Text
* cleanupStatus: String @default("pending") ('pending', 'done', 'failed') @index
* createdAt: DateTime @default(now())
* updatedAt: DateTime @updatedAt
* account: Account @relation(fields: \[managedAccountId\], references: \[id\])
### **7.3. ActivityLog 表 (操作审计日志)**
* id: BigInt @id @default(autoincrement())
* timestamp: DateTime @default(now())
* operationType: String ('SEARCH', 'GET\_LINK\_START', 'GET\_LINK\_SUCCESS', 'GET\_LINK\_FAILURE', 'CLEANUP\_SUCCESS', 'CLEANUP\_FAILURE')
* clientIp: String?
* keyword: String?
* itemId: String? @index
* driveType: String?
* managedAccountId: Int? (关联 Account.id)
* generatedLink: String? @db.Text
* errorCode: String?
* errorMessage: String? @db.Text
* durationMs: Int?
* details: String? @db.Text (存储额外JSON格式的上下文信息)
## **8\. 非功能性需求**
* **缓存:**
* 搜索结果列表缓存 (search:{keyword} \-\> Item List): Redis, TTL \~5分钟。
* 单个项目详细信息缓存 (itemId \-\> Full Item Data): Redis, TTL 1-2天。
* **安全:**
* Account 表中的 credentials 字段必须加密存储 (例如: 使用Node.js的 crypto 模块进行 AES 加密,密钥需妥善管理)。
* 后端API需实施速率限制 (例如: 使用 express-rate-limit 中间件,限制 IP 频率,特别是 POST /api/generate-link)。
* 记录请求来源IP地址到 ActivityLog。
* **日志:**
* 后端应使用成熟的日志库 (如 Winston, pino) 进行结构化日志记录。
* 关键操作、错误均需记录到 ActivityLog 数据库表。
* **错误处理:**
* 后端API在发生内部错误时,应返回预定义的、不暴露细节的错误码给前端。
* 详细的错误信息和堆栈应记录在后端日志和 ActivityLog 的 errorMessage 字段中。
* 前端根据错误码显示统一的、用户友好的错误提示。
## **9\. V1.0 版本范围之外**
* 管理后台界面及相关API (/api/stats, /api/accounts)。
* 用户注册、登录系统。
* 除“网盘类型”外的其他高级搜索筛选(文件类型、大小、时间等)。
* 搜索结果中的文件列表预览功能。
* 异步处理“获取链接”请求。
* 支持每种网盘类型配置多个管理账号及选择策略。
* 与日志聚合系统(如Loki)的集成。