一个基于 V2EX RSS 的个人兴趣订阅后端。持续抓取帖子、维护用户兴趣画像,通过规则或 LLM 语义筛选相关内容,并通过 Telegram Bot 实时推送。
- 全站 RSS 扫描:并发抓取 V2EX ~1200 个节点的 Atom Feed,聚合去重
- 兴趣画像:用自然语言更新用户偏好关键词、偏好节点、屏蔽词等,持久化至 SQLite
- 双模式匹配:本地规则匹配(快,无需 API)或 LLM 语义匹配(更准确),支持自动降级
- 基于画像的节点筛选:LLM 根据用户兴趣从节点目录中筛选 20~80 个相关节点,每个用户只扫自己关心的节点,大幅降低无效抓取
- Telegram Bot:对话式更新画像、查询命中帖子、自动周期推送、帖子回复监控
- SSE 实时推流:Dashboard 通过 Server-Sent Events 实时接收概览和 Feed 数据
- 代理支持:接入 Clash/Mihomo 代理,支持 403 时自动切换代理节点;内置 Shadowsocks 模式
- Web Dashboard:可视化兴趣画像、命中结果、节点选择列表、推送状态
- Rust 1.85+(edition 2024)
- Windows / macOS / Linux
# 启动 Rust 后端(API、Telegram Bot、自动推送 worker 在同一进程)
powershell -ExecutionPolicy Bypass -File .\start_all.ps1或直接启动:
powershell -ExecutionPolicy Bypass -File .\start_api.ps1服务默认监听 http://127.0.0.1:8000,Dashboard 在 http://127.0.0.1:8000/dashboard。
项目根目录放 .env 文件,启动时自动加载(环境变量已存在时不覆盖)。
APP_HOST=127.0.0.1
APP_PORT=8000
APP_LOG_LEVEL=INFO| 变量 | 说明 | 默认 |
|---|---|---|
LLM_PROVIDER |
openai / qwen 等 |
openai |
LLM_API_KEY |
API Key | — |
LLM_MODEL |
模型名称 | gpt-5.4-mini / qwen-turbo |
LLM_BASE_URL |
兼容 OpenAI 接口的 base URL | — |
LLM_TIMEOUT_SECONDS |
请求超时(秒) | 30 |
LLM_MIN_INTERVAL_SECONDS |
LLM 请求启动之间的最小间隔,避免触发限流 | 智谱默认 5,其他默认 0 |
LLM_MAX_CONCURRENCY |
LLM 网关最大并发请求数,多个慢请求可并发在途 | 2 |
Qwen / DashScope 示例:
LLM_PROVIDER=qwen
LLM_API_KEY=replace_with_api_key
LLM_MODEL=qwen-turbo
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
LLM_MAX_CONCURRENCY=2- 支持配置多个 LLM 供应商、API Key 和模型组合,并提供手动切换、限流后自动切换、失败重试回退等策略,用于降低单一账号 429 限流对聊天理解、节点筛选和帖子匹配的影响。
| 变量 | 说明 |
|---|---|
TELEGRAM_BOT_TOKEN |
BotFather 获取的 Token |
TELEGRAM_API_BASE_URL |
自定义 API 地址(可选,用于反代) |
TELEGRAM_WEBHOOK_URL |
Webhook 公网地址(不填则使用 polling) |
TELEGRAM_WEBHOOK_SECRET |
Webhook 验证密钥 |
TELEGRAM_AUTO_PUSH_ENABLED |
开启自动推送 true / false |
TELEGRAM_AUTO_PUSH_INTERVAL_SECONDS |
推送间隔秒数,默认 300 |
TELEGRAM_AUTO_PUSH_LIMIT |
单次最多推送帖子数,默认 5 |
TELEGRAM_AUTO_PUSH_CANDIDATE_LIMIT |
每个节点最多抓取帖子数,默认 30 |
TELEGRAM_AUTO_PUSH_MATCH_MODE |
auto / llm / rules,默认 auto |
TELEGRAM_WATCH_INTERVAL_SECONDS |
帖子回复监控检查间隔,默认 60 |
| 变量 | 说明 |
|---|---|
CLASH_PROXY_ENABLED |
开启代理 true / false |
CLASH_PROXY_URL |
HTTP 代理地址,如 http://127.0.0.1:7890 |
CLASH_CONTROLLER_URL |
Clash REST API 地址,如 http://127.0.0.1:9090 |
CLASH_API_SECRET |
Clash API 密钥 |
CLASH_SELECTOR_NAME |
代理选择器名称,默认 Proxies |
CLASH_ROTATE_ON_403 |
收到 403 时自动轮换代理,默认 true |
CLASH_ROTATE_MAX_ATTEMPTS |
最大轮换次数,默认 2 |
CLASH_BUILTIN_SS |
使用内置 Shadowsocks 模式 |
CLASH_SOURCE_PROFILE_PATH |
Clash 配置文件路径(托管模式) |
CLASH_MIHOMO_BINARY_PATH |
Mihomo 二进制路径(托管模式) |
V2EX_API_TOKEN=your_token # 帖子回复监控备用接口(可选)| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /healthz |
健康检查 |
| GET | /dashboard |
Web Dashboard |
| GET | /api/v1/events |
SSE 实时事件流 |
| GET | /api/v1/dashboard/overview |
全量概览 JSON |
| GET | /api/v1/llm/status |
LLM 配置状态 |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/profile |
获取画像(?profile_id=xxx) |
| PUT | /api/v1/profile |
结构化更新画像 |
| DELETE | /api/v1/profile |
重置画像 |
| POST | /api/v1/profile/chat |
自然语言更新画像 |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/feeds/v2ex |
全站聚合 Feed |
| GET | /api/v1/feeds/v2ex/{node} |
指定节点 Feed |
| GET | /api/v1/matches/v2ex |
全站命中结果 |
| GET | /api/v1/matches/v2ex/{node} |
指定节点命中结果 |
查询参数:limit、match_mode(auto/llm/rules)、matched_only、candidate_limit
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/telegram/status |
Bot 状态 |
| GET | /api/v1/telegram/polling/status |
Polling 状态 |
| GET | /api/v1/telegram/auto-push/status |
自动推送状态 |
| POST | /api/v1/telegram/polling/run-once |
手动触发一次轮询 |
| POST | /api/v1/telegram/auto-push/run-once |
手动触发一次推送 |
| POST | /api/v1/telegram/webhook |
接收 Webhook |
| POST | /api/v1/telegram/webhook/set |
设置 Webhook |
| POST | /api/v1/telegram/webhook/delete |
删除 Webhook |
| POST | /api/v1/telegram/messages/send |
主动发消息 |
| POST | /api/v1/telegram/push/matches |
推送命中内容到指定 chat |
| GET | /api/v1/telegram/topic-watches |
查询回复监控列表 |
| POST | /api/v1/telegram/topic-watches |
添加回复监控 |
| DELETE | /api/v1/telegram/topic-watches |
删除回复监控 |
| POST | /api/v1/telegram/topic-watches/check |
手动检查回复监控 |
rust-backend/src/
├── main.rs # 入口:初始化 tracing、构建 AppState、启动 runtime
├── http_server.rs # Axum 路由、AppState、SSE、graceful shutdown
├── config.rs # AppSettings,从 .env / 环境变量读取
├── models.rs # 所有数据结构(InterestProfile、V2exFeedResponse 等)
├── adapters.rs # HTTP 网关实现(LLM、Telegram、V2EX topic API)
├── matcher.rs # 本地规则匹配引擎
├── rss.rs # V2EX RSS 抓取、节点目录缓存、全局聚合
├── profile/
│ ├── llm.rs # LlmGateway trait、OpenAiProfileLlmService(含代理重试)
│ ├── store.rs # 兴趣画像 SQLite 存储
│ ├── node_selector.rs # 节点选择结果存储(profile_node_selections 表)
│ └── chat.rs # 规则模式画像更新逻辑
├── telegram/
│ ├── bot.rs # TelegramBotService、API 网关
│ ├── chat.rs # 对话处理(dispatch → 画像更新 / 监控 / 查询)
│ ├── polling.rs # Long-polling worker
│ ├── auto_push.rs # 自动推送、节点选择编排
│ ├── topic_watch.rs # 帖子回复监控
│ └── store.rs # chat binding、推送历史 SQLite 存储
└── proxy/
├── clash_proxy.rs # Clash/Mihomo 代理管理、403 轮换、托管启动
└── ss_local.rs # 内置 Shadowsocks 支持
V2EX 有 ~1200 个节点,全量扫描耗时且无意义。系统会为每个用户维护一份专属节点列表:
选节点流程(启动时预热):
- 拉取节点目录(内存缓存 → 磁盘缓存 → 网络,TTL 30 天)
- 对每个活跃用户画像,检查 SQLite 中的
profile_node_selections表 - 若无记录或画像关键词已变动(通过 hash 检测),调用 LLM 分批筛选:
- 节点目录按每批 150 个切分,串行调用,每批走
run_with_proxy_retry - LLM 从每批中选出匹配兴趣的节点,结果用真实目录做 HashSet 过滤
- 最终合并,持久化至 SQLite
- 节点目录按每批 150 个切分,串行调用,每批走
自动推送流程:
启动 → refresh_all_node_selections() → 预热所有画像的节点列表
↓
每 N 秒 → run_once()
└─ 对每个 chat 绑定的画像:
├─ 从 SQLite 读取节点列表(hash 匹配则直接用)
├─ hash 不匹配 → 重新调 LLM 选节点
├─ LLM 失败 → 用旧缓存(stale)
├─ 连旧缓存都没有 → 跳过本轮,不回退全局扫描
└─ fetch_nodes_feed(选定节点) → 匹配 → 推送去重后的新帖子
| 命令 | 说明 |
|---|---|
/start |
初始化,绑定个人画像 |
/help |
帮助说明 |
/profile |
查看当前兴趣画像 |
/matches |
查看当前命中的帖子 |
/reset |
重置画像 |
| 自然语言 | 更新画像,如"我想关注 AI 工具和独立开发,不看求职" |
所有数据存储在 data/app.db(SQLite),表结构:
| 表 | 说明 |
|---|---|
interest_profiles |
用户兴趣画像(关键词、节点偏好等) |
telegram_chat_bindings |
Telegram chat 与画像的绑定关系 |
telegram_pushed_topics |
已推送帖子记录(去重用) |
telegram_topic_watches |
帖子回复监控列表 |
telegram_kv |
自动推送状态、offset 等 KV 状态 |
profile_node_selections |
每个画像对应的节点筛选结果和关键词 hash |
节点目录磁盘缓存存储在项目根目录的 node_catalog_cache.json,TTL 30 天。
# 编译检查
cargo check
# 运行单元测试
cargo test --lib
# 构建 release
cargo build --release日志级别通过 APP_LOG_LEVEL 控制,支持 RUST_LOG 覆盖(用于调试特定模块)。