
做文学创作类的社交论坛和做普通的技术论坛、电商系统完全是两种思路。这个项目我前前后后从立项到上线跑了大概两个月踩过的坑比写过的代码还多。SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这套组合单看每一个都是熟面孔但拼在一起服务一个“作者写稿、读者追更、评论区吵架”的社区场景时很多细节就完全不一样了。今天把这套系统的完整设计思路和关键代码实现一次性整理出来给准备做同类项目的朋友做个参考。1. 项目定位与核心需求拆解文学创作社交论坛听起来就是一个普通的 UGC 社区但把“文学创作”四个字拆开看需求就变得非常具体。它不等同于微博那种短内容流也不等同于知乎那种问答社区它有一个非常鲜明的特征创作内容的篇幅长、结构复杂、且带有强烈的个人风格。系统设计必须围绕这个核心来展开。1.1 “创作”二字决定了系统的主次关系做这个项目之前我一直在想论坛的核心功能到底是“社交”还是“创作”。后来想明白了创作是根社交是叶。一个作者把自己写的小说章节、诗歌、散文发上来他需要的第一是“我的作品得完整保存、排版不能乱”第二才是“有人来看、来评论”。这就直接拉高了对后端数据模型的要求。普通的动态表一个 content 字段就完了但文学作品的内容往往包含章节结构。一个长篇可以是《书名 - 卷 - 章节 - 正文片段》这样的四级结构短篇至少也是“标题 正文 创作说明”。我在设计表结构时引入了章节表和作品表分离的方案作品表存元信息标题、封面、简介、状态、字数章节表存具体的正文内容。这样做的好处是作者可以分章连载读者可以按章节跳读而没必要每次拉取整个作品全文。1.2 社交属性的差异化设计文学社区的社交和普通社区不一样它存在三种不同强度的关系轻量互动点赞、收藏、中量互动评论、留言、重量互动私信、连载订阅。我在做需求分析时把互动行为拆成了独立的“行为记录表”而不是把点赞数、收藏数直接写死在作品表里。虽然后者做起来简单但一旦遇到“我点赞过哪些作品”这类反查需求就会非常痛苦。另外必须考虑一个特有场景作者和读者之间存在一种“创作反馈”关系。读者的一条长评对作者来说可能比一百个点赞都重要。所以系统里我特意把“评论”和“书评”做了区分书评是长文、可以打分、挂在作品主页评论是短消息、挂在章节下面。这个设计在做需求评审时被反复讨论过最后证明是很值得的因为作者后台可以分门别类地看到“对我作品的深度反馈”和“对我某个章节的即时反馈”体验差异非常大。1.3 系统的角色边界系统涉及三类角色游客、注册用户、管理员。游客只能看作品列表、作品详情、部分章节内容注册用户可以发文、评论、点赞、收藏、关注作者管理员负责审核违规内容、处理举报、封禁账号。这里有一个容易被忽略的点未登录用户到底能读多少章节。很多文学社区的做法是前几章免费、后续需要登录或积分这个业务规则必须在需求阶段就定清楚否则开发到一半会改得焦头烂额。2. 技术选型与整体架构思路SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这套组合放在今天依然是中小型社区项目的稳妥之选。选择它们不是因为“流行”而是每一层都能解决当前场景下的具体痛点。2.1 为什么不用 SpringBoot3 而选 SpringBoot2这个决定当时也纠结过。SpringBoot3 已经是新项目的默认选择但考虑到这个项目的特点最终锁定了 SpringBoot2。原因是生态兼容性很多针对 SpringBoot2 的插件、代码生成器、第三方 starter 都经过了大量的生产环境验证文档丰富遇到问题一搜就有答案。而 SpringBoot3 基于 Jakarta EE部分中间件和工具库还在适配期有些坑查半天都找不到解决方案。当然这不是说 SpringBoot3 不好而是对这个项目来说稳定压倒一切。如果你是一个人开发或者小团队交付我建议以“不折腾”为第一原则——框架是拿来快速实现业务逻辑的不该在一个偏僻的兼容性问题上消耗太多精力。2.2 Vue3 组合式 API 带来的开发体验提升前端选择 Vue3核心原因是组合式 API。文学论坛的前端页面有一个特点详情页信息密度极高。一个作品详情页至少包含作品信息、作者信息、章节列表、读者评论、相关推荐五个区域如果用 Vue2 的 Options API 写逻辑会散落在 data、methods、computed 各个块里换成 Composition API 之后我可以把“获取作品详情并且联动加载评论数据的逻辑”打包成一个可复用的组合函数比如useWorkDetail不仅这个页面能用后台管理页稍微改改也能复用。Vue3 的响应式代理机制Proxy相比 Vue2 的 defineProperty 还有一个实际收益对象深层属性新增、数组下标变更都能被自动侦测到。这对论坛的实时交互很有用比如读者给评论点赞后需要从 0 变 1在列表里直接修改嵌套对象就可以触发视图更新不需要像 Vue2 那样用 $set 手动处理。2.3 MyBatis-Plus 解决了什么核心诉求MyBatis-Plus 最大的价值不是“少写几行代码”而是它的内置能力天然匹配这个项目的 CRUD 密集型场景。举个例子作品列表页需要分页查询MP 的 PaginationInnerInterceptor 只需要在配置类里注册一个 Bean然后调用selectPage就能拿到分页结果。如果不引入它我大概得手写一套 PageHelper 或者自己拼 LIMIT 语句还要统一封装返回结构。更实用的是它的条件构造器LambdaQueryWrapper。文学社区的后台管理经常需要组合条件搜索比如“查所有状态为已发布、分类为玄幻、字数大于10万、最近一周有更新的作品”用 LambdaQueryWrapper 可以把这些条件拖链式地拼出来代码看着非常舒服而且因为是 Lambda 表达式引用实体字段字段名写错了编译期就能发现不会跑到线上才爆出 SQL 错误。2.4 MySQL8.0 带来的架构红利MySQL8.0 相比 5.7 对社区系统非常有用的其实有两块窗口函数和更好的 JSON 支持。前者可以用一行 SQL 实现“每个分类下按热度排序的作品 Top10”这在推荐区和排行榜模块非常常用后者可以让我在设计作品扩展字段比如读者标签、内容标签云时直接在表里放一个 JSON 列而不是拆成关联表。初期项目就该这么做灵活度远大于范式化的表结构等业务复杂了再做迁移也不迟。另外 MySQL8.0 的默认字符集已经是 utf8mb4这对于文学创作论坛是刚需——作者可能会在正文里写一些特殊符号、生僻字、古文用字utf8mb3 存某些四字节字符会报错而 8.0 默认就支持。这一项就避免了上线后“字符集导致存储失败”的惨案。3. 数据库设计与核心表结构论坛类项目的数据模型是个老话题但文学创作社区的表结构和普通帖子社区差异非常大。我重点讲三张核心表的设计思路用户表、作品表、章节表顺便说说互动行为表怎么处理。3.1 用户表与作者身份的设计要点用户表是系统的地基。字段设计上除了常见的 username、password、nickname、avatar、email我建议一定要加一个signature个性签名字段。对于文学社区来说个性签名几乎等于作者的“门面”很多人靠一句话决定要不要关注这个人。密码存储用的是 BCrypt 加密这里有一个真实的教训早期我测试时用了简单的 MD5 加盐后来安全意识上来才意识到对于 UGC 社区用户注册的密码一旦泄露用户可能用同一个密码在其他平台登录责任非常重大。BCrypt 嵌入成本很低Spring Security Crypto 包直接有 BCryptPasswordEncoder用起来毫不费力没理由不用。作者身份不要单独建一张“作者表”正确的做法是在用户表上增加author_status字段0 是普通用户1 是作者2 是签约作者。因为一个用户天然可以既是读者又是作者拆表反而会平添复杂度。3.2 作品表与章节表的分层设计作品表work的核心字段包括work_id主键、user_id作者、title标题、category_id分类、description简介、cover_url封面、status连载中/已完结/暂停、word_count字数、view_count浏览量、like_count点赞量、favorite_count收藏量、create_time、update_time。为什么把浏览量、点赞量这些计数冗余在作品表里因为列表页和详情页查一次就把数据带出来了避免每次都去行为表里做 count(*)。但这里要强调冗余计数的前提是有配套的同步机制。我用的是“异步更新 定时对账”方案——点赞行为先改行为表然后通过消息队列异步加一计数每天凌晨再跑一个任务对账统计真实数值修正缓存累计偏差。章节表chapter的字段则更精简chapter_id、work_id、title、contentLONGTEXT、sort_order章节序号、word_count、create_time。sort_order很关键不要用自增主键表示章节顺序因为作者可能插队发一个番外篇此时 sort_order 可以灵活设置而主键一旦定了就很难调整。正文内容我直接放在了 MySQL 里没有上对象存储。原因很简单——文学作品的正文是文本文本的体量再大也有限除非你做到连载百万字级别的长篇密集存储否则 MySQL 的磁盘处理完全扛得住。等真到了那个量级再考虑把超过一定字数的正文迁到 OSS 也不晚数据表里预留一个 content_source 字段本地/OSS以便将来扩展就行。3.3 评论、点赞、收藏、关注的行为表设计行为表我统一采用“一张表一种行为”的原则没有做大而全的“行为类型 对象类型”多态表。原因多态表写起来简单但查询时 SQL 极其别扭比如查“某个用户点赞过的所有作品”需要两次 JOIN 判断对象类型。反而不如直接建work_like、work_favorite、chapter_comment、user_follow四张表来得直观。以work_like为例核心字段就是 id、user_id、work_id、create_time加一个唯一索引(user_id, work_id)。这个唯一索引不仅仅是防止重复点赞更是给后续查询“我是否点过赞”提供索引支撑。点赞/取消点赞的并发安全问题靠唯一索引捕获 DuplicateKeyException 来处理既简单又可靠。user_follow关注关系需要注意方向性。设计时一定要把 follower_id粉丝和 following_id被关注者分清楚。我在开发时就犯过迷糊把查询“我关注的人”和“我的粉丝”搞混最后靠写清晰命名的 Service 方法才理清——listFollowingByUserId和listFollowersByUserId一字之差业务完全不一样。3.4 一个建议软删除与自动填充关键业务表必须做逻辑删除。MyBatis-Plus 的TableLogic注解加上在实体类字段标记 delete_flag所有查询就会自动带上WHERE delete_flag 0删除变成 UPDATE。这在社区场景尤其重要因为管理员删除一条违规评论后作者的回复、其他用户的引用可能还在物理删除会引发数据完整性问题。用逻辑删除则保留所有关联数据只是对外不可见。自动填充也建议用 MP 的 MetaObjectHandler 统一处理 create_time、update_time 这些公共字段不要在每个业务代码里手动塞 currentTimeMillis。这个看似琐碎的习惯能避免一大类“某个接口忘了更新更新时间导致列表排序错乱”的低级 Bug。4. 后端核心功能实现与业务逻辑后端基于 SpringBoot2 做模块化分包按照 controller/service/mapper 三层结构组织。下面挑四个核心模块讲实现细节认证授权、作品发布、互动行为、搜索推荐。这些都是论坛系统的命脉。4.1 基于 JWT 的登录认证踩坑记录JWT 无状态认证在论坛系统里非常合适因为论坛的访问量特点是“读多写少”token 模式能减轻服务端 session 压力。我用的方案是登录成功后后端返回 token 和用户基本信息昵称、头像、ID前端用 Pinia 存储用户状态每次请求在拦截器里把 token 塞进 Authorization 头。这里有三个坑必须提醒。第一个是token 过期策略。我设置的是 7 天有效起初以为省事后来发现用户在社区里连着挂后台几天token 过期后所有操作突然失效体验极差。最后改成双 token 方案access_token 有效 2 小时refresh_token 有效 7 天刷新接口在白名单里。第二个是登出问题。JWT 无状态登出是不能真正让 token 失效的我处理方式是把失效 token 放入 Redis 黑名单设置其过期时间为 token 剩余有效时间。第三个是用户被封禁后的 token 即时失效。这离不开 Redis 的辅助在拦截器里不仅要校验 JWT 签名还要查一下该用户 ID 是否在封禁名单里命中的话直接拒绝访问。JWT 的密钥配置也存在一个教训早期我把秘钥写在 application.yml 里并提交到了 Git 仓库后来被扫描工具报警才发现。正确的做法是通过环境变量注入比如Value(${jwt.secret})配合服务器环境变量或者用配置中心。代码写得好不好是次要的密钥泄露直接关系到用户数据安全这个底线不能丢。4.2 作品发布与章节管理的状态机作品发布不是一个简单的 Insert它应当是一个状态流转的过程。我用状态机管理草稿(0) - 审核中(1) - 已发布(2) - 已下架(3) - 审核失败(4)。作者创建作品时默认是草稿点击发布进入审核通过后对外可见如果管理员发现违规直接改成已下架作者可以修改后重新提交审核。这里我自己写了一个状态变更的校验工具方法核心逻辑是维护一张合法的状态迁移表。比如“已发布”的作品不能直接改回“草稿”必须先“下架”再编辑。虽然稍微繁琐但它能拦住一些非常隐蔽的并发异常——比如作者在编辑旧版本的同时管理员下架了该作品如果状态没有被校验就会出现“下架的作品重新变得可见”的尴尬。章节发布比作品更轻量不需要审核。但我在发布章节时做了一件很多人会忽略的事自动更新作品总字数。每新增或修改一章就把新字数与旧字数的差值累加到作品的 word_count 上。让读者看到一本正在连载中的小说字数在不断增长会给作品带来一种“活着的氛围”对于社区留存非常有用。实现上我用的是 MP 的 UpdateWrapper 做“原子自增”LambdaUpdateWrapperWork wrapper new LambdaUpdateWrapper(); wrapper.eq(Work::getWorkId, workId) .setSql(word_count word_count diff); workMapper.update(null, wrapper);不要先查出旧值再加回去并发时会产生丢失更新。用 setSql 做数据库端原子操作是最稳妥的。4.3 点赞、收藏、关注的三高并发处理互动行为面对的最大问题是“怎么在高并发下不崩”。我的做法是请求入口直接沿用数据库唯一索引来保证幂等计数用异步消息处理。比如点赞请求进来先尝试插入work_like记录如果 DuplicateKeyException 就说明重复点赞直接返回“已赞”插入成功则向消息队列发一条“点赞计数1”的事件由消费者异步更新作品表计数。收藏、关注同理。用这种方案互动接口的平均响应时间可以压在 50ms 以内不会因为在更新大表的 count 字段时造成锁竞争。Redis 在互动系统里还有一个大用处热点作品计数缓存。访问量特别高的作品详情页直接把 view_count 先从 Redis 里加一再定期刷回数据库。注意一定要用 Redis 的 INCR 命令它天生就是线程安全的不需要额外的分布式锁。4.4 搜索与推荐模块的取舍搜索确实是个绕不开的功能但我没有为它引入 Elasticsearch而是用 MySQL 的全文索引撑住了第一版需求。因为社区初期数据量不大作品标题、作者昵称这些字段做全文索引已经够用。但正文搜索就不行了正文是 LONGTEXT全文索引效果极差我的处理是暂时不提供正文搜索只搜标题、简介、作者名。这个取舍是务实的——与其做一个搜不准的“全文搜索”不如先把标题搜索做准。推荐模块我做了个简单的“热度分”计算热度分 浏览量 * 0.4 点赞量 * 0.3 收藏量 * 0.2 评论量 * 0.1再乘一个时间衰减因子1 / (7 距发布天数)。这个公式不需要很高级但非常直观新作品有出头的机会老经典也不会被淹没。榜单查询用 MySQL8.0 的窗口函数一行就搞定了SELECT work_id, title, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY hot_score DESC) AS rn FROM work WHERE status 2 AND is_delete 0这是 MySQL8.0 才能给的便利放在 5.7 时代得用临时变量写半天。5. 前端 Vue3 实现要点与工程化实践前端的任务是把上面的所有功能变成一套流畅可用的界面。Vue3 部分我重点讲工程架构、核心页面拆解、状态管理、以及编辑器选型。5.1 基于 Vite 的工程搭建与目录规范前端工程基于 Vite 搭建完全听从了“快”的号召。Vite 冷启动速度比 Webpack 快一个量级开发时改动热更新基本在几百毫秒内体验非常舒服。用npm create vuelatest初始化项目会自动生成基于 Composition API 的模板省去手动配置的时间。目录结构我按模块划分而非按文件类型划分src/ api/ # 接口请求封装 assets/ # 静态资源 components/ # 公共组件 composables/ # 组合式函数 router/ # 路由配置 stores/ # Pinia 状态 views/ # 页面组件特别提一下composables目录这是 Vue3 组合式 API 给项目带来的最大红利。以“作品列表”逻辑为例我写了一个useWorkList.js里面封装了分页参数、列表数据、加载状态、加载更多方法。结果这个函数在三处复用首页推荐列表、分类页列表、个人中心“我的作品”列表。一次封装三处受益改动逻辑时只改一处。5.2 Pinia 状态管理与用户登录态管理状态管理选型上用 Pinia 替代了 Vuex。原因非常简单Pinia 的 API 更简洁没有 mutation 的概念直接在 actions 里写异步逻辑TypeScript 支持也更好。虽然项目没有用 TypeScript前期为了赶进度但 Pinia 依然让代码简化不少。Pinia 里我建了三个 storeuserStore用户信息、登录态、appStore全局配置如分类列表缓存、noticeStore通知消息数量。userStore 的 store 在初始化时会调用GET /api/user/info接口该接口通过 token 动态获取用户最新信息。这个设计的重要意义在于用户信息以服务器为准。如果用户被封禁、昵称被修改、头像被更新前端刷新页面后能立即同步不会显示过期信息。5.3 编辑器与内容展示的实现细节文学论坛最核心的交互是写稿和读稿。写稿用编辑器读稿用排版。我调研了一圈最终选用了 wangEditor 5它是国产开源编辑器里对 Vue3 支持最好的之一。虽然不支持 Markdown 语法输入但工具栏可以配置加粗、斜体、标题、引用、代码块等对于写长篇小说来说足够了。这里有个非常关键的处理编辑器输出的 HTML 必须经过 XSS 过滤再入库。用户是没有底线可言的他们可能给文章插入 script 标签、iframe 弹窗、恶意链接。我用 js-xssxss 库对 HTML 内容做了白名单过滤只保留 p、span、strong、em、h1-h6、blockquote、img 等标签并过滤掉所有事件属性onclick、onerror 等。这个过滤动作不仅要在前端做后端入库前必须再做一次——永远默认“前端的一切都不可信”。章节阅读页则是重点打磨过的。正文内容我用 CSS 控制最大宽度约 720px行高 1.8字号 17px段间距 1.2em。别小看这些排版参数对长文阅读体验的改善是质变级的。我最初用的是默认 14px 字和 1.5 行高读者反馈“看一会就累”调到 17px 之后阅读舒适度大幅提升。文字排版是文学社区的门面宁可多花时间调样式也不要图省事套一个通用排版。5.4 路由守卫与权限控制前端路由用了三级权利控制公共路由首页、作品列表、作品详情、章节详情、登录路由个人中心、创作中心、管理员路由审核管理、用户管理。我用的 Vue Router 的beforeEach导航守卫来做拦截router.beforeEach((to, from, next) { const userStore useUserStore(); if (to.meta.requiresAuth !userStore.isLoggedIn) { next({ path: /login, query: { redirect: to.fullPath } }); return; } if (to.meta.requiresAdmin userStore.userInfo.role ! 1) { next({ path: /403 }); return; } next(); });登录后要回跳原页面用redirect参数实现这个细节能显著提升用户体验不然用户辛辛苦苦写了一段评论登录完跳回首页前功尽弃换谁都想卸载这破站。6. 部署上线与常见问题排查实录6.1 Docker Compose 一键编排后端与数据库部署阶段我用了 Docker Compose把 MySQL8.0 和应用服务编排在一起。数据库的部署有一个必须注意的点8.0 的认证插件默认是 caching_sha2_password而一些老的客户端驱动不支持连接时会直接报错。如果你用 Docker 镜像启动时加上--default-authentication-pluginmysql_native_password就能兼容services: mysql: image: mysql:8.0 container_name: forum-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: literary_forum ports: - 3306:3306 command: --default-authentication-pluginmysql_native_password volumes: - ./mysql-data:/var/lib/mysql挂载数据卷是必须的否则容器删了数据就没了这是新手踩得最多的大坑。6.2 上线后遇到的 5 个典型问题问题一数据库连接报 Public Key Retrieval is not allowed用 MySQL8.0 配合某些连接池时数据库连接串要加allowPublicKeyRetrievaltrueuseSSLfalse。我排查时发现只加了useSSLfalse还不行必须两个参数都配。原因和 caching_sha2_password 的密钥交换机制有关加上后立解。问题二MyBatis-Plus 分页不生效MP 的分页拦截器从 3.5.x 版本开始有个变化不仅要注册 PaginationInnerInterceptor还需注意注册顺序。如果项目里有多个 InnerInterceptorPaginationInnerInterceptor 必须放在最后否则分页 SQL 可能被其他拦截器改写掉导致查出来全表数据。问题三Vue3 打包后部署到 Nginx刷新页面 404这是因为你的路由是 history 模式Nginx 没有配置 try_files 回退。解决办法是在 nginx.conf 的 location 块里加location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }否则刷新一个/work/123页面Nginx 去找物理路径根本不存在就返回 404 了。问题四富文本内容里的图片上传后无法显示编辑器里的图片上传组件要单独处理上传接口的 token 认证。我最初只给普通请求加了 Authorization 头忽略了编辑器上传组件走的是一套自己的 XHR结果所有图片上传都 401。解决方式是在编辑器的 customUpload 回调里手动加上 Authorization 头。问题五连接池连接耗尽导致接口突然大面积超时上线初期连接数是默认的 10结果评论区一有热门事件接口立刻就超时。HikariCP 配置改成maximum-pool-size: 50minimum-idle: 10connection-timeout: 30000同时检查慢 SQL主要是几个列表查询没有命中索引加上联合索引后问题缓解。6.3 性能优化与安全加固的清单性能层面我给几个实际验证过的硬指标。作品列表页的 SQL 必须覆盖(status, category_id, update_time)联合索引评论列表页覆盖(work_id, create_time)联合索引。热点作品详情页加 Redis 缓存缓存时间 10 分钟缓存对象是“作品基础信息 最近 20 个章节的标题列表”。安全层面做了三件事全站请求加X-Content-Type-Options: nosniff和X-Frame-Options: DENY响应头防内容嗅探和点击劫持所有写操作接口强制校验用户状态封禁用户直接不进入业务逻辑评论和书评内容统一经过 HTML 转义加敏感词过滤同时后端入库前用正则再次校验 URL 协议禁止 javascript: 协议。7. 含文档交付的整理思路项目标题里强调“含文档”这个说法在实际交付时意义重大。文档不是写给自己看的而是写给接手的同事、维护的运维、以及任何可能继续开发的人看的。一套好文档能让这套系统的技术债变成技术资产。7.1 接口文档与设计文档怎么组织我常用的文档结构分四层README项目介绍、技术栈、如何启动、数据库设计文档每个表字段含义、索引设计说明、ER 关系图、接口文档每个接口的入参、出参、错误码、部署文档从零开始部署的完整步骤。这套文档必须在开发过程中同步写开发结束再补文档大概率会漏掉大量只有当时的自己才清楚的细节。7.2 给初学者的快速启动指南如果你第一次接触这类项目启动流程要按顺序走先创建数据库并导入 SQL 脚本修改 application.yml 里的数据库连接信息启动后端能看到 SpringBoot 启动日志带出本机端口就说明后端 OK前端npm install安装依赖npm run dev启动开发服务浏览器访问localhost:5173注册一个账号就能体验完整流程了。这里有一个我特别想提醒的细节npm install 失败时不要第一时间怀疑代码问题大概率是镜像源的问题npm config set registry https://registry.npmmirror.com就能解决。还有 JDK 版本问题SpringBoot2 建议使用 JDK8 或 JDK11不要直接上 JDK17因为某些老版本的依赖在 JDK17 下会有模块访问报错。这套系统做完最大的体会其实不在技术上。文学创作论坛和普通社区最本质的区别是你要尊重作者表达的长篇性、沉浸感和连续性。技术架构是为了这个目标服务的——数据库设计要能支撑连载结构编辑器选择要能保证排版不崩阅读页排版要让人读得下去审核机制要能保护创作者免受恶意攻击。如果只盯着技术栈而忽略了业务本身的特质做出来的只是一个披着“文学社区”外衣的普通论坛。最后再分享一个我这个项目里的小经验做社区系统永远把“防滥用”和“用户体验”放在同一个优先思考的维度。评论里有人发垃圾广告封敏感词撞上先告诉用户“内容包含违规词”而不是默默吞掉让用户一头雾水。把规则的执行逻辑想清楚、提示写明白比什么都强。希望这套剖析能帮到正在做或者打算做同类项目的朋友。