在这里插入图片描述

一、项目整体定位

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

用户可以在平台中完成:

注册登录
  ↓
发布 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,一旦泄露,在过期之前很难主动失效。

而双令牌方案可以兼顾:

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

四、发布系统:渐进式发布 + 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 项目的提升在于:

  1. 认证不是简单 session,而是 JWT 双令牌;
  2. 发布不是普通表单提交,而是 OSS 渐进式发布;
  3. 点赞不是直接改库,而是位图 + Kafka + SDS;
  4. 关系不是同步多写,而是 Outbox 事件驱动;
  5. Feed 不是直接查库,而是三级缓存;
  6. 搜索不是 LIKE,而是 ES 相关性排序;
  7. AI 不是简单接口调用,而是围绕单篇知文的 RAG 问答。

这些模块组合起来,才形成了一个比较完整的知识社区后端系统。


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