欢迎光临
我们一直在努力

【知识获取与分享社区项目 | 项目日记第 22 天】项目梳理上篇:从 0 到 1 复盘一个知识社区后端:认证、发布、Feed、搜索与 AI 问答

在这里插入图片描述

一、项目整体定位

知识获取与分享平台本质上是一个知识内容社区后端项目。

用户可以在平台中完成:

注册登录

发布 Markdown / 图文知文

点赞、收藏、关注作者

通过 Feed、搜索、详情页消费内容

使用 AI 摘要和单篇知文 RAG 问答提升阅读效率

所以这个项目并不是简单 CRUD,而是围绕内容社区常见场景做了一整套工程设计:

模块作用
认证系统 JWT 双令牌登录、刷新、登出、撤销
发布系统 草稿、OSS 直传、内容确认、元数据更新、正式发布
计数系统 点赞、收藏、关注、粉丝、发文等计数
用户关系系统 关注、取关、粉丝投影、关系列表缓存
点赞系统 分片位图判重、Kafka 异步写、计数重建
Feed 流 Caffeine + Redis 页面缓存 + Redis 片段缓存
搜索系统 ES 全文检索、标签过滤、游标分页、联想建议
AI 系统 DeepSeek 摘要、单篇知文 RAG 流式问答

二、技术栈总览

后端主要技术栈如下:

Java 21
Spring Boot 3.2.4
Spring Security
Spring AI
MyBatis
MySQL
Redis
Kafka
Canal
Caffeine
Elasticsearch
阿里云 OSS
DeepSeek

可以简单理解为:

技术在项目中的作用
MySQL 保存用户、知文、关系、Outbox 等权威数据
Redis 验证码、刷新令牌白名单、位图、SDS 计数、缓存
Kafka 计数聚合、关系事件、搜索索引异步更新
Canal 订阅 Outbox 表 binlog,转发领域事件
OSS 保存 Markdown 正文、图片、视频等大对象
Elasticsearch 内容搜索、前缀联想、RAG 向量库后端
Spring AI + DeepSeek 摘要生成与 RAG 问答

这个项目的核心思路是:

主业务事实存 MySQL
高频状态和计数走 Redis
异步派生数据走 Kafka / Canal
大文件走 OSS
搜索和 AI 检索走 Elasticsearch


三、认证系统:短 access token + 长 refresh token

认证系统采用的是 JWT 双令牌模式:

access token:15 分钟
refresh token:7 天

access token 用于普通接口访问,走 JWT 无状态校验。

refresh token 用于续期,但它不是完全无状态的,而是会写入 Redis 白名单。

整体流程是:

用户登录

签发 access token + refresh token

refresh token 的 jti 写入 Redis 白名单

access token 过期后使用 refresh token 换新令牌

刷新成功后撤销旧 refresh token

登出或重置密码时删除 Redis 白名单

这样设计的好处是:

access token 保持高性能
refresh token 具备可撤销能力

如果只使用一个长期 JWT,一旦泄露,在过期之前很难主动失效。

