feat: 部署初版测试
Some checks failed
Extension Build & Release / build (push) Failing after 1m5s
Backend Deploy (Go + Docker) / deploy (push) Failing after 1m40s
Web Console Deploy (Vue 3 + Vite) / deploy (push) Has been cancelled

This commit is contained in:
zs
2026-03-02 21:25:21 +08:00
parent db3abb3174
commit 8cf6cb944b
97 changed files with 10250 additions and 209 deletions

554
docs/IMPLEMENTATION_PLAN.md Normal file
View File

@@ -0,0 +1,554 @@
# InsightReply 实施计划 (Implementation Plan)
> 本文档记录了 InsightReply 各模块的具体技术实现方案,是 `DEVELOPMENT_PLAN.md`(做什么)的落地补充(怎么做)。
> 功能完善度的产品策略分析见 → [`PRODUCT_ROADMAP.md`](./PRODUCT_ROADMAP.md)
---
## 一、数据抓取策略 (Data Acquisition Strategy)
InsightReply 采用 **"前端主动感知 + 后端规则引擎"** 的双模式来实现数据抓取:
### 1. 插件端交互式抓取 (User-Driven)
* **触发场景**:用户在 X (Twitter) 上浏览推文时,想针对某条推文生成评论。
* **实现逻辑**Content Script 直接从浏览器 DOM 中提取当前选定推文的作者、正文、互动数 (Like/Reply)。
* **优点**:零延迟,无需后端预先抓取该推文,即看即得。
### 2. 后端雷达监控抓取 (System-Driven / Radar)
* **方案选型对比**
| 方案 | 代表 | 优点 | 缺点 | 月费 |
|------|------|------|------|------|
| 付费 SaaS | Apify / RapidAPI | 即插即用,免维护 | 按次计费 | $30-$100+ |
| 自建开源 | **Nitter + Camoufox** | 几乎免费 | 技术门槛高 | 仅服务器费 |
* **最终选型**:自建 Nitter 开源方案。
* **Nitter 实例地址**`https://x.beenglish.eu.org/`
---
## 二、浏览器插件技术实现 (Extension Architecture)
### 1. Content Script 与 UI 注入
* **Content Script (`src/content/index.ts`)**:监听页面 URL 变化,使用 `MutationObserver` 实时检测新加载的推文 DOM 节点。
* **Shadow DOM 注入**:在推文操作栏注入"Insight"入口按钮。点击后展示主面板,面板挂载在 Shadow DOM 内,确保与 Twitter 原生样式完全隔离。
* **Background Service Worker**:作为中转站,处理与后端 Go API 的通信和认证。
### 2. UI 设计规范 (ui-ux-pro-max)
* **暗黑毛玻璃风格**:主色调 `#0A0A0A` ~ `#171717`,浮动面板使用 `backdrop-filter: blur()`
* **品牌渐变色**:紫蓝渐变 `#8B5CF6``#3B82F6`,用于 CTA 按钮和高亮标签。
* **字体**:优先使用 `Inter` / `Geist` / `SF Pro` 等现代无衬线字体。
* **微动画**:所有状态切换配 `ease-in-out` 过渡(`200ms-300ms`)。
### 3. 技术栈规范
* **Tailwind CSS v4**:所有间距、颜色从 Design Tokens 读取,禁止硬编码内联样式。
* **Vue 3 Composition API**:高频 UI 元素(`Button`, `Card`, `Badge`)封装为基础组件。
* **`cn()` 工具函数**:基于 `clsx` + `tailwind-merge` 统一处理动态 Class 冲突。
* **轻量化打包**:禁止引入完整组件库,采用按需加载。
### 4. Content Script 性能优化策略 *(新增)*
> 在 Twitter 重型 SPA 中,粗暴监听全文档 DOM 变更会产生严重性能问题。
* **收缩监听范围**:将 `MutationObserver``observe` 目标从 `document.body` 收缩到 `main[role="main"]` 或 Timeline 容器。
* **防抖扫描**`scanTweets()` 加入 `requestIdleCallback``debounce(200ms)`,避免高频回调。
* **已处理标记**:通过 `WeakSet<HTMLElement>` 记录已注入按钮的推文节点,避免重复扫描。
* **多选择器 Fallback**Twitter 的 `data-testid` 属性随时可能变更,已处理推文选择器应支持多层 Fallback
```typescript
const TWEET_SELECTORS = [
'article[data-testid="tweet"]',
'article[role="article"]',
'div[data-testid="cellInnerDiv"] article'
];
```
---
## 三、高可用后台抓取系统 (Industry Radar - Enterprise Architecture)
考虑到系统的**稳定性、安全性和反风控 (Anti-Ban)**,自建 Nitter 之上的抓取系统设计为一套健壮的企业级爬虫架构。
### 1. 终极反风控与防封禁策略 (Anti-Ban Mechanisms)
| 机制 | 说明 |
|------|------|
| **请求抖动 (Jitter)** | 每次请求间加入 `1s-5s` 强随机延迟,严禁整点并发 |
| **熔断降级 (Circuit Breaker)** | Nitter 连续返回 429/503 时自动暂停 30 分钟 |
| **Fallback 节点池** | 主节点熔断时自动切换到备用 Nitter 实例 |
| **指纹轮换 (Fingerprint Rotation)** | 同步轮换 UA + `Sec-Ch-Ua` + `Accept-Language` 等全套 Header |
| **会话隔离 (Session Isolation)** | 每个采集任务独立 Cookie Jar避免跨任务身份串联 |
### 2. 高可用采集架构 (Scraper & Queue)
* **异步任务队列**:基于 Redis 的 `Asynq`,解耦"触发"与"执行",控制最大并发数。
* **指数退避重试 (Exponential Backoff)**:失败后按 1min → 3min → 10min 递增重试间隔。
* **采集器健壮性**
* `goquery` 解析找不到元素时记录 Warn返回部分数据**绝不 Panic**。
* 关键报错时 **Dump HTML 快照**到日志表,方便排查 Nitter DOM 结构变更。
### 3. 数据计算与存储层
* **批量 Upsert**:使用 `ON CONFLICT DO UPDATE` 策略,降低 IO 压力,防止死锁。
* **热度公式**
```
heat_score = (Like增量 × 0.4) + (RT增量 × 0.3) + (Reply增量 × 0.3)
```
* **动态智能抓取频率 (Smart Crawling)**
* 🔥 热度飙升的推文 → **高频队列**(每 15 分钟抓一次)
* 🧊 24 小时无新动态 → **低频队列**(每 4 小时抓一次)
* 极大节省抓取资源,显著降低被封概率。
---
## 四、后端安全与认证体系 *(新增)*
### 1. JWT 认证方案
所有 `/api/v1` 路由(除 `/auth/*` 外)必须经过 JWT 认证中间件。
* **技术选型**`go-chi/jwtauth` + HMAC-SHA256 签名。
* **Token 结构**
```json
{
"sub": "user-uuid",
"tier": "Pro",
"exp": 1709164800
}
```
* **刷新策略**Access Token 有效期 24hRefresh Token 有效期 30d存于 `chrome.storage.local`。
### 2. Rate Limiting分级限流
| Tier | 评论生成 | 监控关键词 | 监控账号 |
|------|---------|-----------|---------|
| Free | 10 次/天 | 3 个 | 3 个 |
| Pro | 无限 | 20 个 | 20 个 |
| Premium | 无限 | 50 个 | 50 个 |
* **实现方案**:基于 Redis 的滑动窗口计数器Key 格式:`ratelimit:{user_id}:{action}:{window}`。
* **中间件挂载**:在 chi Router 中通过 `r.Use(RateLimitMiddleware)` 全局或分路由挂载。
### 3. CORS 配置
```go
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"chrome-extension://*", "https://x.com", "https://twitter.com"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Authorization", "Content-Type"},
AllowCredentials: true,
MaxAge: 300,
}))
```
---
## 五、LLM 服务韧性设计 *(新增)*
### 1. 调用链路防护
```
请求 → Timeout Guard (30s) → Circuit Breaker → Retry (max 2) → OpenAI API
```
* **Timeout**`context.WithTimeout(ctx, 30*time.Second)` 包裹每次 LLM 调用。
* **Circuit Breaker**:使用 `sony/gobreaker`,连续 5 次失败后半开,每 60s 试探一次恢复。
* **指数退避重试**:仅对 5xx / Timeout 重试4xx如 quota exceeded立即失败。
### 2. 多模型动态路由与 Fallback
系统重构为 **多 Provider 支持 (OpenAI / Anthropic / DeepSeek / Gemini)**,以应对不同用户的专属模型偏好和单一 Provider 宕机时的容灾。同时支持**代理接口与自定义模型**。
**路由与配置设计**
1. **环境变量驱动 (ENV)**:管理员可通过配置 `OPENAI_BASE_URL` 改写接口地址(完美兼容 Groq、vLLM、Ollama 等任意 OpenAI 兼容代理)。
2. **可用模型下发**:通过 `OPENAI_AVAILABLE_MODELS="gpt-4o,gpt-4o-mini"` 配置白名单,后端通过 `GET /sys/config/llms` 统一向前端下发。
3. **前端交互 (`default_llm_model`)**:前端展示为**「可输入的下拉列表 (Combobox)」**,用户既可从系统下发的列表中点选,也支持手动输入任意字符串,实现完全自定义。
4. **故障转移 (Fallback)**:当前尝试的 Provider 返回 5xx 时,按兜底顺序切换:`Anthropic → OpenAI → Gemini → DeepSeek`。
**统一适配器抽象**
建立统一的 `LLMProvider` 接口,抹平各平台 API 结构差异,内部集成对应 SDK
- OpenAI (含兼容代理): `sashabaranov/go-openai` (支持通过 Builder 传入 BaseURL)
- Anthropic: 直接构造 REST API 请求
- DeepSeek: 兼容 OpenAI SDK基于 `DEEPSEEK_BASE_URL` 重连
- Gemini: `google.golang.org/api/generativelanguage/v1beta`
### 3. Token 成本审计
每次 LLM 调用记录 `prompt_tokens + completion_tokens`,写入 `api_usage_logs` 表:
```sql
INSERT INTO api_usage_logs (user_id, provider, model, prompt_tokens, completion_tokens, cost_usd)
VALUES ($1, $2, $3, $4, $5, $6);
```
---
## 六、Prompt 工程规范 *(新增)*
### 1. 策略模板结构
每个评论策略维护独立的 Prompt 模板文件,存放于 `server/prompts/` 目录:
```
server/prompts/
├── system.txt # 全局 System Prompt
├── cognitive_upgrade.txt # 认知升级型
├── contrarian.txt # 反向观点型
├── data_supplement.txt # 数据补充型
├── empathy.txt # 共鸣型
└── founder_exp.txt # 创始人经验型
```
### 2. Prompt 结构公式 (必须遵守)
```
[System Prompt]
- 角色定义:专业社交媒体表达顾问
- 输出约束:字数、语言、格式
[User Prompt]
- 用户身份标签:{identity}
- 目标推文:{tweet_content}
- 策略指令:{strategy_specific_instruction}
- 结构公式Hook + Position + Insight + Brevity
- Few-shot 示例2-3 条高质量参考
[Constraints]
- 不超过 280 字符 (或用户指定长度)
- 语言:与原推文一致,或用户指定
- 语气与身份匹配
- 禁止引号包裹
```
### 3. 批量生成规范
API 单次返回 5 种策略 × 2 条备选 = **10 条候选评论**
* 方案 A单次 LLM 调用Prompt 中要求 JSON 数组输出
* 方案 B5 组并发调用,每组 n=2
* **推荐方案 A**,减少 API 调用次数
---
## 七、后端 Go 接口规范
* 采用 RESTful 标准。
* 所有接口返回统一 JSON 结构:
```json
{
"code": 200,
"message": "success",
"data": {}
}
```
* 错误码体系:`4xxx` 为客户端错误,`5xxx` 为服务端错误。
* 详细接口文档见 → [`docs/API.md`](./API.md)
---
## 八、环境变量与配置管理 *(新增)*
所有运行时配置统一通过环境变量注入,**禁止硬编码**。
| 变量名 | 说明 | 示例 |
|-------|------|------|
| `DATABASE_URL` | PostgreSQL 连接串 | `postgres://user:pass@host:5432/db` |
| `OPENAI_API_KEY` | OpenAI API Key | `sk-...` |
| `JWT_SECRET` | JWT 签名密钥 | 随机 32+ 字符串 |
| `SERVER_PORT` | 服务端口 | `8080` |
| `CORS_ORIGINS` | 允许的跨域来源 | `chrome-extension://*` |
| `LOG_LEVEL` | 日志级别 | `info` / `debug` / `warn` |
| `LLM_TIMEOUT_SEC` | LLM 调用超时秒数 | `30` |
模板文件 → [`server/.env.example`](../server/.env.example)
---
## 九、验证计划 (Verification Plan)
| # | 验证项 | 方法 |
|---|--------|------|
| 1 | 防封控链路 | 模拟 100 并发请求,验证熔断 + Jitter 能正确拦截 |
| 2 | HTML 解析容错 | 模拟账号冻结/推文被删等异常页面,确认不会 Panic |
| 3 | Shadow DOM 隔离 | 在 X 深色/浅色模式下验证插件样式不被污染 |
| 4 | 端到端生成链路 | 插件点击 → Background → Go API → LLM → 返回结果 |
| 5 | JWT 认证 *(新增)* | 无 Token / 过期 Token / 非法 Token 拒绝访问 |
| 6 | Rate Limiting *(新增)* | Free 用户超 10 次/天后返回 429 |
| 7 | LLM 韧性 *(新增)* | 模拟 OpenAI 超时 / 5xx验证熔断降级 + 重试 |
| 8 | Graceful Shutdown *(新增)* | 发送 SIGTERM验证在途请求完成后才退出 |
| 9 | Onboarding 流程 *(新增)* | 首次打开插件触发引导,设置存入 chrome.storage + 后端 |
| 10 | 多条备选评论 *(新增)* | 验证返回 3 条备选Sidebar 卡片式展示正确 |
| 11 | 热度标签 *(新增)* | 在高互动推文旁正确显示 🔥/⚡ 标签 |
| 12 | 效果追踪闭环 *(新增)* | 复制评论 → 24h 后回查 → 互动数据写入 DB |
---
## 十、用户 Onboarding 实现方案 *(新增)*
### 1. 流程设计
首次安装/首次打开插件时,检测 `chrome.storage.sync` 中是否存在 `onboarding_completed` 标志。若无,显示 3 步引导:
```typescript
// background/index.ts
chrome.runtime.onInstalled.addListener(({ reason }) => {
if (reason === 'install') {
chrome.storage.sync.set({ onboarding_completed: false });
}
});
```
### 2. 数据存储
| 字段 | 存储位置 | 说明 |
|------|---------|------|
| `identity_label` | `chrome.storage.sync` + `users` 表 | 身份标签 |
| `language_preference` | `chrome.storage.sync` + `users` 表 | 偏好语言 |
| `tone_preference` | `chrome.storage.sync` + `user_style_profiles` 表 | 风格倾向 |
| `onboarding_completed` | `chrome.storage.sync` | 是否已完成引导 |
### 3. 与生成链路的集成
用户设置完成后,每次生成评论时自动读取 `chrome.storage.sync` 中的偏好,附加到 `GENERATE_REPLY` 消息中:
```typescript
// 改造 Sidebar.vue 的 generate()
const prefs = await chrome.storage.sync.get(['identity_label', 'language_preference', 'tone_preference']);
chrome.runtime.sendMessage({
type: 'GENERATE_REPLY',
payload: {
tweetContent: props.tweetData.content,
strategy: 'all',
identity: prefs.identity_label,
language: prefs.language_preference,
tone: prefs.tone_preference
}
});
```
---
## 十一、前端热度标签实现方案 *(新增)*
### 1. 判断逻辑
```typescript
interface HeatLevel {
label: string;
emoji: string;
className: string;
}
const getHeatLevel = (likes: number, minutesAgo: number): HeatLevel | null => {
if (likes > 1000 && minutesAgo < 120) {
return { label: 'Trending', emoji: '🔥', className: 'heat-trending' };
}
if (likes > 100 && minutesAgo < 60) {
return { label: 'Rising', emoji: '⚡', className: 'heat-rising' };
}
return null;
};
```
### 2. 注入位置
在 `injectInsightButton()` 函数中,紧挨 Insight 按钮旁边注入热度标签:
* 🔥 Trending红色渐变 Badge (`background: linear-gradient(135deg, #ef4444, #f97316)`)
* ⚡ Rising黄色渐变 Badge (`background: linear-gradient(135deg, #f59e0b, #eab308)`)
### 3. 时间计算
从推文 DOM 中的 `<time datetime="...">` 元素提取发帖时间,计算与当前时间的差值:
```typescript
const timeEl = tweetElement.querySelector('time');
const postedAt = new Date(timeEl?.getAttribute('datetime') || '');
const minutesAgo = (Date.now() - postedAt.getTime()) / 60000;
```
---
## 十二、数据飞轮 — 效果追踪与风格学习 *(新增)*
### 1. 效果追踪数据流
```
用户复制评论
│ copyToClipboard() → 同时记录 hash(reply_text) 到 chrome.storage.local
chrome.storage.local:
tracked_replies: [{
hash: "a1b2c3",
text: "Most people miss...",
strategy: "contrarian",
copied_at: 1709164800,
tweet_url: "https://x.com/.../status/..."
}]
Content Script 延迟触发 (当用户访问自己 Profile 页时)
│ 条件: URL 匹配 x.com/{current_user}
扫描用户近期 Tweets/Replies
│ 对比 tracked_replies 中的 text hash
匹配成功 → 提取当前 Likes / Replies 数
chrome.runtime.sendMessage → Background → POST /api/v1/replies/{id}/performance
写入 reply_performance 表
```
### 2. 风格学习 Prompt 注入
当 `user_style_profiles` 表有足够数据(≥ 10 条追踪记录)后,生成评论时自动注入风格画像:
```
[附加上下文 - 个人风格画像]
基于你过去 30 天的高互动评论分析:
- 最有效策略: {top_strategies} (平均互动率 {avg_rate}%)
- 风格特征: {style_description}
- 高频高互动关键词: {keywords}
- 偏好评论长度: {avg_length} 字符
请参考以上风格特征生成评论。
```
### 3. 风格特征提取 (后端定时任务)
每周对每个用户的 `reply_performance` 数据运行一次 LLM 分析:
```
System: 你是社交媒体互动分析专家。
User: 以下是该用户最近 30 天互动率最高的 10 条评论:
{replies_list}
请总结其写作风格特征, 包括:
1. 最常用的句式结构
2. 高频关键词或短语
3. 语气倾向 (专业/幽默/犀利)
4. 平均有效长度
输出为 JSON 格式。
```
---
## 十三、用户可配置系统设计 *(新增)*
> **设计原则**:系统不硬编码任何特定产品/领域/策略。所有与用户场景相关的能力均通过用户自定义设置实现,确保对任意行业、任意产品类型的可扩展性。
### 1. 架构思想:零硬编码 (Zero Hardcoding)
```
传统做法 (❌):
Prompt 里写死 "你是一个 AI 创始人,正在推广 SwiftBiu..."
可扩展做法 (✅):
Prompt 运行时从 user_product_profiles 表 + user_custom_strategies 表
动态拼装上下文,适用于任何用户、任何产品
```
### 2. 产品档案 (Product Profile) 注入机制
当用户配置了产品档案后,生成评论时自动注入以下上下文块:
```
[产品上下文 — 自动注入,仅当用户已配置产品档案时]
你正在帮助一位 {identity} 撰写社交评论。
该用户正在推广的产品: {product_name} — {tagline}
产品所属领域: {domain}
核心功能: {key_features}
目标用户群: {target_users}
{custom_context}
注意:
- 不要直接提及产品名称或链接 (除非用户使用了"创始人实战型"策略)
- 评论应从 {domain} 领域专家的角度出发,让读者产生"这个人很懂行"的印象
- 引发好奇心 → 点击 Profile → 发现产品,而非硬推
```
### 3. 自定义策略运行时
用户自定义的策略模板支持以下**模板变量**,运行时自动替换:
| 变量 | 来源 | 说明 |
|------|------|------|
| `{tweet_content}` | 当次请求 | 目标推文原文 |
| `{identity}` | 用户设置 | 身份标签 |
| `{product_name}` | 产品档案 | 产品名称 |
| `{domain}` | 产品档案 | 所属领域 |
| `{key_features}` | 产品档案 | 核心功能 |
| `{competitors}` | 产品档案 | 竞品列表 |
| `{language}` | 用户设置 | 输出语言 |
| `{max_length}` | 当次请求 | 最大字符数 |
| `{custom_context}` | 产品档案 | 自定义上下文 |
**后端处理流程**
```go
// service/ai_service.go
func (s *AIService) buildPrompt(req GenerateRequest, profile *ProductProfile, strategies []Strategy) string {
// 1. 加载 System Prompt
// 2. 如果有产品档案 → 注入产品上下文块
// 3. 合并内置策略 + 用户自定义策略
// 4. 替换所有模板变量
// 5. 如果有风格画像 (user_style_profiles) → 注入风格上下文
// 6. 返回最终 Prompt
}
```
### 4. 前端推文相关性评分
Content Script 基于产品档案中的 `relevance_keywords` 对当前浏览的推文做实时相关性判断:
```typescript
// content/relevance.ts
const scoreRelevance = (tweetText: string, keywords: string[]): number => {
const text = tweetText.toLowerCase();
let score = 0;
for (const kw of keywords) {
if (text.includes(kw.toLowerCase())) score++;
}
return score;
};
// 注入标签
const relevance = scoreRelevance(tweetData.content, userProfile.relevance_keywords);
if (relevance >= 2) {
// 注入 🎯 "High Relevance" 标签
}
```
**关键词来源**`chrome.storage.sync` 缓存用户的 `relevance_keywords`(来自产品档案 API定期同步。
### 5. 前端策略合并展示
Sidebar 策略选择器同时展示系统内置策略和用户自定义策略:
```typescript
// Sidebar.vue
const allStrategies = computed(() => [
...builtinStrategies, // 系统内置 5 种
...userCustomStrategies.value // 用户自定义 N 种 (从 API 拉取)
]);
```
自定义策略用不同背景色或 `⚙️ Custom` 标签区分,方便用户识别。
### 6. Prompt 组装顺序
```
┌─────────────────────────────────────────────┐
│ System Prompt (角色定义 + 输出约束) │
├─────────────────────────────────────────────┤
│ [可选] 产品上下文 (如果用户配置了产品档案) │
├─────────────────────────────────────────────┤
│ [可选] 个人风格画像 (如果有 ≥10 条追踪数据) │
├─────────────────────────────────────────────┤
│ User Prompt: │
│ - 身份标签 │
│ - 目标推文 │
│ - 策略指令 (内置 or 用户模板,变量已替换) │
│ - 结构公式: Hook + Position + Insight │
│ - Few-shot 示例 (系统默认 or 用户自定义) │
├─────────────────────────────────────────────┤
│ Constraints (字数/语言/格式) │
└─────────────────────────────────────────────┘
```