feat: 后端搜索接口开发
This commit is contained in:
+179
@@ -0,0 +1,179 @@
|
||||
# ---
|
||||
|
||||
**百盘搜 \- 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)的集成。
|
||||
Reference in New Issue
Block a user