而双令牌方案可以兼顾:

  • 高频请求不查库;
  • 会话续期可控;
  • 登出、改密、强制下线可以即时生效。

  • 四、发布系统:渐进式发布 + OSS 预签名直传

    发布系统没有采用“一次请求上传所有内容”的方式,而是拆成多个步骤:

    创建草稿

    申请 OSS 预签名 URL

    前端直传 Markdown / 图片 / 视频

    确认内容上传结果

    更新标题、标签、摘要、可见性等元数据

    正式发布

    这个流程可以叫做渐进式发布。

    它的优势是:

    设计好处
    草稿先创建 提前拿到知文 ID,后续资源可以挂到该 ID 下
    OSS 预签名直传 大文件不经过后端,节省带宽和服务器资源
    内容确认 保存 objectKey、ETag、size、SHA256 等元数据
    元数据独立更新 标题、标签、摘要等可以单独修改
    发布动作最后执行 状态从 draft 变成 published,才进入公开消费链路

    发布成功后,还会触发后续派生动作:

    用户发文数 +1
    搜索索引 upsert
    RAG 预索引
    Feed / 详情缓存失效

    也就是说,发布系统是整个内容生命周期的起点。


    五、计数与点赞:位图做事实,SDS 做快照

    项目中点赞、收藏属于高频写场景。

    如果每次点赞都直接更新 MySQL:

    UPDATE know_posts SET like_count = like_count + 1 WHERE id = ?

    热门内容会形成典型的热点行。

    所以项目采用了 Redis 位图 + Kafka 聚合 + Redis SDS 计数快照的方式。

    整体思路是:

    分片位图:记录用户是否点赞 / 收藏
    Kafka:异步传递增量事件
    Redis 聚合桶:临时累加 delta
    Redis SDS:保存紧凑计数快照

    一次点赞流程可以概括为:

    用户点赞

    Lua 脚本执行 GETBIT + SETBIT

    如果状态真的从 0 变成 1,发送 Kafka delta=+1

    消费者写入聚合桶

    后台定时折叠到 SDS 计数

    这里最关键的是位图。

    它既能判断用户是否已经点过赞,又能保证重复点击不会重复产生计数事件。

    读取时则分成两类:

    点赞数 / 收藏数:读 SDS
    我是否点赞 / 收藏:读位图

    这样公共计数和用户态状态被拆开了,Feed 缓存也不会混入某个用户的 liked / faved 状态。


    六、用户关系:following 主表 + follower 投影表

    用户关系系统实现关注、取关、粉丝列表、关注列表和用户维度计数。

    它采用的是一主多从模型:

    following 表:主事实表
    follower 表:粉丝投影表
    用户计数 SDS:关注数 / 粉丝数
    Redis ZSet:关系列表缓存

    关注发生时,并不是同步更新所有数据源,而是在同一个事务中写入:

    following 主表
    outbox 事件表

    之后由 Canal 订阅 Outbox 的 binlog,再通过 Kafka 分发给下游消费者。

    下游异步完成:

    写 follower 投影表
    更新关注列表缓存
    更新粉丝列表缓存
    更新用户维度计数

    这套方案解决的是双写一致性问题。

    如果业务代码直接写 MySQL 后再发 Kafka,可能出现:

    MySQL 成功,Kafka 发送失败

    Outbox 模式把业务事实和待发送事件放在同一个数据库事务里,事务成功就一定有事件记录,后续可以重试和补偿。


    七、Feed 流:公共缓存和用户态状态分离

    Feed 是内容社区的高频读入口。

    项目中 Feed 使用三级缓存:

    L1:Caffeine 本地缓存
    L2:Redis 页面缓存 / ID 列表缓存
    L3:Redis item 片段缓存

    Feed 缓存的一个关键原则是:

    公共内容可以缓存,用户态状态不能写进公共缓存。

    比如标题、封面、作者、标签、点赞数可以缓存。

    但:

    当前用户是否点赞
    当前用户是否收藏

    不能直接缓存到公共 Feed 页面中。

    否则 A 用户看到的点赞状态可能污染 B 用户。

    所以项目采用:

    Feed 公共数据走缓存
    返回前实时读取位图覆盖 liked / faved

    同时为了防止缓存击穿,还实现了 single-flight:

    同一页缓存失效时,只允许一个线程回源 DB
    其他线程等待结果

    再配合 TTL 随机抖动和 hotkey 探测,可以降低缓存雪崩和热点回源压力。


    八、搜索系统:ES 负责召回、排序和联想

    搜索系统基于 Elasticsearch 实现。

    它不只是简单关键词匹配,而是包含:

    multi_match 检索 title / body
    tags 标签过滤
    function_score 融合点赞数、浏览数
    highlight 高亮摘要
    search_after 游标分页
    completion suggester 前缀联想

    搜索排序大致是:

    BM25 文本相关性
    +
    log(点赞数 + 1)
    +
    log(浏览数 + 1)

    这样既能保证关键词相关性,也能让高质量内容适当靠前。

    分页使用 search_after,避免 ES from + size 深分页性能问题。

    索引更新则不直接阻塞发布主链路,而是通过:

    知文发布 / 更新 / 删除

    写 Outbox

    Canal 订阅 binlog

    Kafka 分发

    搜索消费者 upsert / soft delete ES 文档

    这也是最终一致性在搜索系统中的体现。


    九、AI 系统:摘要生成 + 单篇知文 RAG 问答

    AI 部分分成两个能力。

    第一个是文章摘要生成:

    用户输入正文

    调用 DeepSeek

    生成不超过 50 字中文摘要

    服务端做格式清洗和截断

    第二个是 RAG 问答。

    RAG 流程如下:

    用户围绕单篇知文提问

    检查该知文是否已索引

    从 OSS 拉取 Markdown

    按标题和长度切片

    写入 Elasticsearch 向量库

    向量召回相关片段

    按 postId 过滤当前知文上下文

    构造 Prompt

    DeepSeek 流式生成

    SSE 返回前端

    这里最重要的是两个点。

    第一,RAG 只围绕当前知文回答。

    每个切片 metadata 中都有 postId,查询后还会按 postId 过滤,避免召回其他文章内容。

    第二,索引通过 SHA256 / ETag 判断是否过期。

    如果正文没有变化,就跳过重建;如果正文变化,就删除旧切片再写入新切片,保证单篇文章只有一个当前版本。


    十、项目整体链路总结

    把所有模块串起来,平台的完整链路就是:

    认证系统保证用户身份

    发布系统产生内容主事实

    OSS 保存正文和图片等大对象

    Outbox 驱动搜索、关系、缓存等派生数据

    Redis 承担计数、位图、缓存和令牌白名单

    Kafka 承担异步削峰和事件传播

    Feed 和搜索负责内容分发

    RAG 问答负责围绕单篇内容做智能阅读

    它的核心不是某一个单点技术,而是不同技术之间的分工:

    MySQL 管事实
    Redis 管高频状态
    Kafka 管异步事件
    OSS 管大对象
    ES 管搜索和检索
    AI 管内容理解


    十一、本篇总结

    这一篇从业务闭环角度总结了该项目。

    如果用一句话概括:

    知识获取与分享社区是一个围绕知识内容发布、消费、互动和智能问答构建的内容社区后端项目。

    它相比普通 CRUD 项目的提升在于:

  • 认证不是简单 session,而是 JWT 双令牌;
  • 发布不是普通表单提交,而是 OSS 渐进式发布;
  • 点赞不是直接改库,而是位图 + Kafka + SDS;
  • 关系不是同步多写,而是 Outbox 事件驱动;
  • Feed 不是直接查库,而是三级缓存;
  • 搜索不是 LIKE,而是 ES 相关性排序;
  • AI 不是简单接口调用,而是围绕单篇知文的 RAG 问答。
  • 这些模块组合起来,才形成了一个比较完整的知识社区后端系统。


    赞(0)
    未经允许不得转载:171主机测评 » 【知识获取与分享社区项目 | 项目日记第 22 天】项目梳理上篇:从 0 到 1 复盘一个知识社区后端:认证、发布、Feed、搜索与 AI 问答
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址