图文发布、短视频上传、内容搜索、双列瀑布流和点赞评论看似属于不同模块实际上都围绕同一条核心链路运转用户生产内容系统完成存储与审核搜索和推荐负责分发互动行为再反向影响内容排序。这条链路不仅决定发现页能否快速加载也影响热门搜索是否及时更新、关注页是否准确、消息提醒是否重复以及安卓 APP、苹果 APP、微信小程序和 H5 网页端之间的数据能否保持一致。通过 EasyES、Redis、WebSocket、对象存储和定时任务组织一套完整的内容发现与互动体系。一、内容数据不能只按“帖子表”理解最基础的内容表通常只包含标题、正文、作者、发布时间和状态但当系统同时支持图文发布、短视频发布、多图上传、话题标签、内容分类和商品关联后一条内容已经不再是简单的帖子记录。从业务结构来看一条内容至少涉及以下几类数据内容主体信息图片或视频资源作者信息话题和分类点赞、评论、收藏等统计数据内容审核状态搜索索引状态推荐权重地理位置关联商品。这些数据不适合全部堆在同一张表中。比较常见的做法是将内容主体、媒体资源、互动统计和搜索索引分别管理。MySQL 保存需要保证一致性的业务数据腾讯云对象存储负责图片和视频文件EasyES 管理搜索文档Redis 保存高频访问数据。例如一条包含九张图片的内容发布后数据库只需要保存图片地址、排序和资源类型不应该把图片文件直接写入数据库。客户端打开内容详情时再根据资源地址加载对应文件。这样的拆分可以避免内容表字段不断膨胀也方便后续增加视频封面、图片尺寸、视频时长和内容审核结果。二、发布流程应当拆成多个可追踪阶段用户点击“发布”之后后端不应该立刻把内容展示在发现页。一个相对完整的发布流程通常包括客户端提交内容 ↓ 保存草稿或生成待审核记录 ↓ 上传图片、视频到对象存储 ↓ 执行文本与媒体内容审核 ↓ 写入正式内容数据 ↓ 同步 EasyES 搜索索引 ↓ 清理相关 Redis 缓存 ↓ 进入推荐候选池草稿保存和正式发布最好使用不同状态而不是分别建立两套完全独立的数据结构。这样用户在安卓 APP 保存的草稿也可以在苹果 APP 或 H5 网页端继续编辑。例如可以为内容设置以下状态DRAFT 草稿 REVIEWING 审核中 PUBLISHED 已发布 REJECTED 审核未通过 DELETED 已删除只有状态为PUBLISHED的内容才允许进入搜索索引和推荐列表。如果审核未完成就提前写入搜索引擎用户可能通过关键词搜索到尚未公开的内容如果内容删除后没有同步删除索引又可能出现搜索结果存在、详情页却无法打开的情况。因此内容状态变化、数据库更新和索引更新之间需要有清晰的执行顺序。三、使用 EasyES 建立适合内容搜索的索引结构数据库模糊查询适合数据量较小、搜索条件简单的场景。当系统需要支持标题搜索、正文搜索、话题搜索、商品搜索、热门关键词和综合排序时继续使用大量LIKE查询会增加数据库压力。EasyES 可以用接近 MyBatis 的方式操作 Elasticsearch更适合 Java 项目快速组织搜索层代码。下面是一个简化的内容索引实体Data IndexName(community_content) public class ContentIndex { IndexId(type IdType.CUSTOMIZE) private Long contentId; IndexField(fieldType FieldType.TEXT, analyzer ik_max_word, searchAnalyzer ik_smart) private String title; IndexField(fieldType FieldType.TEXT, analyzer ik_max_word, searchAnalyzer ik_smart) private String content; IndexField(fieldType FieldType.KEYWORD) private ListString topicTags; IndexField(fieldType FieldType.KEYWORD) private String categoryCode; IndexField(fieldType FieldType.LONG) private Long authorId; IndexField(fieldType FieldType.INTEGER) private Integer contentType; IndexField(fieldType FieldType.LONG) private Long publishTime; IndexField(fieldType FieldType.DOUBLE) private Double recommendScore; }其中标题和正文使用分词字段话题标签和分类编码使用精确匹配字段。用户搜索“露营装备清单”时系统可以同时匹配标题、正文和话题标签用户进入某个内容分类时则通过categoryCode精确过滤。搜索接口不应该只返回按时间倒序排列的结果。实际排序可以综合以下因素搜索相关性 内容发布时间权重 点赞评论收藏权重 作者质量权重 当前用户兴趣权重对于内容搜索还可以设置标题命中的权重高于正文命中。例如标题中直接出现关键词的内容应当优先于正文中偶然包含关键词的内容。内容删除、隐藏或审核状态改变时也要同步处理 EasyES 索引。为了避免数据库操作成功而索引更新失败可以通过消息队列、任务表或补偿任务实现最终一致性。四、热门搜索不适合每次实时统计热门搜索通常来源于用户近期输入的搜索词。最直接的实现方式是每次搜索都向数据库插入一条日志然后通过 SQL 统计次数。这种方式在访问量较小时没有明显问题但搜索请求增加后频繁写入和聚合查询会持续占用数据库资源。更适合的方式是使用 Redis 的有序集合记录关键词热度Service RequiredArgsConstructor public class SearchHotWordService { private static final String HOT_WORD_KEY search:hot:words; private final StringRedisTemplate redisTemplate; public void increaseScore(String keyword) { if (keyword null || keyword.isBlank()) { return; } String normalizedKeyword keyword.trim().toLowerCase(); redisTemplate.opsForZSet() .incrementScore(HOT_WORD_KEY, normalizedKeyword, 1D); } public SetString getHotWords(int limit) { return redisTemplate.opsForZSet() .reverseRange(HOT_WORD_KEY, 0, limit - 1); } }Redis ZSet 同时保存关键词和分值分值越高排序越靠前。每次搜索只需要执行一次分值递增操作。为了防止旧关键词长期占据榜单可以使用 Quartz 定时任务进行热度衰减。例如每天凌晨将所有关键词分值乘以0.8让近期搜索获得更高权重。还可以将搜索热度拆成不同维度search:hot:words:global search:hot:words:category:video search:hot:words:category:goods search:hot:words:city:shanghai这样发现页可以展示全站热门搜索商品搜索页面可以展示商品类关键词附近页则可以根据城市展示区域热词。五、双列瀑布流需要稳定的分页方式内容社区常见的发现页通常采用双列瀑布流。它并不是普通列表换成两列那么简单还需要处理不同图片比例、视频封面高度、重复内容和翻页数据漂移。如果接口使用传统页码分页?page1size20 ?page2size20当新内容不断插入时第二页可能再次返回第一页出现过的数据。用户就会在瀑布流中看到重复内容。更稳妥的方式是使用游标分页。接口返回当前批次最后一条内容的排序值下一次请求将该值作为游标传回后端。例如GET /api/feed/discovery?cursor1720668123000size20发现页、关注页和附近页虽然都使用瀑布流但候选内容来源并不相同。发现页可以综合热门内容、兴趣分类、近期浏览和随机探索关注页主要读取已关注作者发布的内容附近页则根据内容发布位置和用户当前位置进行范围过滤。三类页面可以共用统一的内容卡片结构但推荐候选集和排序规则应分别实现。六、Redis 缓存应围绕高频数据而不是缓存所有数据内容详情、用户主页和商品详情通常是访问频率较高的接口。用户每次进入内容详情时页面可能需要读取内容主体作者资料点赞数量评论数量收藏数量当前用户是否点赞当前用户是否收藏关联商品信息。如果每次都执行多次数据库查询接口响应时间会随着访问量增加而变长。Redis 可以缓存相对稳定的内容主体、作者简要信息和统计数据但用户是否点赞、是否收藏属于用户维度数据缓存键需要包含用户编号。例如content:detail:10086 content:stats:10086 user:profile:9527 content:like:10086:user:9527 content:collect:10086:user:9527点赞后系统可以先更新数据库中的点赞记录再删除内容统计缓存。下一次读取时重新查询数据库并回写 Redis。对于点赞数量这类允许短时间延迟的数据也可以先在 Redis 中累加再通过 Quartz 定时任务批量回写数据库。但需要注意Redis 不能代替数据库成为唯一的数据来源。尤其是收藏记录、关注关系、购物车、订单状态和支付结果都需要在数据库中保留可靠记录。七、推荐排序来自多个行为信号内容推荐不是简单地查询点赞量最高的内容。如果只按总点赞量排序发布时间较早的内容会长期占据首页新内容很难获得曝光。可以为内容建立一个动态推荐分值推荐分值 点赞数 × 点赞权重 评论数 × 评论权重 收藏数 × 收藏权重 完播率 × 视频权重 内容新鲜度 用户兴趣匹配度 - 重复曝光惩罚其中收藏行为通常比点赞行为表达更强的兴趣评论行为又比简单浏览更有价值。浏览记录不仅用于个人中心的历史记录页面也可以参与推荐计算。例如用户近期多次浏览某个话题、分类或作者系统可以适当提高相关内容的候选比例。关注页则不需要复杂的兴趣推荐重点是保证关注作者的新内容能够稳定展示。粉丝列表、关注列表和用户主页中的关注状态也应当基于同一套关注关系数据避免不同页面显示不一致。内容与商品产生关联后商品详情、购物车、立即购买和订单管理仍应属于独立的商城业务域。内容模块只保存商品关联编号不直接承担库存扣减、支付状态和订单流转。这样即使后续调整商城结构也不会破坏内容索引和推荐逻辑。八、互动完成后还需要消息回流用户完成点赞、评论、回复或关注操作后除了更新互动数据还可能产生一条通知。例如用户 A 评论用户 B 的内容 ↓ 写入评论记录 ↓ 更新内容评论数量 ↓ 生成评论通知 ↓ 通过 WebSocket 推送给用户 B ↓ 用户 B 打开通知后更新已读状态WebSocket 适合传递评论通知、点赞提醒、关注提醒以及单聊消息。图片消息和表情消息只需要使用不同的消息类型不应该建立多套完全分离的连接。下面是一个简化的消息确认处理示例Service RequiredArgsConstructor public class MessageAckService { private final StringRedisTemplate redisTemplate; public void saveUnread(Long receiverId, Long messageId) { String key message:unread: receiverId; redisTemplate.opsForZSet() .add(key, messageId.toString(), System.currentTimeMillis()); } public void markAsRead(Long receiverId, Long messageId) { String key message:unread: receiverId; redisTemplate.opsForZSet() .remove(key, messageId.toString()); } public Long countUnread(Long receiverId) { String key message:unread: receiverId; return redisTemplate.opsForZSet().zCard(key); } }用户在微信小程序中读取消息后后端更新统一的已读状态安卓 APP、苹果 APP 和 H5 网页端再次连接时都能获得相同的未读数量。WebSocket 只负责实时推送消息本身仍然需要持久化。因为用户可能处于离线状态也可能在网络不稳定时错过实时消息。系统通知、评论通知、点赞提醒和关注提醒可以共用通知表通过消息类型区分单聊消息则可以单独保存会话编号、发送方、接收方、消息内容和已读状态。九、四端数据互通依赖统一业务接口安卓 APP、苹果 APP、微信小程序和 H5 网页端共用同一套后台时前端只是不同的展示入口内容、用户、消息、商品和订单数据都应该由服务端统一管理。例如用户在 H5 页面收藏一条内容后苹果 APP 的收藏管理中也应该立即显示用户在微信小程序中把商品加入购物车后安卓 APP 打开购物车时也应该读取同一份数据。因此接口设计不能把业务规则写死在某个客户端中。比较合理的职责划分是客户端负责页面展示、交互和本地状态 服务端负责权限、数据校验和业务规则 MySQL负责持久化业务数据 Redis负责热点缓存和临时状态 EasyES负责内容与商品搜索 WebSocket负责实时消息 对象存储负责图片与视频文件移动端响应式适配主要解决 H5 页面在不同屏幕宽度下的布局问题。双列瀑布流需要根据屏幕宽度计算卡片尺寸但内容排序和分页逻辑仍然由服务端统一控制。十、连接池、权限和定时任务属于基础保障Druid 连接池可以集中管理数据库连接并观察慢 SQL、连接数量和执行时间。对于发现页、用户主页、商品列表和订单查询等高频接口SQL 索引和连接池配置同样重要。Shiro 权限控制则适合处理登录状态、接口访问权限和后台管理角色。普通用户可以发布内容、评论和收藏管理端则需要执行内容审核、分类管理和系统通知等操作。Quartz 定时任务可以承担以下工作热门搜索分值衰减Redis 点赞统计回写搜索索引补偿失效订单关闭浏览记录清理推荐分值重新计算。短信服务通常用于登录验证码、账号安全验证和手机号码绑定不应参与普通内容接口。在工程组织上如果代码需要满足可商用、二次开发和独立部署等约束就更需要保持内容、用户、即时通讯、商城和订单模块之间的边界。模块之间尽量通过接口、事件或明确的数据编号关联避免直接跨模块修改内部数据。十一、从一次内容发布看完整技术链路将前面的模块串联起来一条图文或短视频内容从发布到产生互动大致会经历以下过程1. 用户上传图片或视频 2. 文件进入对象存储 3. 内容保存为草稿或待审核状态 4. 文本和媒体执行内容审核 5. 审核通过后正式发布 6. EasyES 创建搜索索引 7. Redis 清理相关列表缓存 8. 内容进入发现页或关注页候选池 9. 用户通过搜索、推荐或话题进入详情 10. 浏览记录写入用户行为数据 11. 点赞、评论、收藏更新互动统计 12. WebSocket 推送互动通知 13. 互动数据反向参与后续推荐排序这条链路说明内容社区中的搜索、推荐、互动、消息和多端同步并不是相互独立的功能。EasyES 解决内容检索和复杂筛选Redis 负责热门数据与高频访问WebSocket 负责实时消息MySQL 保存可靠业务数据对象存储承载图片和视频文件。只有明确每种技术负责的边界才能避免把所有压力集中到单一组件上。对于采用双列瀑布流、内容搜索、话题聚合、商品关联和即时通讯的内容社区来说真正需要优先确定的并不是页面数量而是内容数据如何流转、索引如何同步、缓存如何失效以及用户行为如何回到下一轮内容分发中。与功能架构相关的资料整理湖南宠友信息技术有限公司是一家专注社区交友类产品、企业即时通信软件开发为企业提供即时通信工具、垂直类内容圈子自主研发的业界知名友猫产品拥有广大的企业用户群体https://chongyou.info/1/product/tm.html