Skip to content

Community Shared Links

longsizhuo edited this page Apr 23, 2026 · 1 revision

社区分享链接墙 · 设计提案(v1)

负责人:@longsizhuo
状态:设计全部锁定,待进入实施阶段
最后更新:2026-04-19


0. 一句话概述

在 involutionhell.com 首页 Contribute 按钮下方新增一个轻量入口 /feed,供群友把微信公众号 / 知乎 / 小红书等平台的随手转发文章丢进来;后端抓 Open Graph meta + DeepSeek 异步分类审核,通过后进社区分享墙,点击卡片直接跳原文,不转存正文(规避盗链)。

这条链路与现有的 Fumadocs 贡献库完全独立

  • Fumadocs = 严肃、需 Git PR、经同行审阅的知识库
  • /feed = 轻量、UGC、AI 审核、实时性不强的社区分享流

1. 背景与动机

现状痛点

  • 群友日常在微信群里转发大量好文章(公众号、知乎专栏等),信息随群聊流失
  • 走 Fumadocs 投稿门槛太高(写 frontmatter、开 PR),不适合"转发即分享"
  • 直接站内解析正文有盗链风险,也无法覆盖微信/知乎的反爬

目标

  • 降低"分享好内容"的门槛到 "粘贴 URL + 一句话推荐"
  • 保留 Fumadocs 的严肃性,不被 UGC 噪音污染
  • 用 AI 异步审核 + 域名白名单,把人工审核压到最小

2. 首页入口设计

