-
Notifications
You must be signed in to change notification settings - Fork 45
Community Shared Links
负责人:@longsizhuo
状态:设计全部锁定,待进入实施阶段
最后更新:2026-04-19
在 involutionhell.com 首页 Contribute 按钮下方新增一个轻量入口 /feed,供群友把微信公众号 / 知乎 / 小红书等平台的随手转发文章丢进来;后端抓 Open Graph meta + DeepSeek 异步分类审核,通过后进社区分享墙,点击卡片直接跳原文,不转存正文(规避盗链)。
这条链路与现有的 Fumadocs 贡献库完全独立:
- Fumadocs = 严肃、需 Git PR、经同行审阅的知识库
-
/feed= 轻量、UGC、AI 审核、实时性不强的社区分享流
现状痛点
- 群友日常在微信群里转发大量好文章(公众号、知乎专栏等),信息随群聊流失
- 走 Fumadocs 投稿门槛太高(写 frontmatter、开 PR),不适合"转发即分享"
- 直接站内解析正文有盗链风险,也无法覆盖微信/知乎的反爬
目标
- 降低"分享好内容"的门槛到 "粘贴 URL + 一句话推荐"
- 保留 Fumadocs 的严肃性,不被 UGC 噪音污染
- 用 AI 异步审核 + 域名白名单,把人工审核压到最小
位置:Hero.tsx 中 <Contribute /> 组件下方
样式:斜体小号文字链,text-muted-foreground text-sm,视觉层级明显弱于主 CTA
文案草稿:📎 看到好文章?丢个链接 →(点击跳转 /feed)
为什么不做成并列大按钮
- Contribute 是首页主 CTA,不能被稀释
- 两者性质完全不同(严肃投稿 vs 随手分享),并列会混淆信息架构
- 次级文字链既"明显可见"又"不突兀",符合双轨叙事
用户(已登录) → 首页点 "丢个链接"
→ /feed/submit 粘贴 URL + 一句话推荐理由
→ 后端 POST /api/community/links
├── URL 规范化(严格根域校验)
├── 若命中白名单域名 → status = PENDING(进异步处理)
└── 若非白名单 → status = PENDING(进异步处理 + 管理员可见)
→ 前端显示 "提交成功,AI 正在审核(预计 10s - 数分钟)"
→ 异步 worker 跑:
├── 抓 Open Graph meta(title / desc / cover / siteName)
├── DeepSeek 分类 + 安全判定 (nsfw / ad / flame)
└── 落结果:
├── 白名单 + 安全 → APPROVED(上公共流)
├── 非白名单 + 安全 → PENDING_MANUAL(进管理员待审)
└── 任一 flag 命中 → FLAGGED(进管理员待审)
→ /feed 页面按分类 tab 展示 APPROVED 的卡片
→ 用户点击卡片 → 跳原文(target="_blank" rel="noopener noreferrer")
- 复用现有 NextAuth
- 提交接口需 session 校验
- 每用户每天限频 5 条,防账号盗用刷屏
- 不追求实时分类:参照 Fumadocs Git 贡献流,用户已有"提交后等一会儿"的心理预期
- 异步延迟本身是安全缓冲:避免提交瞬间曝光风险内容
-
后端独立 Service,不复用现有
OpenAIController的 chat 路由,避免 prompt 耦合
- AI 前沿 / 论文解读
- 工程实践 / 工具
- 求职 & 实习
- 考研 & 留学
- 行业观察 / 商业
- 学习方法 / 认知
- 生活 & 随笔
- 其他
分类错了允许用户手动改(本人可改自己的,管理员可改任意)。
白名单(命中免人工审核,但仍过机器闸)
-
mp.weixin.qq.com(公众号) -
zhuanlan.zhihu.com/www.zhihu.com/p/*/www.zhihu.com/question/*/answer/* xiaohongshu.com- 技术/科普向补充:
juejin.cn、36kr.com、sspai.com(可迭代)
三道机器闸(无论是否白名单都跑)
-
URL 规范化:解析 hostname,只认根域精确匹配,拒绝
weixin.qq.com.evil.com钓鱼 - DeepSeek 同时返回分类 + NSFW/广告/引战 flag,任一命中进待审
- 社区举报:每张卡片带举报按钮,3 个独立用户举报自动下架 + 进待审
非白名单域名:默认进管理员待审队列(预期频率低,每周处理一次即可)
- 后端只抓 Open Graph meta(title / description / cover / siteName),这是所有社交平台开放且期望的行为
- 不缓存原文正文
- 前端卡片 = OG 预览 + 点击跳原文,流量 100% 回源
- 所有数据走后端,前端只调后端 REST API
- 逐步减少前端 API Route,避免分类不清、维护困难
- 复用现有共享 PostgreSQL 与
users表
- 不加 hCaptcha / 图形验证码(成员体量小,朋友圈社区,体验优先)
- 防滥用仅靠:登录 + 5 条/天限频 + 社区举报
- 若后续真出现刷屏,再补 captcha
- 不拒收提交,避免用户被静默丢弃
- 失败时降级展示:只显示
url+host+ 用户一句话推荐 - 必须留 log:记录失败原因(超时 / 403 / 解析失败 / DNS 等),方便排障
- 日志落在后端现有日志系统(与 Spring Boot 其他 service 一致)
- 卡片 UI 上给一个弱提示 "封面/摘要未能抓取",不影响点击跳转
问题:公众号文章常常因作者自删或违规下架变成 404,若静默删除,作者会以为自己的分享从未出现过,体验极差。
方案:
- 定期(建议每周一次)后台任务扫描
APPROVED的链接,HEAD 请求探活 - 连续 2 次失败 →
status = ARCHIVED,不再出现在主流瀑布流 - 但在
/u/[userId]/shares"我提交的" 页面正常显示,并加"原文已失效"标签 - 另开一个
/feed?tab=archived归档 tab(默认不显示,URL 参数可达),供考古党查看 -
ARCHIVED不进人工复审队列,也不耗费 DeepSeek 额度
-
/feed、/feed/submit、/u/[userId]/shares、管理员面板全部接入 next-intl - 新增文案集中在
messages/{zh,en}.json的feed.*命名空间下 - 分类枚举的展示名也走 i18n(枚举本身仍是固定英文 slug 存库,展示时翻译)
- AI 返回的分类结果是枚举 slug,不受 i18n 影响
- 用户提交的"一句话推荐理由"按原文展示不翻译(UGC)
backend/src/main/java/.../community/
├── SharedLinkController.java
│ POST /api/community/links # 提交
│ GET /api/community/links # 列表(默认 status=APPROVED)
│ GET /api/community/links/mine # 当前用户提交(含各状态)
│ POST /api/community/links/{id}/report # 举报
│ PATCH /api/community/links/{id} # 修改分类(作者或管理员)
├── SharedLinkService.java # 业务编排 + 白名单 + 限频
├── ClassificationService.java # 独立封装 DeepSeek 调用
├── OgFetchService.java # 抓 Open Graph meta
├── UrlNormalizer.java # 根域严格校验
└── entity/
├── SharedLink.java # 主表
└── LinkReport.java # 举报表
为什么新开 community/ 而不是塞进 docs/:语义完全不同,前者是 UGC 社区流,后者是文档贡献,混在一起会造成后续维护混乱。analytics/ 也不复用,那是埋点用的。
CREATE TABLE shared_links (
id BIGSERIAL PRIMARY KEY,
submitter_id BIGINT NOT NULL REFERENCES users(id),
url TEXT NOT NULL,
url_hash VARCHAR(64) NOT NULL, -- 去重用
host VARCHAR(255) NOT NULL, -- 根域
recommendation TEXT, -- 用户一句话理由
og_title TEXT,
og_description TEXT,
og_cover TEXT,
og_site_name VARCHAR(255),
category VARCHAR(64), -- AI 分类结果
status VARCHAR(32) NOT NULL, -- PENDING / APPROVED / PENDING_MANUAL / FLAGGED / REJECTED / ARCHIVED
archived_at TIMESTAMPTZ, -- 失效时间(ARCHIVED 时写入)
archived_reason VARCHAR(64), -- link_dead / manual / spam 等
og_fetch_error TEXT, -- OG 抓取失败时记录原因(用于排障)
flags JSONB, -- {nsfw, ad, flame}
report_count INT DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (url_hash) -- 同 URL 不重复提交
);
CREATE INDEX idx_shared_links_status_created ON shared_links (status, created_at DESC);
CREATE INDEX idx_shared_links_category ON shared_links (category) WHERE status = 'APPROVED';
CREATE TABLE link_reports (
id BIGSERIAL PRIMARY KEY,
link_id BIGINT NOT NULL REFERENCES shared_links(id) ON DELETE CASCADE,
reporter_id BIGINT NOT NULL REFERENCES users(id),
reason VARCHAR(64),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (link_id, reporter_id) -- 同一人对同一条只能举报一次
);app/
├── page.tsx # 无需改,入口在 Hero 内
├── components/
│ ├── Hero.tsx # <Contribute /> 下方加文字链 → /feed
│ └── Contribute.tsx # 不改
├── feed/
│ ├── page.tsx # 瀑布流 + 分类 tab
│ ├── submit/page.tsx # 提交表单(URL + 推荐语)
│ └── components/
│ ├── LinkCard.tsx # OG 卡片
│ ├── CategoryTabs.tsx
│ └── ReportButton.tsx
└── u/[userId]/
└── shares/page.tsx # "我提交的"(含审核状态)
- 现有 migration / 连接池 / Sa-Token 都在此 schema
- 新开 schema 会增加跨 schema 引用、权限配置、backup 脚本的负担
- 表名前缀
shared_links/link_reports本身已提供命名隔离
- 对齐现有
app/u/[username]/AdminLinkIfOwnerAdmin.tsx的个人主页 tab 模式 - 不放
/settings(那是账户设置,UGC 列表语义上不属于"设置")
- 完全对齐 Events 模块的
AdminGuard+@SaCheckRole("admin")组合 - 团队已熟悉这套模式,零学习成本
- 路径:
- 前端
app/admin/community/page.tsx(待审列表) - 前端
app/admin/community/[id]/page.tsx(详情 + 通过/拒绝) - 后端
/api/admin/community/pending(GET 待审列表) - 后端
/api/admin/community/{id}/approve·/reject
- 前端
AI 前沿 / 工程实践 / 求职 / 考研留学 / 行业观察 / 学习方法 / 生活 / 其他
mp.weixin.qq.com · zhuanlan.zhihu.com · www.zhihu.com · xiaohongshu.com · juejin.cn · 36kr.com · sspai.com
| 里程碑 | 内容 | 预估 |
|---|---|---|
| M1 后端骨架 | Entity + Migration + Controller 空壳 + 白名单校验 | 1 天 |
| M2 OG 抓取 |
OgFetchService,单测覆盖微信/知乎/小红书 |
0.5 天 |
| M3 DeepSeek 分类 |
ClassificationService + prompt 调优 + 单测 |
1 天 |
| M4 异步 worker | 事件驱动或轮询(与后端团队对齐方案) | 0.5 天 |
M5 前端 /feed
|
列表页 + 提交页 + 卡片组件 | 1.5 天 |
| M6 首页入口 |
Hero.tsx 文字链 |
0.2 天 |
| M7 举报 + 管理员 | 举报 API + admin 审核 UI | 1 天 |
| M8 "我提交的" | 用户侧查看状态 | 0.5 天 |
| M9 失效探活任务 | 每周定时 job + ARCHIVED 状态迁移 + 归档 tab |
0.5 天 |
| M10 i18n 接入 |
messages/{zh,en}.json 新增 feed.* 命名空间 |
0.5 天 |
| M11 联调 + E2E | 本地 3010/8081 + Playwright 冒烟 | 1 天 |
合计 ~8 天(前后端并行可压缩)。
以下为仍在讨论中的灰色地带:
- 分类调整历史:用户改分类要不要留 audit log?(当前倾向不留,简化 schema,后续真有纠纷再加)
- ARCHIVED 复活机制:作者主动点"重新验证"再次请求 HEAD 探活?或人工申诉入口?
- 限频边界:5 条/天是按自然日还是滚动 24h?(建议滚动 24h,实现用 Redis / DB 时间窗)
- 举报阈值的"独立"判定:同 IP 多账号刷举报要不要防?(v1 先信任,出问题再加)
- ❌ 站内渲染原文正文(盗链风险 + 微信反爬无法解决)
- ❌ 实时分类(与设计目标冲突,异步才是安全缓冲)
- ❌ 合并进 Fumadocs(两个系统彻底隔离,保持严肃库纯净)
- ❌ 评论/讨论功能(v1 不做,后续按数据决定是否加)
- ❌ 订阅/推送通知(v1 不做)