社区类源码开发实践中的仿小红书系统技术要点分析

仿小红书类社区系统图文、短视频、多图、话题、搜索、收藏、浏览记录、发现页、关注页、附近页,看起来是很多功能,其实大部分都围绕同一个对象:内容。

一开始把图文、视频、话题、互动各做一套,第一版确实快,后面接搜索、推荐和多端数据时就会越来越难维护。所以这类社区源码,我更关注的是一条完整链路:内容怎么存、媒体怎么挂、审核什么时候做、搜索索引什么时候写,以及发现页怎么连续往下刷。

一、内容表先统一,图文和短视频别各玩各的

很多项目第一版只有图文,表名直接叫 article。后面加短视频,又补一张 video

功能少的时候没问题,一旦点赞、评论、收藏、浏览记录都接进来,麻烦就开始了。

比如收藏到底是:

article_collect

还是:

video_collect

评论表是不是也要分两套?

搜索的时候图文和视频又要分别查,最后再合并排序。

所以如果项目从一开始就确定图文、短视频都会有,直接抽一张内容主表更省事。

javascript 复制代码
CREATE TABLE community_content (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    content_type TINYINT NOT NULL COMMENT '1图文 2短视频',
    title VARCHAR(200),
    content TEXT,
    cover_url VARCHAR(500),
    status TINYINT DEFAULT 0 COMMENT '0待审核 1正常 2驳回',
    like_count INT DEFAULT 0,
    comment_count INT DEFAULT 0,
    collect_count INT DEFAULT 0,
    view_count INT DEFAULT 0,
    publish_time DATETIME,
    create_time DATETIME
);

多图不要继续往这张表里塞 image1、image2、image3

单独做媒体表就行,保存 content_id、media_url、media_type、width、height、sort

这里 widthheight 我觉得比很多人想象中更重要。

双列瀑布流如果只拿到图片 URL,前端要等图片加载完才知道高度,页面刚打开时经常会出现卡片往下跳。后端直接把原图宽高一起返回,前端在图片加载前就能把卡片高度算出来,首屏会稳定很多。

短视频也可以继续挂在媒体表,只是多几个字段,例如视频封面、时长。

话题标签也是类似做法,内容表里不要直接存一串 #摄影#旅行#露营,最好还是内容表、话题表、中间关系表分开。

这一步前面多写一点代码,后面做话题页、搜索和推荐时反而省事。

二、多图发布这块,Java 服务尽量别负责"搬文件"

社区项目的上传量一般不小。

一条普通图文可能 6 张图,短视频更不用说。如果客户端先把文件传给 Java 服务,Java 再转到腾讯云对象存储,应用服务器会白白吃掉不少带宽和内存。

业务量小的时候看不出来,图片大一点、并发一上来,这种中转就比较浪费。

更常用的方式是先拿上传凭证,客户端直接传对象存储。

流程其实不复杂:

用户选图或者视频,先请求临时上传凭证,上传腾讯云 COS,拿到文件地址后,再提交发布接口。

真正的发布接口只接这种业务数据:

javascript 复制代码
{
  "contentType": 1,
  "content": "整理了一组夜景照片",
  "topicIds": [18, 27],
  "mediaList": [
    {
      "url": "/2026/08/a1.jpg",
      "width": 1080,
      "height": 1440
    },
    {
      "url": "/2026/08/a2.jpg",
      "width": 1080,
      "height": 1350
    }
  ]
}

后台拿到以后,先写内容主表,再批量写媒体关系。

草稿也不用另外搞一套发布流程。

内容表增加一个草稿状态就够了,保存草稿时不进搜索、不进推荐;真正点击发布以后再切到待审核。

这样编辑草稿、继续上传图片、删除某张图片,都是围绕同一个 content_id 做。

还有一个细节,上传成功不等于内容就能公开。

对象存储里有文件,只代表文件已经上传了,跟内容状态不是一回事。

三、审核、搜索、推荐这三个顺序别反了

用户提交内容以后,我比较建议先落库,再做审核。

