【知识获取与分享社区项目 | 项目日记第 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 问答。
这些模块组合起来,才形成了一个比较完整的知识社区后端系统。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)