Files
makemd/docs/04_Plugin/Plugin_Design.md
wurenzhi 72cd7f6f45 chore: 清理归档文件和文档模板
删除不再需要的归档文件和过时的文档模板,包括多个README、安全策略、前端集成蓝图等文件,同时移除了未使用的业务文档和项目结构文件。

优化项目结构,移除冗余文件,保持代码库整洁。主要删除archive/handover目录下的多个文件及doc目录下的部分文档模板。
2026-03-18 01:21:15 +08:00

574 lines
17 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.
# Plugin Design (Crawlful Hub)
> **定位**Crawlful Hub 浏览器插件架构设计文档 - 包含技术栈、目录结构、核心功能及开发规范。
> **更新日期**: 2026-03-18
> **最高优先级参考**: [Business_ClosedLoops.md](../00_Business/Business_ClosedLoops.md)
---
## 1. 技术栈 (Tech Stack)
| 层级 | 技术 | 版本 | 用途 |
|------|------|------|------|
| **Framework** | WebExtensions API | MV3 | 浏览器扩展标准 |
| **Language** | TypeScript | 5.x | 开发语言 |
| **Build Tool** | Vite | 5.x | 构建工具 |
| **Bundler** | Rollup | 4.x | 代码打包 |
| **UI** | React + Tailwind | 18.x + 3.x | 弹窗/选项页 UI |
| **Testing** | Vitest | 1.x | 单元测试 |
---
## 2. 目录结构 (Directory Structure)
```
extension/
├─ manifest.json # 扩展清单 (MV3)
├─ src/
│ │
│ ├─ background/ # Service Worker (后台脚本)
│ │ ├─ index.ts # 入口
│ │ ├─ handlers/
│ │ │ ├─ messageHandler.ts # 消息处理
│ │ │ ├─ alarmHandler.ts # 定时任务
│ │ │ └─ commandHandler.ts # 快捷键处理
│ │ └─ services/
│ │ ├─ crawlerService.ts # 网页采集服务无API平台
│ │ ├─ syncService.ts # 同步服务
│ │ └─ authService.ts # 认证服务
│ │
│ ├─ content/ # 内容脚本 (注入页面)
│ │ ├─ index.ts # 入口
│ │ ├─ crawlers/ # 采集器
│ │ │ ├─ amazonCrawler.ts
│ │ │ ├─ ebayCrawler.ts
│ │ │ ├─ shopifyCrawler.ts
│ │ │ └─ aliexpressCrawler.ts
│ │ ├─ automation/ # 自动化操作
│ │ │ ├─ listingAutomation.ts
│ │ │ ├─ orderAutomation.ts
│ │ │ └─ adAutomation.ts
│ │ └─ utils/
│ │ ├─ domUtils.ts
│ │ ├─ selectorUtils.ts
│ │ └─ eventUtils.ts
│ │
│ ├─ popup/ # 弹窗页面
│ │ ├─ index.tsx
│ │ ├─ components/
│ │ │ ├─ QuickActions.tsx
│ │ │ ├─ StatusPanel.tsx
│ │ │ └─ RecentTasks.tsx
│ │ └─ hooks/
│ │ └─ useBackground.ts
│ │
│ ├─ options/ # 选项页面
│ │ ├─ index.tsx
│ │ ├─ components/
│ │ │ ├─ GeneralSettings.tsx
│ │ │ ├─ PlatformSettings.tsx
│ │ │ ├─ AccountSettings.tsx
│ │ │ └─ AdvancedSettings.tsx
│ │ └─ stores/
│ │ └─ settingsStore.ts
│ │
│ ├─ shared/ # 共享资源
│ │ ├─ types/
│ │ │ ├─ messaging.ts # 消息类型定义
│ │ │ ├─ crawler.ts # 采集类型
│ │ │ └─ platform.ts # 平台类型
│ │ ├─ constants/
│ │ │ ├─ platforms.ts # 平台常量
│ │ │ └─ selectors.ts # 选择器常量
│ │ └─ utils/
│ │ ├─ logger.ts
│ │ ├─ storage.ts
│ │ └─ crypto.ts
│ │
│ └─ injected/ # 注入脚本 (隔离环境)
│ ├─ index.ts
│ └─ services/
│ └─ bridgeService.ts
├─ assets/ # 静态资源
│ ├─ icons/
│ │ ├─ icon-16.png
│ │ ├─ icon-32.png
│ │ ├─ icon-48.png
│ │ └─ icon-128.png
│ └─ styles/
│ └─ global.css
├─ _locales/ # 国际化
│ ├─ en/
│ │ └─ messages.json
│ └─ zh_CN/
│ └─ messages.json
└─ dist/ # 构建输出
```
---
## 3. 架构设计 (Architecture)
### 3.1 核心组件关系
```
┌─────────────────────────────────────────────────────────────┐
│ Browser Extension │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Popup │ │ Options │ │ Content │ │
│ │ (UI) │◄───►│ (Settings) │◄───►│ (Page) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └────────────────────┼────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Service Worker │ │
│ │ (Background) │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Crawler │ │ Sync │ │ Auth │ │
│ │ Engine │ │ Engine │ │ Engine │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 3.2 通信机制
#### 消息类型 (Messaging Types)
```typescript
// src/shared/types/messaging.ts
export enum MessageType {
// 采集相关
CRAWL_PRODUCT = 'CRAWL_PRODUCT',
CRAWL_ORDER = 'CRAWL_ORDER',
CRAWL_COMPLETE = 'CRAWL_COMPLETE',
// 自动化相关
AUTO_LISTING = 'AUTO_LISTING',
AUTO_ORDER = 'AUTO_ORDER',
AUTO_AD = 'AUTO_AD',
// 同步相关
SYNC_DATA = 'SYNC_DATA',
SYNC_STATUS = 'SYNC_STATUS',
// 认证相关
AUTH_LOGIN = 'AUTH_LOGIN',
AUTH_LOGOUT = 'AUTH_LOGOUT',
AUTH_REFRESH = 'AUTH_REFRESH',
// 设置相关
GET_SETTINGS = 'GET_SETTINGS',
SET_SETTINGS = 'SET_SETTINGS',
// 任务相关
TASK_STATUS = 'TASK_STATUS',
TASK_RESULT = 'TASK_RESULT',
}
export interface MessagePayload {
type: MessageType;
data?: any;
error?: string;
traceId?: string;
}
```
#### 通信流程
```
Content Script ──► Background Service Worker ──► Backend API
│ │
│◄─────────────────────┘
Popup/Options ◄─── Chrome Storage
```
---
## 4. 核心功能模块
### 4.1 数据采集模块 (Crawler)
**功能定位**
- 从各电商平台采集商品、订单数据
- **仅处理无API平台**TikTok Shop, Temu, 部分1688页面等
- **有API平台由后端处理**Amazon MWS, eBay API, Shopee Open API
- 支持沙箱模式(测试环境)
**采集策略矩阵**
| 平台 | 类型 | 采集方式 | 登录要求 | 反爬策略 |
|------|------|----------|----------|----------|
| Amazon | 有API | ❌ 后端处理 | OAuth | API限流 |
| eBay | 有API | ❌ 后端处理 | OAuth | API限流 |
| Shopee | 有API | ❌ 后端处理 | OAuth | API限流 |
| TikTok Shop | 无API | ✅ 插件采集 | 需登录 | 指纹隔离+代理IP |
| Temu | 无API | ✅ 插件采集 | 需登录 | 指纹隔离+代理IP |
| 1688(部分) | 无API | ✅ 插件采集 | 可选 | 频率控制 |
**⚠️ 重要约束**
- **后端严禁直接爬取**IP封禁风险、法律合规问题
- 所有网页级采集必须通过插件在用户浏览器端执行
- 插件必须实现店铺隔离一店一IP一指纹
**采集器实现**
```typescript
// src/content/crawlers/amazonCrawler.ts
export class AmazonCrawler {
private selectors = {
title: '#productTitle',
price: '.a-price-whole, .a-offscreen',
images: '#landingImage, .a-dynamic-image',
description: '#feature-bullets, #productDescription',
reviews: '#acrCustomerReviewText',
};
async crawlProduct(): Promise<ProductData> {
const title = this.extractText(this.selectors.title);
const price = this.extractPrice(this.selectors.price);
const images = this.extractImages(this.selectors.images);
const description = this.extractText(this.selectors.description);
return {
platform: 'AMAZON',
title,
price,
images,
description,
url: window.location.href,
crawledAt: new Date().toISOString(),
};
}
private extractText(selector: string): string {
const element = document.querySelector(selector);
return element?.textContent?.trim() || '';
}
private extractPrice(selector: string): number {
const element = document.querySelector(selector);
const text = element?.textContent?.replace(/[^\d.]/g, '') || '0';
return parseFloat(text);
}
private extractImages(selector: string): string[] {
const elements = document.querySelectorAll(selector);
return Array.from(elements)
.map(el => el.getAttribute('src') || el.getAttribute('data-src'))
.filter(Boolean) as string[];
}
}
```
**采集流程**
```
1. 用户点击采集按钮 / 定时任务触发
2. Content Script 注入采集器
3. 采集器解析 DOM 提取数据
4. 数据发送至 Background
5. Background 发送至后端 API
6. 返回采集结果
```
### 4.2 自动化操作模块 (Automation)
**功能定位**
- 自动刊登商品
- 自动处理订单
- 自动投放广告
**自动化实现**
```typescript
// src/content/automation/listingAutomation.ts
export class ListingAutomation {
async autoListing(productData: ProductData, platform: string): Promise<boolean> {
switch (platform) {
case 'AMAZON':
return this.listOnAmazon(productData);
case 'EBAY':
return this.listOnEbay(productData);
case 'SHOPIFY':
return this.listOnShopify(productData);
default:
throw new Error(`Unsupported platform: ${platform}`);
}
}
private async listOnAmazon(product: ProductData): Promise<boolean> {
// 1. 导航到刊登页面
await this.navigateTo('/inventory/add');
// 2. 填写商品信息
await this.fillInput('#title', product.title);
await this.fillInput('#price', product.price.toString());
await this.fillTextarea('#description', product.description);
// 3. 上传图片
for (const imageUrl of product.images) {
await this.uploadImage(imageUrl);
}
// 4. 提交刊登
await this.click('#submit-button');
// 5. 等待结果
return this.waitForSuccess();
}
private async navigateTo(path: string): Promise<void> {
window.location.href = `https://sellercentral.amazon.com${path}`;
await this.waitForElement('#title', 10000);
}
private async fillInput(selector: string, value: string): Promise<void> {
const input = document.querySelector(selector) as HTMLInputElement;
if (input) {
input.value = value;
input.dispatchEvent(new Event('input', { bubbles: true }));
}
}
private async waitForElement(selector: string, timeout: number): Promise<void> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const check = () => {
if (document.querySelector(selector)) {
resolve();
} else if (Date.now() - startTime > timeout) {
reject(new Error('Timeout waiting for element'));
} else {
setTimeout(check, 100);
}
};
check();
});
}
}
```
### 4.3 数据同步模块 (Sync)
**功能定位**
- 定时同步订单数据
- 同步库存状态
- 同步广告数据
**同步配置**
```typescript
// src/background/services/syncService.ts
export class SyncService {
private syncIntervals: Record<string, number> = {
orders: 5 * 60 * 1000, // 5分钟
inventory: 10 * 60 * 1000, // 10分钟
ads: 30 * 60 * 1000, // 30分钟
};
async startSync(): Promise<void> {
// 创建定时任务
chrome.alarms.create('syncOrders', { periodInMinutes: 5 });
chrome.alarms.create('syncInventory', { periodInMinutes: 10 });
chrome.alarms.create('syncAds', { periodInMinutes: 30 });
}
async handleAlarm(alarmName: string): Promise<void> {
switch (alarmName) {
case 'syncOrders':
await this.syncOrders();
break;
case 'syncInventory':
await this.syncInventory();
break;
case 'syncAds':
await this.syncAds();
break;
}
}
private async syncOrders(): Promise<void> {
const platforms = await this.getEnabledPlatforms();
for (const platform of platforms) {
try {
const orders = await this.crawlOrders(platform);
await this.sendToBackend('/api/v1/orders/sync', orders);
} catch (error) {
console.error(`Failed to sync orders for ${platform}:`, error);
}
}
}
}
```
---
## 5. 安全与隐私
### 5.1 数据存储
```typescript
// src/shared/utils/storage.ts
export class SecureStorage {
// 存储敏感数据(加密)
static async setSecure(key: string, value: string): Promise<void> {
const encrypted = await this.encrypt(value);
await chrome.storage.local.set({ [key]: encrypted });
}
// 读取敏感数据(解密)
static async getSecure(key: string): Promise<string | null> {
const result = await chrome.storage.local.get(key);
if (result[key]) {
return this.decrypt(result[key]);
}
return null;
}
// 存储普通数据
static async set(key: string, value: any): Promise<void> {
await chrome.storage.local.set({ [key]: value });
}
// 读取普通数据
static async get(key: string): Promise<any> {
const result = await chrome.storage.local.get(key);
return result[key];
}
private static async encrypt(text: string): Promise<string> {
// 使用 Chrome 的加密 API
// 实际实现需要更复杂的加密逻辑
return btoa(text);
}
private static async decrypt(encrypted: string): Promise<string> {
return atob(encrypted);
}
}
```
### 5.2 权限控制
```json
// manifest.json
{
"manifest_version": 3,
"name": "Crawlful Hub",
"version": "1.0.0",
"permissions": [
"storage",
"alarms",
"activeTab",
"scripting"
],
"host_permissions": [
"https://sellercentral.amazon.com/*",
"https://www.ebay.com/*",
"https://*.myshopify.com/*",
"https://*.tiktok.com/*"
],
"background": {
"service_worker": "src/background/index.ts"
},
"content_scripts": [
{
"matches": [
"https://sellercentral.amazon.com/*",
"https://www.ebay.com/*"
],
"js": ["src/content/index.ts"]
}
],
"action": {
"default_popup": "src/popup/index.html"
},
"options_page": "src/options/index.html"
}
```
---
## 6. 开发规范
### 6.1 代码规范
- 使用 TypeScript 严格模式
- 使用函数式组件 + Hooks
- 避免使用 `any` 类型
- 所有消息类型必须在 `messaging.ts` 中定义
### 6.2 测试规范
```typescript
// __tests__/crawler.test.ts
import { AmazonCrawler } from '../src/content/crawlers/amazonCrawler';
describe('AmazonCrawler', () => {
let crawler: AmazonCrawler;
beforeEach(() => {
crawler = new AmazonCrawler();
});
test('should extract product title', async () => {
// Mock DOM
document.body.innerHTML = `
<span id="productTitle">Test Product</span>
`;
const product = await crawler.crawlProduct();
expect(product.title).toBe('Test Product');
});
});
```
### 6.3 构建与发布
```bash
# 开发模式
npm run dev
# 生产构建
npm run build
# 打包扩展
npm run package
# 运行测试
npm run test
```
---
## 7. 相关文档
- [DOM Interaction](./DOM_Interaction.md)
- [Automation Scripts](./Automation_Scripts.md)
- [Backend Design](../02_Backend/Backend_Design.md)
- [Business ClosedLoops](../00_Business/Business_ClosedLoops.md)
---
*本文档基于业务闭环设计,最后更新: 2026-03-18*