状态先设为"待审核",腾讯云内容审核通过以后,再把内容改成正常。

搜索索引也放在这个时间点之后。

否则会出现一种很尴尬的情况:MySQL 里的内容还是待审核,Elasticsearch 已经能搜到了。

发现页如果又直接从推荐池读,也可能把还没审核完的内容放出去。

所以顺序最好固定:

发布成功 → 待审核 → 审核通过 → 更新内容状态 → 写搜索索引 → 进入发现页或推荐池。

审核接口本身不一定要卡着发布请求同步执行。

短视频审核时间有时候会长一些,用户没必要一直等着接口返回。

发布接口先返回"审核中",后台再异步跑审核任务就可以。

项目里已经用了 Quartz 的话,前期甚至不一定马上引入 MQ,可以先做一张审核任务表,定时扫描没有处理完的记录。

后面量大了再把审核、索引同步拆成消息任务。

这个调整对前端基本没影响,但后台会舒服很多。

四、搜索做到一定量以后,LIKE 基本还是要换

内容只有几千条的时候,MySQL LIKE '%关键词%' 确实够用。

很多项目第一次做搜索也都是这样。

问题是数据继续增加以后,除了速度,中文搜索本身也不太好处理。

比如用户搜索"城市夜景",他可能希望搜到标题里只有"夜景摄影"的内容,也可能希望搜到话题里带"街拍"的内容。

这时候就不是简单 LIKE 能解决的了。

EasyES 可以放在这里。

内容审核通过以后,把需要搜索的字段同步过去,例如标题、正文、内容类型、话题、发布时间。

javascript 复制代码
LambdaEsQueryWrapper<ContentDocument> wrapper =
        new LambdaEsQueryWrapper<>();

wrapper.multiMatch(
        keyword,
        ContentDocument::getTitle,
        ContentDocument::getContent
);

wrapper.eq(ContentDocument::getStatus, 1);
wrapper.orderByDesc(ContentDocument::getPublishTime);

List<ContentDocument> list = contentMapper.selectList(wrapper);

但 Elasticsearch 不要反过来当主数据库用。

点赞状态、收藏状态、内容是否下架,这些还是业务库说了算。

搜索服务主要负责一件事:根据关键词快速找到对应内容。

后面哪怕索引损坏或者需要重建,也能从 MySQL 重新生成。

热门搜索则没必要每次打开页面都去日志表 GROUP BY

关键词搜索次数直接累加到 Redis,用一个有序集合做排名就够用了。定时再把需要长期保存的数据回库。

五、双列瀑布流表面是前端问题,实际坑经常在分页

瀑布流 UI 本身不算难。

左边一列、右边一列,根据当前高度把下一张卡片放到短的一边,再配合前面保存的图片宽高,基本就能做出来。

真正容易踩坑的是后台分页。

普通后台管理页面习惯用 page=1、page=2,最后变成 LIMIT offset,size

放在社区发现页里就不太合适。

用户一直往下刷,offset 会越来越大。更麻烦的是推荐排序本身会变。

一条内容刚开始排在第 50,突然多了不少点赞,权重升到第 20。

用户刚好在这个过程中加载下一页,就可能遇到重复内容。

所以发现页一般更适合游标方式。

第一次拿 20 条,后台把最后一条记录的排序值作为 nextCursor 返回。

下一次客户端带着这个 cursor 继续请求,不再关心"这是第几页"。

排序可以用推荐分、发布时间、内容 ID 一起做。

例如:

推荐分倒序,发布时间倒序,ID 倒序。

这里最好不要只拿发布时间当游标。

同一秒内如果发布了多条内容,只靠时间很容易碰到重复值,最后还是得把 ID 带进去做兜底。

关注页其实也可以继续用同样方式,只不过它的数据来源变成"我关注的人发布的内容"。

附近页再多一个位置过滤。

三个页面返回的数据结构尽量别拆成三套,前端的内容卡片也能直接复用。

六、Redis用在热点数据上,不要什么都往里塞

社区类系统有不少字段变化频率挺高。

浏览量、点赞数、收藏数就是典型例子。