位置Hero.tsx<Contribute /> 组件下方
样式:斜体小号文字链,text-muted-foreground text-sm,视觉层级明显弱于主 CTA
文案草稿📎 看到好文章?丢个链接 →(点击跳转 /feed

为什么不做成并列大按钮

  • Contribute 是首页主 CTA,不能被稀释
  • 两者性质完全不同(严肃投稿 vs 随手分享),并列会混淆信息架构
  • 次级文字链既"明显可见"又"不突兀",符合双轨叙事

3. 核心产品流程

用户(已登录) → 首页点 "丢个链接"
  → /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")

4. 已锁定的设计决策(逐条列出,全员可审阅)

4.1 需要登录

  • 复用现有 NextAuth
  • 提交接口需 session 校验
  • 每用户每天限频 5 条,防账号盗用刷屏

4.2 AI 模型:DeepSeek(沿用现有 URL)

  • 不追求实时分类:参照 Fumadocs Git 贡献流,用户已有"提交后等一会儿"的心理预期
  • 异步延迟本身是安全缓冲:避免提交瞬间曝光风险内容
  • 后端独立 Service,不复用现有 OpenAIController 的 chat 路由,避免 prompt 耦合

4.3 分类枚举(8 类,AI 从固定枚举选 1 个,temperature=0)

  1. AI 前沿 / 论文解读
  2. 工程实践 / 工具
  3. 求职 & 实习
  4. 考研 & 留学
  5. 行业观察 / 商业
  6. 学习方法 / 认知
  7. 生活 & 随笔
  8. 其他

分类错了允许用户手动改(本人可改自己的,管理员可改任意)。

4.4 审核策略:白名单 + 机器闸 + 社区举报

白名单(命中免人工审核,但仍过机器闸)

  • mp.weixin.qq.com(公众号)
  • zhuanlan.zhihu.com / www.zhihu.com/p/* / www.zhihu.com/question/*/answer/*
  • xiaohongshu.com
  • 技术/科普向补充:juejin.cn36kr.comsspai.com(可迭代)

三道机器闸(无论是否白名单都跑)

  1. URL 规范化:解析 hostname,只认根域精确匹配,拒绝 weixin.qq.com.evil.com 钓鱼
  2. DeepSeek 同时返回分类 + NSFW/广告/引战 flag,任一命中进待审
  3. 社区举报:每张卡片带举报按钮,3 个独立用户举报自动下架 + 进待审

非白名单域名:默认进管理员待审队列(预期频率低,每周处理一次即可)

4.5 盗链规避

  • 后端只抓 Open Graph meta(title / description / cover / siteName),这是所有社交平台开放且期望的行为
  • 不缓存原文正文
  • 前端卡片 = OG 预览 + 点击跳原文,流量 100% 回源

4.6 数据库

  • 所有数据走后端,前端只调后端 REST API
  • 逐步减少前端 API Route,避免分类不清、维护困难
  • 复用现有共享 PostgreSQL 与 users

4.7 防刷策略

  • 不加 hCaptcha / 图形验证码(成员体量小,朋友圈社区,体验优先)
  • 防滥用仅靠:登录 + 5 条/天限频 + 社区举报
  • 若后续真出现刷屏,再补 captcha

4.8 OG 抓取失败 → 降级而非拒收

  • 不拒收提交,避免用户被静默丢弃
  • 失败时降级展示:只显示 url + host + 用户一句话推荐
  • 必须留 log:记录失败原因(超时 / 403 / 解析失败 / DNS 等),方便排障
  • 日志落在后端现有日志系统(与 Spring Boot 其他 service 一致)
  • 卡片 UI 上给一个弱提示 "封面/摘要未能抓取",不影响点击跳转

4.9 失效链接处理:Archive 而非删除

问题:公众号文章常常因作者自删或违规下架变成 404,若静默删除,作者会以为自己的分享从未出现过,体验极差。

方案

  • 定期(建议每周一次)后台任务扫描 APPROVED 的链接,HEAD 请求探活
  • 连续 2 次失败 → status = ARCHIVED,不再出现在主流瀑布流
  • 但在 /u/[userId]/shares "我提交的" 页面正常显示,并加"原文已失效"标签
  • 另开一个 /feed?tab=archived 归档 tab(默认不显示,URL 参数可达),供考古党查看
  • ARCHIVED 不进人工复审队列,也不耗费 DeepSeek 额度

4.10 国际化:next-intl

  • /feed/feed/submit/u/[userId]/shares、管理员面板全部接入 next-intl
  • 新增文案集中在 messages/{zh,en}.jsonfeed.* 命名空间下
  • 分类枚举的展示名也走 i18n(枚举本身仍是固定英文 slug 存库,展示时翻译)
  • AI 返回的分类结果是枚举 slug,不受 i18n 影响
  • 用户提交的"一句话推荐理由"按原文展示不翻译(UGC)

5. 技术实施方案

5.1 后端新增模块

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/ 也不复用,那是埋点用的。

5.2 数据模型(草稿,最终以 migration 为准)

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)                -- 同一人对同一条只能举报一次
);

5.3 前端改动

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               # "我提交的"(含审核状态)

6. 最终确认的设计决策

6.1 数据库 schema 位置:users 同库同 schema

  • 现有 migration / 连接池 / Sa-Token 都在此 schema
  • 新开 schema 会增加跨 schema 引用、权限配置、backup 脚本的负担
  • 表名前缀 shared_links / link_reports 本身已提供命名隔离

6.2 "我提交的"页面:/u/[userId]/shares

  • 对齐现有 app/u/[username]/AdminLinkIfOwnerAdmin.tsx 的个人主页 tab 模式
  • 不放 /settings(那是账户设置,UGC 列表语义上不属于"设置")

6.3 管理员面板:app/admin/community/ + 后端 /api/admin/community/*

  • 完全对齐 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

6.4 分类枚举(8 类)

AI 前沿 / 工程实践 / 求职 / 考研留学 / 行业观察 / 学习方法 / 生活 / 其他

6.5 白名单域名

mp.weixin.qq.com · zhuanlan.zhihu.com · www.zhihu.com · xiaohongshu.com · juejin.cn · 36kr.com · sspai.com


7. 实施里程碑(初步)

里程碑 内容 预估
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 天(前后端并行可压缩)。


8. 开放问题(供讨论)

以下为仍在讨论中的灰色地带:

  • 分类调整历史:用户改分类要不要留 audit log?(当前倾向不留,简化 schema,后续真有纠纷再加)
  • ARCHIVED 复活机制:作者主动点"重新验证"再次请求 HEAD 探活?或人工申诉入口?
  • 限频边界:5 条/天是按自然日还是滚动 24h?(建议滚动 24h,实现用 Redis / DB 时间窗)
  • 举报阈值的"独立"判定:同 IP 多账号刷举报要不要防?(v1 先信任,出问题再加)

9. 附录:非目标(明确不做的事)

  • ❌ 站内渲染原文正文(盗链风险 + 微信反爬无法解决)
  • ❌ 实时分类(与设计目标冲突,异步才是安全缓冲)
  • ❌ 合并进 Fumadocs(两个系统彻底隔离,保持严肃库纯净)
  • ❌ 评论/讨论功能(v1 不做,后续按数据决定是否加)
  • ❌ 订阅/推送通知(v1 不做)

Clone this wiki locally