如果每一次浏览都马上执行:

view_count = view_count + 1

访问量上来以后,会产生很多意义并不大的高频更新。

这种数据可以先放 Redis,再通过 Quartz 定时回写 MySQL。

但这里也别走到另一个极端。

Redis 里有一个 likeCount=100,并不能说明当前用户点没点过赞。

"点赞总数"和"谁点过赞"是两件事。

用户点赞关系最好还是有真实记录,至少需要保存 user_idcontent_id,否则取消点赞、防重复点赞、个人点赞记录都不好处理。

浏览记录也是一样。

一方面它是个人中心里的功能,用户可以查看最近看过什么;另一方面它还能给发现页做简单去重。

刚看完的内容,刷新以后又连续刷到两三次,体验会很明显。

第一版推荐没有必要马上上复杂算法。

按发布时间、点赞、评论、收藏、浏览,再叠加一点内容分类和话题兴趣,已经能跑出一个基础版本。

例如用户最近经常看摄影内容,发现页就适当多给摄影相关分类和话题一点权重。

等后面真实行为数据多了,再考虑把推荐逻辑继续拆开。

四端共用后台时,真正统一的是数据,不是页面

Android APP、iOS APP、微信小程序和 H5 四端共用一套后台,这个结构本身没有太多特殊的地方。

关键是不要把业务数据留在某一个客户端。

用户在 APP 收藏的内容,换到 H5 登录同一个账号以后,应该还能看到。

关注关系、粉丝关系、浏览记录也是一样。

这些数据都应该围绕统一的用户 ID 存在服务端。

接口层也尽量保持一致。

发现页、内容详情、点赞、收藏、用户主页,四个终端调同一套接口。

真正需要单独适配的一般是相册选择、相机、定位、微信登录、支付这些终端能力。

后台没必要为了 Android 再写一套 /app/content,为了小程序再写 /mini/content

接口一旦这样拆开,后面字段改一次就要改好几处。

单聊和通知则单独放到 WebSocket 这一层处理。

评论通知、点赞提醒、关注提醒,本质上都是业务动作完成以后产生一条通知。数据库先保存,WebSocket 负责在线推送。

消息已读未读也是一样,不能只依赖连接状态。

用户断线重连以后,历史消息和未读数还得能重新查出来。

商品分类、商品列表、购物车、订单、支付这些模块没必要直接塞进内容主链路。内容如果需要关联商品,保存一层内容和商品关系即可,订单和支付还是走商城自己的业务表。

这种拆法后面做作品管理、收藏管理、浏览记录、关注列表和粉丝列表时,基本都还是围绕内容 ID、用户 ID 两套关系继续查,不需要再重新建立一套数据模型。

使用场景中的功能资料内容说明湖南宠友信息技术有限公司是一家专注社区交友类产品、企业即时通信软件开发,为企业提供即时通信工具、垂直类内容圈子,自主研发的业界知名友猫产品拥有广大的企业用户群体https://chongyou.info/index.html

相关推荐
用户37215742613519 分钟前
Java 设置 PDF 表单域只读或扁平化
java
摇滚侠1 小时前
《SpringBoot 3:入门与应用实战》第 9 章 使用 WebMvc 开发应用 阅读笔记 1
spring boot·笔记·后端
Lyra_Infra1 小时前
MySQL Docker 误删恢复:binlog PITR
mysql·docker·命令行
李昊哲小课1 小时前
SpringBoot4 云端咖啡站 阶段四:安全、文件与性能
spring boot·安全·性能优化·文件·性能
luteres2 小时前
Spring学习笔记
java·后端·spring
南城以南溫暖如初1472 小时前
从零搭建24小时自助健身系统:技术选型与核心模块实战
java·spring boot·redis·mysql·vue·mybatis
重生之后端学习2 小时前
53. 最大子数组和[中等]✅
java·数据结构·算法·leetcode·职场和发展
hweiyu002 小时前
Redis命令:EXPIRE
redis
XS0301062 小时前
【无标题】
java·tomcat·maven·intellij-idea